This is an automated email from the ASF dual-hosted git repository.

adamsaghy pushed a commit to branch develop
in repository https://gitbox.apache.org/repos/asf/fineract.git

commit 5df6af6a23e5a9a25f8b0959b3584bcc4816e1c8
Author: Oleksii Novikov <[email protected]>
AuthorDate: Fri Sep 12 10:59:05 2025 +0300

    FINERACT-2354: Merge re-aged installment with the next (N+1) installment if 
they overlap
---
 .../test/resources/features/LoanReAging.feature    | 120 +++++++++++++++++++++
 ...dvancedPaymentScheduleTransactionProcessor.java |  98 +++++++++++++----
 2 files changed, 195 insertions(+), 23 deletions(-)

diff --git 
a/fineract-e2e-tests-runner/src/test/resources/features/LoanReAging.feature 
b/fineract-e2e-tests-runner/src/test/resources/features/LoanReAging.feature
index 0da3fbbd4e..c32e07bc0f 100644
--- a/fineract-e2e-tests-runner/src/test/resources/features/LoanReAging.feature
+++ b/fineract-e2e-tests-runner/src/test/resources/features/LoanReAging.feature
@@ -1652,3 +1652,123 @@ Feature: LoanReAging
       | Name    | isPenalty | Payment due at       | Due as of   | Calculation 
type | Due  | Paid | Waived | Outstanding |
       | NSF fee | true      | Specified due date   | 01 May 2025 | Flat        
     | 20.0 | 0.0  | 0.0    | 20.0        |
 
+  Scenario: Verify merging re-aging transaction with N+1 installment in the 
same bucket(YEARS)
+    When Admin sets the business date to "01 January 2024"
+    When Admin creates a client with random data
+    When Admin creates a fully customized loan with the following data:
+      | LoanProduct                                      | submitted on date | 
with Principal | ANNUAL interest rate % | interest type | interest calculation 
period | amortization type  | loanTermFrequency | loanTermFrequencyType | 
repaymentEvery | repaymentFrequencyType | numberOfRepayments | 
graceOnPrincipalPayment | graceOnInterestPayment | interest free period | 
Payment strategy            |
+      | LP2_DOWNPAYMENT_AUTO_ADVANCED_PAYMENT_ALLOCATION | 01 January 2024   | 
100            | 0                      | FLAT          | 
SAME_AS_REPAYMENT_PERIOD    | EQUAL_INSTALLMENTS | 3                 | MONTHS   
             | 1              | MONTHS                 | 3                  | 0 
                      | 0                      | 0                    | 
ADVANCED_PAYMENT_ALLOCATION |
+    And Admin successfully approves the loan on "01 January 2024" with "100" 
amount and expected disbursement date on "01 January 2024"
+    When Admin successfully disburse the loan on "01 January 2024" with "100" 
EUR transaction amount
+    When Admin sets the business date to "05 April 2024"
+    And Admin adds "LOAN_SNOOZE_FEE" due date charge with "03 May 2024" due 
date and 100 EUR transaction amount
+    When Admin creates a Loan re-aging transaction with the following data:
+      | frequencyNumber | frequencyType | startDate     | numberOfInstallments 
|
+      | 1               | YEARS         | 01 April 2024 | 3                    
|
+    Then Loan Repayment schedule has 7 periods, with the following data for 
periods:
+      | Nr | Days | Date             | Paid date       | Balance of loan | 
Principal due | Interest | Fees  | Penalties | Due   | Paid | In advance | Late 
| Outstanding |
+      |    |      | 01 January 2024  |                 | 100.0           |     
          |          | 0.0   |           | 0.0   | 0.0  |            |      |   
          |
