diff --git a/fineract-e2e-tests-runner/src/test/resources/features/Loan.feature b/fineract-e2e-tests-runner/src/test/resources/features/Loan.feature index 7b8fc14e2be..08dad09d14f 100644 --- a/fineract-e2e-tests-runner/src/test/resources/features/Loan.feature +++ b/fineract-e2e-tests-runner/src/test/resources/features/Loan.feature @@ -5755,3 +5755,87 @@ Feature: Loan | Term Type Id | Term Type Code | Term Type Value | Applicable From | Decimal Value | Date Value | Is Specific To Installment | Is Processed | | 1 | loanTermType.emiAmount | emiAmount | 01 January 2023 | 50.0 | | false | | | 4 | loanTermType.dueDate | dueDate | 01 February 2023 | 50.0 | 15 January 2023 | false | | + + Scenario: EMI calculation with 360/30 Early repayment - Last installment strategy - UC1: Multiple early repayment + 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_ADV_PYMNT_INTEREST_DAILY_EMI_360_30_IR_DAILY_TILL_PRECLOSE_LAST_INSTALLMENT_STRATEGY | 01 January 2024 | 100 | 7.0 | DECLINING_BALANCE | DAILY | EQUAL_INSTALLMENTS | 6 | MONTHS | 1 | MONTHS | 6 | 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 + Then Loan Repayment schedule has 6 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 | 31 | 01 February 2024 | | 83.57 | 16.43 | 0.58 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 2 | 29 | 01 March 2024 | | 67.05 | 16.52 | 0.49 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 3 | 31 | 01 April 2024 | | 50.43 | 16.62 | 0.39 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 4 | 30 | 01 May 2024 | | 33.71 | 16.72 | 0.29 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 5 | 31 | 01 June 2024 | | 16.9 | 16.81 | 0.2 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 6 | 30 | 01 July 2024 | | 0.0 | 16.9 | 0.1 | 0.0 | 0.0 | 17.0 | 0.0 | 0.0 | 0.0 | 17.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 100.0 | 2.05 | 0.0 | 0.0 | 102.05 | 0.0 | 0.0 | 0.0 | 102.05 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | + | 01 January 2024 | Disbursement | 100.0 | 0.0 | 0.0 | 0.0 | 0.0 | 100.0 | +# --- Early repayment on 15 January 2024 --- + When Admin sets the business date to "15 January 2024" + And Customer makes "AUTOPAY" repayment on "15 January 2024" with 15.00 EUR transaction amount + Then Loan Repayment schedule has 6 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 | 31 | 01 February 2024 | | 83.52 | 16.48 | 0.53 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 2 | 29 | 01 March 2024 | | 66.91 | 16.61 | 0.4 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 3 | 31 | 01 April 2024 | | 50.2 | 16.71 | 0.3 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 4 | 30 | 01 May 2024 | | 33.4 | 16.8 | 0.21 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 5 | 31 | 01 June 2024 | | 16.5 | 16.9 | 0.11 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 6 | 30 | 01 July 2024 | | 0.0 | 16.5 | 0.01 | 0.0 | 0.0 | 16.51 | 15.0 | 15.0 | 0.0 | 1.51 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 100.0 | 1.56 | 0.0 | 0.0 | 101.56 | 15.0 | 15.0 | 0.0 | 86.56 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | + | 01 January 2024 | Disbursement | 100.0 | 0.0 | 0.0 | 0.0 | 0.0 | 100.0 | + | 15 January 2024 | Repayment | 15.0 | 15.0 | 0.0 | 0.0 | 0.0 | 85.0 | + # --- Early repayment on 15 January 2024 --- + And Customer makes "AUTOPAY" repayment on "15 January 2024" with 1.50 EUR transaction amount + Then Loan Repayment schedule has 6 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 | 31 | 01 February 2024 | | 83.52 | 16.48 | 0.53 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 2 | 29 | 01 March 2024 | | 66.9 | 16.62 | 0.39 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 3 | 31 | 01 April 2024 | | 50.18 | 16.72 | 0.29 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 4 | 30 | 01 May 2024 | | 33.37 | 16.81 | 0.2 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 5 | 31 | 01 June 2024 | | 16.5 | 16.87 | 0.1 | 0.0 | 0.0 | 16.97 | 0.0 | 0.0 | 0.0 | 16.97 | + | 6 | 30 | 01 July 2024 | 15 January 2024 | 0.0 | 16.5 | 0.0 | 0.0 | 0.0 | 16.5 | 16.5 | 16.5 | 0.0 | 0.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 100.0 | 1.51 | 0.0 | 0.0 | 101.51 | 16.5 | 16.5 | 0.0 | 85.01 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | + | 01 January 2024 | Disbursement | 100.0 | 0.0 | 0.0 | 0.0 | 0.0 | 100.0 | + | 15 January 2024 | Repayment | 15.0 | 15.0 | 0.0 | 0.0 | 0.0 | 85.0 | + | 15 January 2024 | Repayment | 1.5 | 1.5 | 0.0 | 0.0 | 0.0 | 83.5 | + # --- Pay-off on 15 January 2024 --- + And Customer makes "AUTOPAY" repayment on "15 January 2024" with 83.76 EUR transaction amount + Then Loan Repayment schedule has 6 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 | 31 | 01 February 2024 | 15 January 2024 | 84.54 | 15.46 | 0.26 | 0.0 | 0.0 | 15.72 | 15.72 | 15.72 | 0.0 | 0.0 | + | 2 | 29 | 01 March 2024 | 15 January 2024 | 67.53 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | 17.01 | 17.01 | 0.0 | 0.0 | + | 3 | 31 | 01 April 2024 | 15 January 2024 | 50.52 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | 17.01 | 17.01 | 0.0 | 0.0 | + | 4 | 30 | 01 May 2024 | 15 January 2024 | 33.51 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | 17.01 | 17.01 | 0.0 | 0.0 | + | 5 | 31 | 01 June 2024 | 15 January 2024 | 16.5 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | 17.01 | 17.01 | 0.0 | 0.0 | + | 6 | 30 | 01 July 2024 | 15 January 2024 | 0.0 | 16.5 | 0.0 | 0.0 | 0.0 | 16.5 | 16.5 | 16.5 | 0.0 | 0.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 100.0 | 0.26 | 0.0 | 0.0 | 100.26 | 100.26 | 100.26 | 0.0 | 0.0 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | + | 01 January 2024 | Disbursement | 100.0 | 0.0 | 0.0 | 0.0 | 0.0 | 100.0 | + | 15 January 2024 | Repayment | 15.0 | 15.0 | 0.0 | 0.0 | 0.0 | 85.0 | + | 15 January 2024 | Repayment | 1.5 | 1.5 | 0.0 | 0.0 | 0.0 | 83.5 | + | 15 January 2024 | Repayment | 83.76 | 83.5 | 0.26 | 0.0 | 0.0 | 0.0 | + | 15 January 2024 | Accrual | 0.26 | 0.0 | 0.26 | 0.0 | 0.0 | 0.0 | + Then Loan status will be "CLOSED_OBLIGATIONS_MET" \ No newline at end of file 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); } }); }