From 36bd4c399fb068ff4d6cd2340c20a6ae030e029c Mon Sep 17 00:00:00 2001 From: Adam Saghy Date: Tue, 29 Oct 2024 14:45:39 +0100 Subject: [PATCH] FINERACT-1981: revise last installment payment strategy --- .../ProgressiveLoanInterestScheduleModel.java | 9 +++++++++ .../loanschedule/data/RepaymentPeriod.java | 5 +++++ .../calc/ProgressiveEMICalculator.java | 20 ++++++++++++++++++- 3 files changed, 33 insertions(+), 1 deletion(-) diff --git a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/data/ProgressiveLoanInterestScheduleModel.java b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/data/ProgressiveLoanInterestScheduleModel.java index 70b0ca0a83e..66bf7febbb6 100644 --- a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/data/ProgressiveLoanInterestScheduleModel.java +++ b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/data/ProgressiveLoanInterestScheduleModel.java @@ -30,6 +30,7 @@ import java.util.function.Consumer; import lombok.Data; import lombok.experimental.Accessors; +import org.apache.fineract.infrastructure.core.service.MathUtil; import org.apache.fineract.organisation.monetary.domain.Money; import org.apache.fineract.portfolio.loanproduct.domain.LoanProductRelatedDetail; @@ -196,4 +197,12 @@ void insertInterestPeriod(final RepaymentPeriod repaymentPeriod, final LocalDate public Money getZero() { return Money.zero(loanProductRelatedDetail.getCurrency(), mc); } + + public Money getTotalOutstandingBalance() { + Money totalDisbursedAmount = repaymentPeriods.stream() + .flatMap(rp -> rp.getInterestPeriods().stream().map(InterestPeriod::getDisbursementAmount)).reduce(getZero(), Money::plus); + Money totalPrincipalPaidAmount = repaymentPeriods.stream().map(RepaymentPeriod::getPaidPrincipal).reduce(getZero(), Money::plus); + + return MathUtil.negativeToZero(totalDisbursedAmount.minus(totalPrincipalPaidAmount, mc)); + } } diff --git a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/data/RepaymentPeriod.java b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/data/RepaymentPeriod.java index 2a393522704..94d36a5f5ee 100644 --- a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/data/RepaymentPeriod.java +++ b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/data/RepaymentPeriod.java @@ -46,6 +46,9 @@ public class RepaymentPeriod { @Setter @Getter private Money emi; + @Setter + @Getter + private Money originalEmi; @Getter private Money paidPrincipal; @Getter @@ -62,6 +65,7 @@ public RepaymentPeriod(RepaymentPeriod previous, LocalDate fromDate, LocalDate d this.fromDate = fromDate; this.dueDate = dueDate; this.emi = emi; + this.originalEmi = emi; this.mc = mc; this.interestPeriods = new ArrayList<>(); // There is always at least 1 interest period, by default with same from-due date as repayment period @@ -76,6 +80,7 @@ public RepaymentPeriod(RepaymentPeriod previous, RepaymentPeriod repaymentPeriod this.fromDate = repaymentPeriod.fromDate; this.dueDate = repaymentPeriod.dueDate; this.emi = repaymentPeriod.emi; + this.originalEmi = repaymentPeriod.originalEmi; this.interestPeriods = new ArrayList<>(); this.paidPrincipal = repaymentPeriod.paidPrincipal; this.paidInterest = repaymentPeriod.paidInterest; diff --git a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/calc/ProgressiveEMICalculator.java b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/calc/ProgressiveEMICalculator.java index 97b1d3bcc1b..a8974a2da77 100644 --- a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/calc/ProgressiveEMICalculator.java +++ b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/calc/ProgressiveEMICalculator.java @@ -136,13 +136,23 @@ public void payInterest(ProgressiveLoanInterestScheduleModel scheduleModel, Loca @Override public void payPrincipal(ProgressiveLoanInterestScheduleModel scheduleModel, LocalDate repaymentPeriodDueDate, LocalDate transactionDate, Money principalAmount) { - findRepaymentPeriod(scheduleModel, repaymentPeriodDueDate).ifPresent(rp -> rp.addPaidPrincipalAmount(principalAmount)); + Optional repaymentPeriod = findRepaymentPeriod(scheduleModel, repaymentPeriodDueDate); + repaymentPeriod.ifPresent(rp -> rp.addPaidPrincipalAmount(principalAmount)); LocalDate balanceCorrectionDate = transactionDate; if (repaymentPeriodDueDate.isBefore(transactionDate)) { // If it is paid late, we need to calculate with the period due date balanceCorrectionDate = repaymentPeriodDueDate; } addBalanceCorrection(scheduleModel, balanceCorrectionDate, principalAmount.negated()); + + repaymentPeriod.ifPresent(rp -> { + // If any period total paid > calculated EMI, then set EMI to total paid -> effectively it is marked as + // fully paid + if (rp.getTotalPaidAmount().compareTo(rp.getEmi()) > 0) { + rp.setEmi(rp.getTotalPaidAmount()); + calculateLastUnpaidRepaymentPeriodEMI(scheduleModel); + } + }); } @Override @@ -173,6 +183,11 @@ public PayableDetails getPayableDetails(final ProgressiveLoanInterestScheduleMod calculateOutstandingBalance(scheduleModelCopy); calculateLastUnpaidRepaymentPeriodEMI(scheduleModelCopy); + boolean multiplePeriodIsUnpaid = scheduleModelCopy.repaymentPeriods().stream().filter(rp -> !rp.isFullyPaid()).count() > 1L; + if (multiplePeriodIsUnpaid && !targetDate.isAfter(repaymentPeriod.getFromDate())) { + repaymentPeriod.setEmi(repaymentPeriod.getOriginalEmi()); + } + return new PayableDetails(repaymentPeriod.getEmi(), repaymentPeriod.getDuePrincipal(), repaymentPeriod.getDueInterest(), interestPeriod.getOutstandingLoanBalance().add(interestPeriod.getDisbursementAmount(), mc)); } @@ -245,6 +260,7 @@ private void checkAndAdjustEmiIfNeededOnRelatedRepaymentPeriods(final Progressiv if (!period.getDueDate().isBefore(relatedPeriodsFirstDueDate) && !adjustedEqualMonthlyInstallmentValue.isLessThan(period.getTotalPaidAmount())) { period.setEmi(adjustedEqualMonthlyInstallmentValue); + period.setOriginalEmi(adjustedEqualMonthlyInstallmentValue); } }); calculateOutstandingBalance(newScheduleModel); @@ -265,6 +281,7 @@ private void checkAndAdjustEmiIfNeededOnRelatedRepaymentPeriods(final Progressiv } final RepaymentPeriod newRepaymentPeriod = relatedPeriodFromNewModelIterator.next(); relatedRepaymentPeriod.setEmi(newRepaymentPeriod.getEmi()); + relatedRepaymentPeriod.setOriginalEmi(newRepaymentPeriod.getEmi()); }); calculateOutstandingBalance(scheduleModel); } @@ -401,6 +418,7 @@ void calculateEMIOnPeriods(final List repaymentPeriods, final P repaymentPeriods.forEach(period -> { if (!finalEqualMonthlyInstallment.isLessThan(period.getTotalPaidAmount())) { period.setEmi(finalEqualMonthlyInstallment); + period.setOriginalEmi(finalEqualMonthlyInstallment); } }); }