+      | 1  | 0    | 01 January 2024  | 01 January 2024 | 75.0            | 
25.0          | 0.0      | 0.0   | 0.0       | 25.0  | 25.0 | 0.0        | 0.0  
| 0.0         |
+      | 2  | 31   | 01 February 2024 | 05 April 2024   | 75.0            | 0.0 
          | 0.0      | 0.0   | 0.0       | 0.0   | 0.0  | 0.0        | 0.0  | 
0.0         |
+      | 3  | 29   | 01 March 2024    | 05 April 2024   | 75.0            | 0.0 
          | 0.0      | 0.0   | 0.0       | 0.0   | 0.0  | 0.0        | 0.0  | 
0.0         |
+      | 4  | 31   | 01 April 2024    | 05 April 2024   | 75.0            | 0.0 
          | 0.0      | 0.0   | 0.0       | 0.0   | 0.0  | 0.0        | 0.0  | 
0.0         |
+      | 5  | 0    | 01 April 2024    |                 | 50.0            | 
25.0          | 0.0      | 100.0 | 0.0       | 125.0 | 0.0  | 0.0        | 0.0  
| 125.0       |
+      | 6  | 365  | 01 April 2025    |                 | 25.0            | 
25.0          | 0.0      | 0.0   | 0.0       | 25.0  | 0.0  | 0.0        | 0.0  
| 25.0        |
+      | 7  | 365  | 01 April 2026    |                 | 0.0             | 
25.0          | 0.0      | 0.0   | 0.0       | 25.0  | 0.0  | 0.0        | 0.0  
| 25.0        |
+
+  Scenario: Verify merging re-aging transaction with N+1 installment in the 
same bucket(MONTHS)
+    When Admin sets the business date to "01 January 2024"
+    When Admin creates a client with random data
+    When Admin creates a fully customized loan with the following data:
+      | LoanProduct                                      | submitted on date | 
with Principal | ANNUAL interest rate % | interest type | interest calculation 
period | amortization type  | loanTermFrequency | loanTermFrequencyType | 
repaymentEvery | repaymentFrequencyType | numberOfRepayments | 
graceOnPrincipalPayment | graceOnInterestPayment | interest free period | 
Payment strategy            |
+      | LP2_DOWNPAYMENT_AUTO_ADVANCED_PAYMENT_ALLOCATION | 01 January 2024   | 
100            | 0                      | FLAT          | 
SAME_AS_REPAYMENT_PERIOD    | EQUAL_INSTALLMENTS | 3                 | MONTHS   
             | 1              | MONTHS                 | 3                  | 0 
                      | 0                      | 0                    | 
ADVANCED_PAYMENT_ALLOCATION |
+    And Admin successfully approves the loan on "01 January 2024" with "100" 
amount and expected disbursement date on "01 January 2024"
+    When Admin successfully disburse the loan on "01 January 2024" with "100" 
EUR transaction amount
+    When Admin sets the business date to "05 April 2024"
+    And Admin adds "LOAN_SNOOZE_FEE" due date charge with "03 May 2024" due 
date and 100 EUR transaction amount
+    When Admin creates a Loan re-aging transaction with the following data:
+      | frequencyNumber | frequencyType | startDate     | numberOfInstallments 
|
+      | 1               | MONTHS        | 01 April 2024 | 3                    
|
+    Then Loan Repayment schedule has 7 periods, with the following data for 
periods:
+      | Nr | Days | Date             | Paid date       | Balance of loan | 
Principal due | Interest | Fees  | Penalties | Due   | Paid | In advance | Late 
| Outstanding |
+      |    |      | 01 January 2024  |                 | 100.0           |     
          |          | 0.0   |           | 0.0   | 0.0  |            |      |   
          |
+      | 1  | 0    | 01 January 2024  | 01 January 2024 | 75.0            | 
25.0          | 0.0      | 0.0   | 0.0       | 25.0  | 25.0 | 0.0        | 0.0  
| 0.0         |
+      | 2  | 31   | 01 February 2024 | 05 April 2024   | 75.0            | 0.0 
          | 0.0      | 0.0   | 0.0       | 0.0   | 0.0  | 0.0        | 0.0  | 
