FINERACT-2290: Handling accelerate maturity date charge-off in case LAST_INSTALLMENT strategy

This commit is contained in:
mariiaKraievska
2025-02-13 13:20:39 +01:00
committed by Adam Saghy
parent e2157501c0
commit cb0ad23f37
10 changed files with 431 additions and 13 deletions
@@ -88,6 +88,7 @@ import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.Mon
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.TransactionCtx;
import org.apache.fineract.portfolio.loanaccount.loanschedule.data.PeriodDueDetails;
import org.apache.fineract.portfolio.loanaccount.loanschedule.data.ProgressiveLoanInterestScheduleModel;
import org.apache.fineract.portfolio.loanaccount.loanschedule.data.RepaymentPeriod;
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleProcessingType;
import org.apache.fineract.portfolio.loanaccount.service.InterestRefundService;
import org.apache.fineract.portfolio.loanproduct.calc.EMICalculator;
@@ -1264,6 +1265,12 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
.filter(installment -> transactionDate.isAfter(installment.getFromDate()))
.collect(Collectors.toCollection(ArrayList::new));
final List<LoanTransaction> transactionsToBeReprocessed = installments.stream()
.filter(installment -> transactionDate.isBefore(installment.getFromDate())
&& !installment.getLoanTransactionToRepaymentScheduleMappings().isEmpty())
.flatMap(installment -> installment.getLoanTransactionToRepaymentScheduleMappings().stream())
.map(LoanTransactionToRepaymentScheduleMapping::getLoanTransaction).toList();
if (futureFee.compareTo(BigDecimal.ZERO) > 0 || futurePenalty.compareTo(BigDecimal.ZERO) > 0) {
final Optional<LocalDate> latestDueDate = loan.getCharges().stream()
.filter(loanCharge -> loanCharge.isActive() && loanCharge.isNotFullyPaid()).map(LoanCharge::getDueDate)
@@ -1282,6 +1289,19 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
loan.updateLoanSchedule(installmentsUpToTransactionDate);
loan.updateLoanScheduleDependentDerivedFields();
if (transactionCtx instanceof ProgressiveTransactionCtx progressiveTransactionCtx && loan.isInterestRecalculationEnabled()) {
updateRepaymentPeriodsAfterChargeOff(progressiveTransactionCtx, transactionDate, transactionsToBeReprocessed);
} else {
for (LoanTransaction processTransaction : transactionsToBeReprocessed) {
final LoanTransaction newTransaction = LoanTransaction.copyTransactionProperties(processTransaction);
processLatestTransaction(newTransaction, transactionCtx);
createNewTransaction(processTransaction, newTransaction, transactionCtx);
newTransaction.updateLoan(loan);
loan.getLoanTransactions().add(newTransaction);
}
loan.updateLoanSummaryDerivedFields();
}
}
}
@@ -2049,4 +2069,46 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
public static LoanPaymentAllocationRule getDefaultAllocationRule(Loan loan) {
return loan.getPaymentAllocationRules().stream().filter(e -> e.getTransactionType().isDefault()).findFirst().orElseThrow();
}
private void updateRepaymentPeriodsAfterChargeOff(final ProgressiveTransactionCtx transactionCtx, final LocalDate chargeOffDate,
final List<LoanTransaction> transactionsToBeReprocessed) {
final List<RepaymentPeriod> repaymentPeriods = transactionCtx.getModel().repaymentPeriods();
if (repaymentPeriods.isEmpty()) {
return;
}
final List<RepaymentPeriod> periodsBeforeChargeOff = repaymentPeriods.stream()
.filter(rp -> rp.getFromDate().isBefore(chargeOffDate)).toList();
if (periodsBeforeChargeOff.isEmpty()) {
return;
}
final RepaymentPeriod lastPeriod = periodsBeforeChargeOff.get(periodsBeforeChargeOff.size() - 1);
final List<RepaymentPeriod> periodsToRemove = repaymentPeriods.stream().filter(rp -> rp.getFromDate().isAfter(chargeOffDate))
.toList();
lastPeriod.setDueDate(chargeOffDate);
lastPeriod.getInterestPeriods().removeIf(interestPeriod -> !interestPeriod.getFromDate().isBefore(chargeOffDate));
transactionCtx.getModel().repaymentPeriods().removeAll(periodsToRemove);
final BigDecimal totalPrincipal = periodsToRemove.stream().map(rp -> rp.getDuePrincipal().getAmount()).reduce(BigDecimal.ZERO,
BigDecimal::add);
final BigDecimal newInterest = emiCalculator
.getPeriodInterestTillDate(transactionCtx.getModel(), lastPeriod.getDueDate(), chargeOffDate).getAmount();
lastPeriod.setEmi(lastPeriod.getDuePrincipal().add(totalPrincipal).add(newInterest));
emiCalculator.calculateRateFactorForRepaymentPeriod(lastPeriod, transactionCtx.getModel());
for (LoanTransaction processTransaction : transactionsToBeReprocessed) {
emiCalculator.addBalanceCorrection(transactionCtx.getModel(), processTransaction.getTransactionDate(),
processTransaction.getPrincipalPortion(transactionCtx.getCurrency()));
processSingleTransaction(processTransaction, transactionCtx);
}
}
}
@@ -60,7 +60,7 @@ public class ProgressiveLoanInterestScheduleModel {
public ProgressiveLoanInterestScheduleModel(final List<RepaymentPeriod> repaymentPeriods,
final LoanProductMinimumRepaymentScheduleRelatedDetail loanProductRelatedDetail,
final List<LoanTermVariationsData> loanTermVariations, final Integer installmentAmountInMultiplesOf, final MathContext mc) {
this.repaymentPeriods = repaymentPeriods;
this.repaymentPeriods = new ArrayList<>(repaymentPeriods);
this.interestRates = new TreeSet<>(Collections.reverseOrder());
this.loanProductRelatedDetail = loanProductRelatedDetail;
this.loanTermVariations = buildLoanTermVariationMap(loanTermVariations);
@@ -42,8 +42,9 @@ public class RepaymentPeriod {
private final RepaymentPeriod previous;
@Getter
private final LocalDate fromDate;
@Setter
@Getter
private final LocalDate dueDate;
private LocalDate dueDate;
@Getter
@Setter
private List<InterestPeriod> interestPeriods;
@@ -76,6 +76,8 @@ public interface EMICalculator {
OutstandingDetails getOutstandingAmountsTillDate(ProgressiveLoanInterestScheduleModel model, LocalDate targetDate);
void calculateRateFactorForRepaymentPeriod(RepaymentPeriod repaymentPeriod, ProgressiveLoanInterestScheduleModel scheduleModel);
Money getSumOfDueInterestsOnDate(ProgressiveLoanInterestScheduleModel scheduleModel, LocalDate subjectDate);
void applyInterestPause(ProgressiveLoanInterestScheduleModel scheduleModel, LocalDate fromDate, LocalDate endDate);
@@ -261,6 +261,17 @@ public final class ProgressiveEMICalculator implements EMICalculator {
return new OutstandingDetails(totalOutstandingPrincipal, totalOutstandingInterest);
}
@Override
public void calculateRateFactorForRepaymentPeriod(final RepaymentPeriod repaymentPeriod,
final ProgressiveLoanInterestScheduleModel scheduleModel) {
repaymentPeriod.getInterestPeriods().forEach(interestPeriod -> {
interestPeriod.setRateFactor(calculateRateFactorPerPeriod(scheduleModel, repaymentPeriod, interestPeriod.getFromDate(),
interestPeriod.getDueDate()));
interestPeriod.setRateFactorTillPeriodDueDate(calculateRateFactorPerPeriodForInterest(scheduleModel, repaymentPeriod,
interestPeriod.getFromDate(), repaymentPeriod.getDueDate()));
});
}
@NotNull
private ProgressiveLoanInterestScheduleModel recalculateScheduleModelTillDate(
@NotNull ProgressiveLoanInterestScheduleModel scheduleModel, @NotNull LocalDate periodDueDate, @NotNull LocalDate targetDate) {
@@ -419,16 +430,6 @@ public final class ProgressiveEMICalculator implements EMICalculator {
repaymentPeriods.forEach(repaymentPeriod -> calculateRateFactorForRepaymentPeriod(repaymentPeriod, scheduleModel));
}
private void calculateRateFactorForRepaymentPeriod(final RepaymentPeriod repaymentPeriod,
final ProgressiveLoanInterestScheduleModel scheduleModel) {
repaymentPeriod.getInterestPeriods().forEach(interestPeriod -> {
interestPeriod.setRateFactor(calculateRateFactorPerPeriod(scheduleModel, repaymentPeriod, interestPeriod.getFromDate(),
interestPeriod.getDueDate()));
interestPeriod.setRateFactorTillPeriodDueDate(calculateRateFactorPerPeriodForInterest(scheduleModel, repaymentPeriod,
interestPeriod.getFromDate(), repaymentPeriod.getDueDate()));
});
}
BigDecimal calculateRateFactorPerPeriodForInterest(final ProgressiveLoanInterestScheduleModel scheduleModel,
final RepaymentPeriod repaymentPeriod, final LocalDate interestPeriodFromDate, final LocalDate interestPeriodDueDate) {
final MathContext mc = scheduleModel.mc();