FINERACT-2354: Loan reschedule with extra terms after re-aging

This commit is contained in:
mariiaKraievska
2025-12-18 12:37:14 +01:00
committed by Adam Saghy
parent 4bfae0400c
commit 97b8bfa26f
9 changed files with 76 additions and 74 deletions
@@ -237,7 +237,7 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
for (final ChangeOperation changeOperation : changeOperations) {
if (changeOperation.isLoanTermVariationsData()) {
final LoanTermVariationsData termVariationsData = changeOperation.getLoanTermVariationsData().get();
processLoanTermVariation(installments, termVariationsData, scheduleModel);
processLoanTermVariation(termVariationsData, ctx);
} else if (changeOperation.isTransaction()) {
LoanTransaction transaction = changeOperation.getLoanTransaction().get();
if (loan.getStatus().isOverpaid() && transaction.isAccrualActivity()) {
@@ -303,12 +303,14 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
.map(TransactionChangeData::getNewTransaction).findFirst().orElse(transaction);
}
private void processLoanTermVariation(final List<LoanRepaymentScheduleInstallment> installments,
final LoanTermVariationsData termVariationsData, final ProgressiveLoanInterestScheduleModel scheduleModel) {
private void processLoanTermVariation(final LoanTermVariationsData termVariationsData, final ProgressiveTransactionCtx ctx) {
final List<LoanRepaymentScheduleInstallment> installments = ctx.getInstallments();
final ProgressiveLoanInterestScheduleModel scheduleModel = ctx.getModel();
switch (termVariationsData.getTermVariationType()) {
case INTEREST_PAUSE -> handleInterestPause(installments, termVariationsData, scheduleModel);
case INTEREST_RATE_FROM_INSTALLMENT -> handleChangeInterestRate(installments, termVariationsData, scheduleModel);
case EXTEND_REPAYMENT_PERIOD -> handleExtraRepaymentPeriod(installments, termVariationsData, scheduleModel);
case EXTEND_REPAYMENT_PERIOD ->
handleExtraRepaymentPeriod(installments, termVariationsData, scheduleModel, ctx.getAlreadyProcessedTransactions());
case DUE_DATE -> handleDueDateChangeOnRepaymentPeriod(installments, termVariationsData, scheduleModel);
default -> throw new IllegalStateException("Unhandled LoanTermVariationType.");
}
@@ -396,10 +398,12 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
}
private void handleExtraRepaymentPeriod(final List<LoanRepaymentScheduleInstallment> installments,
final LoanTermVariationsData termVariationsData, final ProgressiveLoanInterestScheduleModel scheduleModel) {
final LoanTermVariationsData termVariationsData, final ProgressiveLoanInterestScheduleModel scheduleModel,
final List<LoanTransaction> alreadyProcessedTransactions) {
final LocalDate interestRateChangeSubmittedOnDate = termVariationsData.getTermVariationApplicableFrom();
final int repaymentPeriodsToAdd = termVariationsData.getDecimalValue().intValue();
emiCalculator.addRepaymentPeriods(scheduleModel, interestRateChangeSubmittedOnDate, repaymentPeriodsToAdd);
emiCalculator.addRepaymentPeriods(scheduleModel, interestRateChangeSubmittedOnDate, repaymentPeriodsToAdd,
alreadyProcessedTransactions);
final Loan loan = installments.getFirst().getLoan();
int nextInstallmentNumber = installments.stream().mapToInt(LoanRepaymentScheduleInstallment::getInstallmentNumber).max().orElse(0)
@@ -419,6 +423,7 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
}
}
processInterestRateChangeOnInstallments(scheduleModel, interestRateChangeSubmittedOnDate, installments);
reprocessInstallments(installments);
}
private void handleChangeInterestRate(final List<LoanRepaymentScheduleInstallment> installments,
@@ -3219,7 +3224,7 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
}).map(LoanTermVariations::toData).collect(Collectors.toCollection(ArrayList::new));
for (LoanTermVariationsData termVariationsData : loanTermVariationsToReprocess) {
processLoanTermVariation(installments, termVariationsData, ctx.getModel());
processLoanTermVariation(termVariationsData, ctx);
}
}
}
@@ -3273,7 +3278,7 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
installments.stream().sorted(LoanRepaymentScheduleInstallment::compareToByFromDueDate).forEachOrdered(i -> {
i.updateInstallmentNumber(counter.getAndIncrement());
final LocalDate prev = previousDueDate.get();
if (prev != null && (i.isAdditional() || i.isReAged())) {
if (prev != null && !i.getFromDate().isEqual(prev) && !i.isDownPayment()) {
i.updateFromDate(prev);
}
previousDueDate.set(i.getDueDate());
@@ -27,6 +27,7 @@ import java.util.Optional;
import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
import org.apache.fineract.organisation.monetary.domain.Money;
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
import org.apache.fineract.portfolio.loanaccount.domain.reaging.LoanReAgeParameter;
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanApplicationTerms;
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleModelRepaymentPeriod;
@@ -81,7 +82,7 @@ public interface EMICalculator {
BigDecimal newInterestRate);
void addRepaymentPeriods(ProgressiveLoanInterestScheduleModel scheduleModel, LocalDate submittedOnDate,
int numberOfRepaymentPeriodsToAdd);
int numberOfRepaymentPeriodsToAdd, List<LoanTransaction> alreadyProcessedTransactions);
/**
* This method applies outstanding balance correction on the interest model. Negative amount decreases the
@@ -46,6 +46,7 @@ import org.apache.fineract.portfolio.common.domain.DaysInYearCustomStrategyType;
import org.apache.fineract.portfolio.common.domain.DaysInYearType;
import org.apache.fineract.portfolio.common.domain.PeriodFrequencyType;
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
import org.apache.fineract.portfolio.loanaccount.domain.reaging.LoanReAgeInterestHandlingType;
import org.apache.fineract.portfolio.loanaccount.domain.reaging.LoanReAgeParameter;
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanApplicationTerms;
@@ -185,18 +186,24 @@ public final class ProgressiveEMICalculator implements EMICalculator {
@Override
public void addRepaymentPeriods(final ProgressiveLoanInterestScheduleModel scheduleModel, final LocalDate submittedOnDate,
final int numberOfRepaymentPeriodsToAdd) {
final int numberOfRepaymentPeriodsToAdd, final List<LoanTransaction> alreadyProcessedTransactions) {
addRepaymentPeriods(scheduleModel,
EmiChangeOperation.addRepaymentPeriods(submittedOnDate, scheduleModel.zero(), numberOfRepaymentPeriodsToAdd));
EmiChangeOperation.addRepaymentPeriods(submittedOnDate, scheduleModel.zero(), numberOfRepaymentPeriodsToAdd),
alreadyProcessedTransactions);
}
public void addRepaymentPeriods(final ProgressiveLoanInterestScheduleModel scheduleModel, final EmiChangeOperation operation) {
LocalDate disbursementDate = scheduleModel.getStartDate();
int repaymentPeriodCount = scheduleModel.repaymentPeriods().size();
private void addRepaymentPeriods(final ProgressiveLoanInterestScheduleModel scheduleModel, final EmiChangeOperation operation,
final List<LoanTransaction> alreadyProcessedTransactions) {
final Optional<LoanReAgeParameter> reAgeTransactionParameter = alreadyProcessedTransactions.stream()
.filter(t -> t.isReAge() && !t.isReversed()).findFirst().map(LoanTransaction::getLoanReAgeParameter);
final LocalDate seedDate = reAgeTransactionParameter.map(LoanReAgeParameter::getStartDate).orElse(scheduleModel.getStartDate());
final int repaymentPeriodCount = reAgeTransactionParameter.map(param -> param.getNumberOfInstallments() - 1)
.orElse(scheduleModel.repaymentPeriods().size());
final LocalDate interestRateChangeEffectiveDate = operation.getSubmittedOnDate().minusDays(1);
List<LocalDateInterval> periods2 = generateAdditionalRepaymentPeriodDueDates(scheduleModel, operation.getPeriodsToAdd(),
repaymentPeriodCount, scheduleModel.resolveRepaymentPEriodLengthGeneratorFunction(disbursementDate));
final List<LocalDateInterval> periods2 = generateAdditionalRepaymentPeriodDueDates(scheduleModel, operation.getPeriodsToAdd(),
repaymentPeriodCount, scheduleModel.resolveRepaymentPeriodLengthGeneratorFunction(seedDate));
updateModel(scheduleModel, periods2, LocalDateInterval::startDate, LocalDateInterval::endDate);
scheduleModel
@@ -420,7 +420,7 @@ public class ProgressiveLoanInterestScheduleModel {
return this.modifiers.get(COPY);
}
public Function<Long, LocalDate> resolveRepaymentPEriodLengthGeneratorFunction(LocalDate instance) {
public Function<Long, LocalDate> resolveRepaymentPeriodLengthGeneratorFunction(final LocalDate instance) {
return switch (loanProductRelatedDetail.getRepaymentPeriodFrequencyType()) {
case MONTHS -> instance::plusMonths;
case WEEKS -> instance::plusWeeks;
@@ -429,7 +429,4 @@ public class ProgressiveLoanInterestScheduleModel {
};
}
public boolean isInterestRecalculationIsAllowed() {
return modifiers.get(INTEREST_RECALCULATION_ENABLED);
}
}
@@ -4049,14 +4049,14 @@ class ProgressiveEMICalculatorTest {
checkPeriod(interestSchedule, 2, 340.03, 3.37, 336.66, 0.00, false); //
// add extra repayment period 1
emiCalculator.addRepaymentPeriods(interestSchedule, LocalDate.of(2024, 7, 2), 1);
emiCalculator.addRepaymentPeriods(interestSchedule, LocalDate.of(2024, 7, 2), 1, List.of());
checkPeriod(interestSchedule, 0, 340.02, 10.00, 330.02, 669.98, false); //
checkPeriod(interestSchedule, 1, 227.81, 6.70, 221.11, 448.87, false); //
checkPeriod(interestSchedule, 2, 227.81, 4.49, 223.32, 225.55, false); //
checkPeriod(interestSchedule, 3, 227.81, 2.26, 225.55, 0.00, false); //
emiCalculator.addRepaymentPeriods(interestSchedule, LocalDate.of(2024, 8, 2), 2);
emiCalculator.addRepaymentPeriods(interestSchedule, LocalDate.of(2024, 8, 2), 2, List.of());
checkPeriod(interestSchedule, 0, 340.02, 10.00, 330.02, 669.98, false); //
checkPeriod(interestSchedule, 1, 227.81, 6.70, 221.11, 448.87, false); //
@@ -4088,14 +4088,14 @@ class ProgressiveEMICalculatorTest {
checkPeriod(interestSchedule, 2, 343.34, 10.00, 333.34, 0.00, false); //
// add extra repayment period 1
emiCalculator.addRepaymentPeriods(interestSchedule, LocalDate.of(2024, 7, 2), 1);
emiCalculator.addRepaymentPeriods(interestSchedule, LocalDate.of(2024, 7, 2), 1, List.of());
checkPeriod(interestSchedule, 0, 343.33, 10.00, 333.33, 666.67, false); //
checkPeriod(interestSchedule, 1, 232.22, 10.00, 222.22, 444.45, false); //
checkPeriod(interestSchedule, 2, 232.22, 10.00, 222.22, 222.23, false); //
checkPeriod(interestSchedule, 3, 232.23, 10.00, 222.23, 0.00, false); //
emiCalculator.addRepaymentPeriods(interestSchedule, LocalDate.of(2024, 8, 2), 2);
emiCalculator.addRepaymentPeriods(interestSchedule, LocalDate.of(2024, 8, 2), 2, List.of());
checkPeriod(interestSchedule, 0, 343.33, 10.00, 333.33, 666.67, false); //
checkPeriod(interestSchedule, 1, 232.22, 10.00, 222.22, 444.45, false); //
@@ -4140,7 +4140,7 @@ class ProgressiveEMICalculatorTest {
checkPeriod(interestSchedule, 3, 256.29, 0.00, 256.29, 0.00, true); //
// add extra repayment period 1
emiCalculator.addRepaymentPeriods(interestSchedule, LocalDate.of(2024, 6, 15), 1);
emiCalculator.addRepaymentPeriods(interestSchedule, LocalDate.of(2024, 6, 15), 1, List.of());
// verify 1st and 4th installments are not modified, additional principal paid are substract from last
// period
@@ -4150,7 +4150,7 @@ class ProgressiveEMICalculatorTest {
checkPeriod(interestSchedule, 3, 256.29, 0.00, 256.29, 88.00, true); //
checkPeriod(interestSchedule, 4, 89.76, 1.76, 88.00, 0.00, false); //
emiCalculator.addRepaymentPeriods(interestSchedule, LocalDate.of(2024, 6, 15), 2);
emiCalculator.addRepaymentPeriods(interestSchedule, LocalDate.of(2024, 6, 15), 2, List.of());
// verify 1st and 4th installments are not modified, additional principal paid are substract from last
// periods
@@ -4210,7 +4210,7 @@ class ProgressiveEMICalculatorTest {
checkPeriod(interestSchedule, 3, 260.00, 10.00, 250.00, 0.00, true); //
// add extra repayment period 1
emiCalculator.addRepaymentPeriods(interestSchedule, LocalDate.of(2024, 6, 15), 1);
emiCalculator.addRepaymentPeriods(interestSchedule, LocalDate.of(2024, 6, 15), 1, List.of());
// verify 1st and 4th installments are not modified, additional principal paid are substract from last
// period
@@ -4221,7 +4221,7 @@ class ProgressiveEMICalculatorTest {
checkPeriod(interestSchedule, 4, 110.00, 10.00, 100.00, 0.00, false); //
// add extra repayment period 2
emiCalculator.addRepaymentPeriods(interestSchedule, LocalDate.of(2024, 6, 15), 2);
emiCalculator.addRepaymentPeriods(interestSchedule, LocalDate.of(2024, 6, 15), 2, List.of());
// verify 1st and 4th installments are not modified, additional principal paid are substract from last
// periods