0.0         |
+      | 3  | 29   | 01 March 2024    | 05 April 2024   | 75.0            | 0.0 
          | 0.0      | 0.0   | 0.0       | 0.0   | 0.0  | 0.0        | 0.0  | 
0.0         |
+      | 4  | 31   | 01 April 2024    | 05 April 2024   | 75.0            | 0.0 
          | 0.0      | 0.0   | 0.0       | 0.0   | 0.0  | 0.0        | 0.0  | 
0.0         |
+      | 5  | 0    | 01 April 2024    |                 | 50.0            | 
25.0          | 0.0      | 0.0   | 0.0       | 25.0  | 0.0  | 0.0        | 0.0  
| 25.0        |
+      | 6  | 30   | 01 May 2024      |                 | 25.0            | 
25.0          | 0.0      | 100.0 | 0.0       | 125.0 | 0.0  | 0.0        | 0.0  
| 125.0       |
+      | 7  | 31   | 01 June 2024     |                 | 0.0             | 
25.0          | 0.0      | 0.0   | 0.0       | 25.0  | 0.0  | 0.0        | 0.0  
| 25.0        |
+
+  Scenario: Verify merging re-aging transaction with N+1 installment in the 
same bucket(WEEKS)
+    When Admin sets the business date to "01 January 2024"
+    When Admin creates a client with random data
+    When Admin creates a fully customized loan with the following data:
+      | LoanProduct                                      | submitted on date | 
with Principal | ANNUAL interest rate % | interest type | interest calculation 
period | amortization type  | loanTermFrequency | loanTermFrequencyType | 
repaymentEvery | repaymentFrequencyType | numberOfRepayments | 
graceOnPrincipalPayment | graceOnInterestPayment | interest free period | 
Payment strategy            |
+      | LP2_DOWNPAYMENT_AUTO_ADVANCED_PAYMENT_ALLOCATION | 01 January 2024   | 
100            | 0                      | FLAT          | 
SAME_AS_REPAYMENT_PERIOD    | EQUAL_INSTALLMENTS | 3                 | MONTHS   
             | 1              | MONTHS                 | 3                  | 0 
                      | 0                      | 0                    | 
ADVANCED_PAYMENT_ALLOCATION |
+    And Admin successfully approves the loan on "01 January 2024" with "100" 
amount and expected disbursement date on "01 January 2024"
+    When Admin successfully disburse the loan on "01 January 2024" with "100" 
EUR transaction amount
+    When Admin sets the business date to "05 April 2024"
+    And Admin adds "LOAN_SNOOZE_FEE" due date charge with "09 April 2024" due 
date and 100 EUR transaction amount
+    When Admin creates a Loan re-aging transaction with the following data:
+      | frequencyNumber | frequencyType | startDate     | numberOfInstallments 
|
+      | 1               | WEEKS         | 01 April 2024 | 3                    
|
+    Then Loan Repayment schedule has 7 periods, with the following data for 
periods:
+      | Nr | Days | Date             | Paid date       | Balance of loan | 
Principal due | Interest | Fees  | Penalties | Due   | Paid | In advance | Late 
| Outstanding |
+      |    |      | 01 January 2024  |                 | 100.0           |     
          |          | 0.0   |           | 0.0   | 0.0  |            |      |   
          |
+      | 1  | 0    | 01 January 2024  | 01 January 2024 | 75.0            | 
25.0          | 0.0      | 0.0   | 0.0       | 25.0  | 25.0 | 0.0        | 0.0  
| 0.0         |
+      | 2  | 31   | 01 February 2024 | 05 April 2024   | 75.0            | 0.0 
          | 0.0      | 0.0   | 0.0       | 0.0   | 0.0  | 0.0        | 0.0  | 
