FINERACT-2354: Merge re-aged installment with the next (N+1) installment if they overlap

This commit is contained in:
Oleksii Novikov
2025-09-24 14:38:59 +02:00
committed by Adam Saghy
parent 054e4aee52
commit 5df6af6a23
2 changed files with 195 additions and 23 deletions
@@ -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 |
@@ -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();
};
}