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..b8311550cce 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,7 +136,11 @@ 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)); + findRepaymentPeriod(scheduleModel, repaymentPeriodDueDate).ifPresent(rp -> { + rp.addPaidPrincipalAmount(principalAmount); + // If more was paid then calculated EMI, go with the original EMI instead + rp.setEmi(rp.getPaidPrincipal().isGreaterThan(rp.getEmi()) ? rp.getOriginalEmi() : rp.getEmi()); + }); LocalDate balanceCorrectionDate = transactionDate; if (repaymentPeriodDueDate.isBefore(transactionDate)) { // If it is paid late, we need to calculate with the period due date @@ -173,6 +177,12 @@ public PayableDetails getPayableDetails(final ProgressiveLoanInterestScheduleMod calculateOutstandingBalance(scheduleModelCopy); calculateLastUnpaidRepaymentPeriodEMI(scheduleModelCopy); + boolean multiplePeriodIsUnpaid = scheduleModelCopy.repaymentPeriods().stream().filter(rp -> !rp.isFullyPaid()).count() > 1L; + // If there is multiple unpaid installment, we can pay the original EMI, if applicable + if (multiplePeriodIsUnpaid) { + repaymentPeriod.setEmi(repaymentPeriod.getOriginalEmi()); + } + return new PayableDetails(repaymentPeriod.getEmi(), repaymentPeriod.getDuePrincipal(), repaymentPeriod.getDueInterest(), interestPeriod.getOutstandingLoanBalance().add(interestPeriod.getDisbursementAmount(), mc)); } @@ -208,7 +218,11 @@ private void calculateLastUnpaidRepaymentPeriodEMI(ProgressiveLoanInterestSchedu .reduce(Money.zero(scheduleModel.loanProductRelatedDetail().getCurrency(), mc), (m1, m2) -> m1.plus(m2, mc)); // 100 Money diff = totalDisbursedAmount.plus(totalDueInterest, mc).minus(totalEMI, mc); - Optional findLastUnpaidRepaymentPeriod = scheduleModel.repaymentPeriods().stream().filter(rp -> !rp.isFullyPaid()) + // TODO: move to the right place + // Find last period, which is not fully paid or it was paid more principal then calculated emi -> get due + // principal + diff + Optional findLastUnpaidRepaymentPeriod = scheduleModel.repaymentPeriods().stream() + .filter(rp -> !rp.isFullyPaid() && rp.getEmi().plus(diff).isGreaterThan(rp.getPaidPrincipal())) .reduce((first, second) -> second); findLastUnpaidRepaymentPeriod.ifPresent(repaymentPeriod -> repaymentPeriod.setEmi(repaymentPeriod.getEmi().add(diff, mc))); } @@ -245,6 +259,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 +280,7 @@ private void checkAndAdjustEmiIfNeededOnRelatedRepaymentPeriods(final Progressiv } final RepaymentPeriod newRepaymentPeriod = relatedPeriodFromNewModelIterator.next(); relatedRepaymentPeriod.setEmi(newRepaymentPeriod.getEmi()); + relatedRepaymentPeriod.setOriginalEmi(newRepaymentPeriod.getEmi()); }); calculateOutstandingBalance(scheduleModel); } @@ -401,6 +417,7 @@ void calculateEMIOnPeriods(final List repaymentPeriods, final P repaymentPeriods.forEach(period -> { if (!finalEqualMonthlyInstallment.isLessThan(period.getTotalPaidAmount())) { period.setEmi(finalEqualMonthlyInstallment); + period.setOriginalEmi(finalEqualMonthlyInstallment); } }); }