0.0         |
+      | 3  | 29   | 01 March 2024    | 05 April 2024   | 75.0            | 0.0 
          | 0.0      | 0.0   | 0.0       | 0.0   | 0.0  | 0.0        | 0.0  | 
0.0         |
+      | 4  | 31   | 01 April 2024    | 05 April 2024   | 75.0            | 0.0 
          | 0.0      | 0.0   | 0.0       | 0.0   | 0.0  | 0.0        | 0.0  | 
0.0         |
+      | 5  | 0    | 01 April 2024    |                 | 50.0            | 
25.0          | 0.0      | 0.0   | 0.0       | 25.0  | 0.0  | 0.0        | 0.0  
| 25.0        |
+      | 6  | 7    | 08 April 2024    |                 | 25.0            | 
25.0          | 0.0      | 100.0 | 0.0       | 125.0 | 0.0  | 0.0        | 0.0  
| 125.0       |
+      | 7  | 7    | 15 April 2024    |                 | 0.0             | 
25.0          | 0.0      | 0.0   | 0.0       | 25.0  | 0.0  | 0.0        | 0.0  
| 25.0        |
+
+  Scenario: Verify merging re-aging transaction with N+1 installment in the 
same bucket(DAYS)
+    When Admin sets the business date to "01 January 2024"
+    When Admin creates a client with random data
+    When Admin creates a fully customized loan with the following data:
+      | LoanProduct                                      | submitted on date | 
with Principal | ANNUAL interest rate % | interest type | interest calculation 
period | amortization type  | loanTermFrequency | loanTermFrequencyType | 
repaymentEvery | repaymentFrequencyType | numberOfRepayments | 
graceOnPrincipalPayment | graceOnInterestPayment | interest free period | 
Payment strategy            |
+      | LP2_DOWNPAYMENT_AUTO_ADVANCED_PAYMENT_ALLOCATION | 01 January 2024   | 
100            | 0                      | FLAT          | 
SAME_AS_REPAYMENT_PERIOD    | EQUAL_INSTALLMENTS | 3                 | MONTHS   
             | 1              | MONTHS                 | 3                  | 0 
                      | 0                      | 0                    | 
ADVANCED_PAYMENT_ALLOCATION |
+    And Admin successfully approves the loan on "01 January 2024" with "100" 
amount and expected disbursement date on "01 January 2024"
+    When Admin successfully disburse the loan on "01 January 2024" with "100" 
EUR transaction amount
+    When Admin sets the business date to "05 April 2024"
+    And Admin adds "LOAN_SNOOZE_FEE" due date charge with "03 April 2024" due 
date and 100 EUR transaction amount
+    When Admin creates a Loan re-aging transaction with the following data:
+      | frequencyNumber | frequencyType | startDate     | numberOfInstallments 
|
+      | 1               | DAYS          | 01 April 2024 | 3                    
|
+    Then Loan Repayment schedule has 7 periods, with the following data for 
periods:
+      | Nr | Days | Date             | Paid date       | Balance of loan | 
Principal due | Interest | Fees  | Penalties | Due   | Paid | In advance | Late 
| Outstanding |
+      |    |      | 01 January 2024  |                 | 100.0           |     
          |          | 0.0   |           | 0.0   | 0.0  |            |      |   
          |
+      | 1  | 0    | 01 January 2024  | 01 January 2024 | 75.0            | 
25.0          | 0.0      | 0.0   | 0.0       | 25.0  | 25.0 | 0.0        | 0.0  
| 0.0         |
+      | 2  | 31   | 01 February 2024 | 05 April 2024   | 75.0            | 0.0 
          | 0.0      | 0.0   | 0.0       | 0.0   | 0.0  | 0.0        | 0.0  | 
0.0         |
+      | 3  | 29   | 01 March 2024    | 05 April 2024   | 75.0            | 0.0 
          | 0.0      | 0.0   | 0.0       | 0.0   | 0.0  | 0.0        | 0.0  | 
0.0         |
+      | 4  | 31   | 01 April 2024    | 05 April 2024   | 75.0            | 0.0 
          | 0.0      | 0.0   | 0.0       | 0.0   | 0.0  | 0.0        | 0.0  | 
0.0         |
+      | 5  | 0    | 01 April 2024    |                 | 50.0            | 
25.0          | 0.0      | 0.0   | 0.0       | 25.0  | 0.0  | 0.0        | 0.0  
| 25.0        |
+      | 6  | 1    | 02 April 2024    |                 | 25.0            | 
25.0          | 0.0      | 0.0   | 0.0       | 25.0  | 0.0  | 0.0        | 0.0  
| 25.0        |
+      | 7  | 1    | 03 April 2024    |                 | 0.0             | 
25.0          | 0.0      | 100.0 | 0.0       | 125.0 | 0.0  | 0.0        | 0.0  
| 125.0       |
+
+  Scenario: Verify re-aging transaction with N+1 installment outside bucket
+    When Admin sets the business date to "01 January 2024"
+    When Admin creates a client with random data
+    When Admin creates a fully customized loan with the following data:
+      | LoanProduct                                      | submitted on date | 
with Principal | ANNUAL interest rate % | interest type | interest calculation 
period | amortization type  | loanTermFrequency | loanTermFrequencyType | 
repaymentEvery | repaymentFrequencyType | numberOfRepayments | 
graceOnPrincipalPayment | graceOnInterestPayment | interest free period | 
Payment strategy            |
+      | LP2_DOWNPAYMENT_AUTO_ADVANCED_PAYMENT_ALLOCATION | 01 January 2024   | 
100            | 0                      | FLAT          | 
SAME_AS_REPAYMENT_PERIOD    | EQUAL_INSTALLMENTS | 3                 | MONTHS   
             | 1              | MONTHS                 | 3                  | 0 
                      | 0                      | 0                    | 
ADVANCED_PAYMENT_ALLOCATION |
+    And Admin successfully approves the loan on "01 January 2024" with "100" 
amount and expected disbursement date on "01 January 2024"
+    When Admin successfully disburse the loan on "01 January 2024" with "100" 
EUR transaction amount
+    When Admin sets the business date to "05 April 2024"
+    And Admin adds "LOAN_SNOOZE_FEE" due date charge with "03 July 2024" due 
date and 100 EUR transaction amount
+    When Admin creates a Loan re-aging transaction with the following data:
+      | frequencyNumber | frequencyType | startDate     | numberOfInstallments 
|
+      | 1               | MONTHS        | 01 April 2024 | 3                    
|
+    Then Loan Repayment schedule has 8 periods, with the following data for 
periods:
+      | Nr | Days | Date             | Paid date       | Balance of loan | 
Principal due | Interest | Fees  | Penalties | Due   | Paid | In advance | Late 
| Outstanding |
+      |    |      | 01 January 2024  |                 | 100.0           |     
          |          | 0.0   |           | 0.0   | 0.0  |            |      |   
          |
+      | 1  | 0    | 01 January 2024  | 01 January 2024 | 75.0            | 
25.0          | 0.0      | 0.0   | 0.0       | 25.0  | 25.0 | 0.0        | 0.0  
| 0.0         |
+      | 2  | 31   | 01 February 2024 | 05 April 2024   | 75.0            | 0.0 
          | 0.0      | 0.0   | 0.0       | 0.0   | 0.0  | 0.0        | 0.0  | 
0.0         |
+      | 3  | 29   | 01 March 2024    | 05 April 2024   | 75.0            | 0.0 
          | 0.0      | 0.0   | 0.0       | 0.0   | 0.0  | 0.0        | 0.0  | 
0.0         |
+      | 4  | 31   | 01 April 2024    | 05 April 2024   | 75.0            | 0.0 
          | 0.0      | 0.0   | 0.0       | 0.0   | 0.0  | 0.0        | 0.0  | 
0.0         |
+      | 5  | 0    | 01 April 2024    |                 | 50.0            | 
25.0          | 0.0      | 0.0   | 0.0       | 25.0  | 0.0  | 0.0        | 0.0  
| 25.0        |
+      | 6  | 30   | 01 May 2024      |                 | 25.0            | 
25.0          | 0.0      | 0.0   | 0.0       | 25.0  | 0.0  | 0.0        | 0.0  
| 25.0        |
+      | 7  | 31   | 01 June 2024     |                 | 0.0             | 
25.0          | 0.0      | 0.0   | 0.0       | 25.0  | 0.0  | 0.0        | 0.0  
| 25.0        |
+      | 8  | 32   | 03 July 2024     |                 | 0.0             | 0.0 
          | 0.0      | 100.0 | 0.0       | 100.0 | 0.0  | 0.0        | 0.0  | 
100.0       |
\ No newline at end of file
diff --git 
a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/AdvancedPaymentScheduleTransactionProcessor.java
 
b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/AdvancedPaymentScheduleTransactionProcessor.java
index b0d82d1184..85970a72f7 100644
--- 
a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/AdvancedPaymentScheduleTransactionProcessor.java
+++ 
b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/AdvancedPaymentScheduleTransactionProcessor.java
@@ -68,6 +68,7 @@ import 
org.apache.fineract.infrastructure.core.service.MathUtil;
 import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
 import org.apache.fineract.organisation.monetary.domain.Money;
 import org.apache.fineract.organisation.monetary.domain.MoneyHelper;
+import org.apache.fineract.portfolio.common.domain.PeriodFrequencyType;
 import org.apache.fineract.portfolio.loanaccount.data.LoanTermVariationsData;
 import org.apache.fineract.portfolio.loanaccount.data.OutstandingAmountsDTO;
 import org.apache.fineract.portfolio.loanaccount.data.TransactionChangeData;
@@ -2847,12 +2848,18 @@ public class 
AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
         MonetaryCurrency currency = ctx.getCurrency();
         List<LoanRepaymentScheduleInstallment> installments = 
ctx.getInstallments();
 
-        AtomicReference<Money> outstandingPrincipalBalance = new 
AtomicReference<>(Money.zero(currency));
+        final LocalDate startDate = 
loanTransaction.getLoanReAgeParameter().getStartDate();
+        final LocalDate endDate = 
calculateReAgedInstallmentEndDate(loanTransaction.getLoanReAgeParameter());
+        final AtomicReference<Money> outstandingPrincipalBalance = new 
AtomicReference<>(Money.zero(currency));
         installments.forEach(i -> {
-            Money principalOutstanding = i.getPrincipalOutstanding(currency);
-            if (principalOutstanding.isGreaterThanZero()) {
-                
outstandingPrincipalBalance.set(outstandingPrincipalBalance.get().add(principalOutstanding));
-                i.addToPrincipal(loanTransaction.getTransactionDate(), 
principalOutstanding.negated());
+            final boolean shouldInclude = !i.isAdditional() || 
i.getDueDate().isBefore(startDate)
+                    || (!i.getDueDate().isBefore(startDate) && 
i.getDueDate().isBefore(endDate));
+            if (shouldInclude) {
+                Money principalOutstanding = 
i.getPrincipalOutstanding(currency);
+                if (principalOutstanding.isGreaterThanZero()) {
+                    
outstandingPrincipalBalance.set(outstandingPrincipalBalance.get().add(principalOutstanding));
+                    i.addToPrincipal(loanTransaction.getTransactionDate(), 
principalOutstanding.negated());
+                }
             }
         });
 
@@ -2873,25 +2880,26 @@ public class 
AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
                     
.minus(calculatedPrincipal.multipliedBy(loanTransaction.getLoanReAgeParameter().getNumberOfInstallments()));
         }
         final LoanRepaymentScheduleInstallment lastNormalInstallment = 
installments.stream() //
-                .filter(i -> 
i.getDueDate().isBefore(loanTransaction.getTransactionDate())) //
+                .filter(i -> !i.isDownPayment() && 
i.getDueDate().isBefore(loanTransaction.getTransactionDate())) //
                 .reduce((first, second) -> second) //
                 .orElseThrow();
-        LoanRepaymentScheduleInstallment reAgedInstallment = 
LoanRepaymentScheduleInstallment.newReAgedInstallment(
-                lastNormalInstallment.getLoan(), 
lastNormalInstallment.getInstallmentNumber() + 1, 
lastNormalInstallment.getDueDate(),
-                loanTransaction.getLoanReAgeParameter().getStartDate(), 
calculatedPrincipal.getAmount());
-        installments.add(reAgedInstallment);
+
+        LoanRepaymentScheduleInstallment reAgedInstallment = 
createOrConvertReAgedInstallment(installments, startDate,
+                loanTransaction.getLoanReAgeParameter(), 
lastNormalInstallment.getLoan(), lastNormalInstallment.getInstallmentNumber() + 
1,
+                lastNormalInstallment.getDueDate(), 
calculatedPrincipal.getAmount());
         reAgedInstallment.updateObligationsMet(currency, 
loanTransaction.getTransactionDate());
 
         for (int i = 1; i < 
loanTransaction.getLoanReAgeParameter().getNumberOfInstallments(); i++) {
-            LocalDate calculatedDueDate = 
calculateReAgedInstallmentDueDate(loanTransaction.getLoanReAgeParameter(),
+            final LocalDate calculatedDueDate = 
calculateReAgedInstallmentDueDate(loanTransaction.getLoanReAgeParameter(),
                     reAgedInstallment.getDueDate());
-            reAgedInstallment = 
LoanRepaymentScheduleInstallment.newReAgedInstallment(reAgedInstallment.getLoan(),
-                    reAgedInstallment.getInstallmentNumber() + 1, 
reAgedInstallment.getDueDate(), calculatedDueDate,
+
+            reAgedInstallment = createOrConvertReAgedInstallment(installments, 
calculatedDueDate, loanTransaction.getLoanReAgeParameter(),
+                    reAgedInstallment.getLoan(), 
reAgedInstallment.getInstallmentNumber() + 1, reAgedInstallment.getDueDate(),
                     calculatedPrincipal.getAmount());
-            installments.add(reAgedInstallment);
             reAgedInstallment.updateObligationsMet(currency, 
loanTransaction.getTransactionDate());
         }
         reAgedInstallment.addToPrincipal(loanTransaction.getTransactionDate(), 
adjustCalculatedPrincipal);
+
         reprocessInstallments(installments);
     }
 
@@ -2905,21 +2913,65 @@ public class 
AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
         
installments.stream().sorted(LoanRepaymentScheduleInstallment::compareToByDueDate).forEachOrdered(i
 -> {
             i.updateInstallmentNumber(counter.getAndIncrement());
             final LocalDate prev = previousDueDate.get();
-
-            if (prev != null && i.isAdditional()) {
+            if (prev != null && (i.isAdditional() || i.isReAged())) {
                 i.updateFromDate(prev);
             }
             previousDueDate.set(i.getDueDate());
         });
     }
 
-    private LocalDate calculateReAgedInstallmentDueDate(LoanReAgeParameter 
reAgeParameter, LocalDate dueDate) {
-        return switch (reAgeParameter.getFrequencyType()) {
-            case DAYS -> dueDate.plusDays(reAgeParameter.getFrequencyNumber());
-            case WEEKS -> 
dueDate.plusWeeks(reAgeParameter.getFrequencyNumber());
-            case MONTHS -> 
dueDate.plusMonths(reAgeParameter.getFrequencyNumber());
-            case YEARS -> 
dueDate.plusYears(reAgeParameter.getFrequencyNumber());
-            default -> throw new 
UnsupportedOperationException(reAgeParameter.getFrequencyType().getCode());
+    private LoanRepaymentScheduleInstallment convertAdditionalToReAged(final 
LoanRepaymentScheduleInstallment installment,
+            final LocalDate fromDate, final LocalDate dueDate, final 
BigDecimal principalAmount) {
+        installment.setAdditional(false);
+        installment.setReAged(true);
+        installment.updateFromDate(fromDate);
+        installment.updateDueDate(dueDate);
+        installment.updatePrincipal(principalAmount);
+        return installment;
+    }
+
+    private Optional<LoanRepaymentScheduleInstallment> 
findAdditionalForFrequency(final List<LoanRepaymentScheduleInstallment> 
installments,
+            final LocalDate targetDueDate, final LoanReAgeParameter 
reAgeParameter) {
+        final LocalDate nextReAgedDate = 
calculateReAgedInstallmentDueDate(reAgeParameter, targetDueDate);
+
+        return installments.stream() //
+                .filter(LoanRepaymentScheduleInstallment::isAdditional) //
+                .filter(ai -> !ai.getDueDate().isBefore(targetDueDate) && 
ai.getDueDate().isBefore(nextReAgedDate)).findAny();
+    }
+
+    private LoanRepaymentScheduleInstallment 
createOrConvertReAgedInstallment(final List<LoanRepaymentScheduleInstallment> 
installments,
+            final LocalDate targetDueDate, final LoanReAgeParameter 
reAgeParameter, final Loan loan, final int installmentNumber,
+            final LocalDate fromDate, final BigDecimal principalAmount) {
+        final Optional<LoanRepaymentScheduleInstallment> additionalInstallment 
= findAdditionalForFrequency(installments, targetDueDate,
+                reAgeParameter);
+
+        if (additionalInstallment.isPresent()) {
+            return convertAdditionalToReAged(additionalInstallment.get(), 
fromDate, targetDueDate, principalAmount);
+        } else {
+            final LoanRepaymentScheduleInstallment reAgedInstallment = 
LoanRepaymentScheduleInstallment.newReAgedInstallment(loan,
+                    installmentNumber, fromDate, targetDueDate, 
principalAmount);
+            installments.add(reAgedInstallment);
+            return reAgedInstallment;
+        }
+    }
+
+    private LocalDate calculateReAgedInstallmentEndDate(final 
LoanReAgeParameter reAgeParameter) {
+        return calculateReAgedNextDate(reAgeParameter.getFrequencyType(), 
reAgeParameter.getStartDate(),
+                reAgeParameter.getFrequencyNumber(), 
reAgeParameter.getNumberOfInstallments());
+    }
+
+    private LocalDate calculateReAgedInstallmentDueDate(final 
LoanReAgeParameter reAgeParameter, final LocalDate dueDate) {
+        return calculateReAgedNextDate(reAgeParameter.getFrequencyType(), 
dueDate, reAgeParameter.getFrequencyNumber(), 1);
+    }
+
+    private LocalDate calculateReAgedNextDate(final PeriodFrequencyType 
frequencyType, final LocalDate dueDate,
+            final Integer frequencyNumber, final Integer numberOfInstallments) 
{
+        return switch (frequencyType) {
+            case DAYS -> dueDate.plusDays((long) frequencyNumber * 
numberOfInstallments);
+            case WEEKS -> dueDate.plusWeeks((long) frequencyNumber * 
numberOfInstallments);
+            case MONTHS -> dueDate.plusMonths((long) frequencyNumber * 
numberOfInstallments);
+            case YEARS -> dueDate.plusYears((long) frequencyNumber * 
numberOfInstallments);
+            default -> throw new UnsupportedOperationException();
         };
     }
 

Reply via email to