From d704852228c098544bc12fbeda7f573a6b483852 Mon Sep 17 00:00:00 2001 From: Janos Meszaros Date: Tue, 14 Jan 2025 19:13:10 +0100 Subject: [PATCH] FINERACT-1981: Enhance EMI Adjustment --- .../resources/features/EMICalculation.feature | 8 +-- .../calc/ProgressiveEMICalculator.java | 4 +- .../calc/ProgressiveEMICalculatorTest.java | 49 +++++++++++++++++++ 3 files changed, 55 insertions(+), 6 deletions(-) diff --git a/fineract-e2e-tests-runner/src/test/resources/features/EMICalculation.feature b/fineract-e2e-tests-runner/src/test/resources/features/EMICalculation.feature index 42d7791628..d489a3a3ea 100644 --- a/fineract-e2e-tests-runner/src/test/resources/features/EMICalculation.feature +++ b/fineract-e2e-tests-runner/src/test/resources/features/EMICalculation.feature @@ -4115,10 +4115,10 @@ Feature: EMI calculation and repayment schedule checks for interest bearing loan | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | | | | 01 January 2024 | | 250.0 | | | 0.0 | | 0.0 | 0.0 | | | | | | | 04 January 2024 | | 750.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 31 | 01 February 2024 | 22 January 2024 | 750.09 | 249.91 | 5.07 | 0.0 | 0.0 | 254.98 | 254.98 | 254.98 | 0.0 | 0.0 | - | 2 | 29 | 01 March 2024 | 22 January 2024 | 495.11 | 254.98 | 0.0 | 0.0 | 0.0 | 254.98 | 254.98 | 254.98 | 0.0 | 0.0 | - | 3 | 31 | 01 April 2024 | 22 January 2024 | 240.13 | 254.98 | 0.0 | 0.0 | 0.0 | 254.98 | 254.98 | 254.98 | 0.0 | 0.0 | - | 4 | 30 | 01 May 2024 | 22 January 2024 | 0.0 | 240.13 | 0.0 | 0.0 | 0.0 | 240.13 | 240.13 | 240.13 | 0.0 | 0.0 | + | 1 | 31 | 01 February 2024 | 22 January 2024 | 750.08 | 249.92 | 5.07 | 0.0 | 0.0 | 254.99 | 254.99 | 254.99 | 0.0 | 0.0 | + | 2 | 29 | 01 March 2024 | 22 January 2024 | 495.09 | 254.99 | 0.0 | 0.0 | 0.0 | 254.99 | 254.99 | 254.99 | 0.0 | 0.0 | + | 3 | 31 | 01 April 2024 | 22 January 2024 | 240.1 | 254.99 | 0.0 | 0.0 | 0.0 | 254.99 | 254.99 | 254.99 | 0.0 | 0.0 | + | 4 | 30 | 01 May 2024 | 22 January 2024 | 0.0 | 240.1 | 0.0 | 0.0 | 0.0 | 240.1 | 240.1 | 240.1 | 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 | | 1000.0 | 5.07 | 0.0 | 0.0 | 1005.07 | 1005.07 | 1005.07 | 0.0 | 0.0 | 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 b5adfcc5e7..6e4e2ce834 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 @@ -324,7 +324,7 @@ private void checkAndAdjustEmiIfNeededOnRelatedRepaymentPeriods(final Progressiv final List relatedRepaymentPeriods) { MathContext mc = scheduleModel.mc(); ProgressiveLoanInterestScheduleModel newScheduleModel = null; - int adjustCounter = 0; + int adjustCounter = 1; EmiAdjustment emiAdjustment; do { @@ -367,7 +367,7 @@ private void checkAndAdjustEmiIfNeededOnRelatedRepaymentPeriods(final Progressiv }); calculateOutstandingBalance(scheduleModel); adjustCounter++; - } while (emiAdjustment.hasUncountablePeriods() && adjustCounter < 3); + } while (adjustCounter <= 3); } /** diff --git a/fineract-progressive-loan/src/test/java/org/apache/fineract/portfolio/loanproduct/calc/ProgressiveEMICalculatorTest.java b/fineract-progressive-loan/src/test/java/org/apache/fineract/portfolio/loanproduct/calc/ProgressiveEMICalculatorTest.java index 2f33c5efff..04b6e43ce3 100644 --- a/fineract-progressive-loan/src/test/java/org/apache/fineract/portfolio/loanproduct/calc/ProgressiveEMICalculatorTest.java +++ b/fineract-progressive-loan/src/test/java/org/apache/fineract/portfolio/loanproduct/calc/ProgressiveEMICalculatorTest.java @@ -999,6 +999,50 @@ public void test_disbursedAmt100_dayInYearsActual_daysInMonthActual_repayEvery1M checkPeriod(interestSchedule, 5, 0, 17.13, 0.008031371585, 0.14, 16.99, 0.0); } + @Test + public void test_multidisbursement_total_repay1st_dayInYears360_daysInMonth30_repayEvery1Month() { + + final List expectedRepaymentPeriods = List.of( + repayment(1, LocalDate.of(2024, 1, 1), LocalDate.of(2024, 2, 1)), + repayment(2, LocalDate.of(2024, 2, 1), LocalDate.of(2024, 3, 1)), + repayment(3, LocalDate.of(2024, 3, 1), LocalDate.of(2024, 4, 1)), + repayment(4, LocalDate.of(2024, 4, 1), LocalDate.of(2024, 5, 1)), + repayment(5, LocalDate.of(2024, 5, 1), LocalDate.of(2024, 6, 1)), + repayment(6, LocalDate.of(2024, 6, 1), LocalDate.of(2024, 7, 1))); + + final BigDecimal interestRate = BigDecimal.valueOf(7.0); + final Integer installmentAmountInMultiplesOf = null; + + Mockito.when(loanProductRelatedDetail.getAnnualNominalInterestRate()).thenReturn(interestRate); + Mockito.when(loanProductRelatedDetail.getDaysInYearType()).thenReturn(DaysInYearType.DAYS_360.getValue()); + Mockito.when(loanProductRelatedDetail.getDaysInMonthType()).thenReturn(DaysInMonthType.DAYS_30.getValue()); + Mockito.when(loanProductRelatedDetail.getRepaymentPeriodFrequencyType()).thenReturn(PeriodFrequencyType.MONTHS); + Mockito.when(loanProductRelatedDetail.getRepayEvery()).thenReturn(1); + Mockito.when(loanProductRelatedDetail.getCurrencyData()).thenReturn(currency); + + final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generatePeriodInterestScheduleModel( + expectedRepaymentPeriods, loanProductRelatedDetail, installmentAmountInMultiplesOf, mc); + + emiCalculator.addDisbursement(interestSchedule, LocalDate.of(2024, 1, 1), toMoney(300.0)); + Assertions.assertEquals(6.15, toDouble(interestSchedule.getTotalDueInterest())); + + // pay back the whole loan on first day + emiCalculator.payPrincipal(interestSchedule, LocalDate.of(2024, 2, 1), LocalDate.of(2024, 1, 1), toMoney(51.03)); + emiCalculator.payPrincipal(interestSchedule, LocalDate.of(2024, 3, 1), LocalDate.of(2024, 1, 1), toMoney(51.03)); + emiCalculator.payPrincipal(interestSchedule, LocalDate.of(2024, 4, 1), LocalDate.of(2024, 1, 1), toMoney(51.03)); + emiCalculator.payPrincipal(interestSchedule, LocalDate.of(2024, 5, 1), LocalDate.of(2024, 1, 1), toMoney(51.03)); + emiCalculator.payPrincipal(interestSchedule, LocalDate.of(2024, 6, 1), LocalDate.of(2024, 1, 1), toMoney(51.03)); + emiCalculator.payPrincipal(interestSchedule, LocalDate.of(2024, 7, 1), LocalDate.of(2024, 1, 1), toMoney(44.85)); + + Assertions.assertEquals(0.0, toDouble(interestSchedule.getTotalDueInterest())); + + emiCalculator.addDisbursement(interestSchedule, LocalDate.of(2024, 1, 1), toMoney(200.0)); + Assertions.assertEquals(4.20, toDouble(interestSchedule.getTotalDueInterest())); + + checkEmi(interestSchedule, 4, 84.03); + checkEmi(interestSchedule, 5, 84.05); + } + @Test public void test_disbursedAmt1000_NoInterest_repayEvery1Month() { @@ -1349,6 +1393,11 @@ private static void checkDailyInterest(final ProgressiveLoanInterestScheduleMode Assertions.assertEquals(interest, toDouble(currentInterest)); } + private static void checkEmi(final ProgressiveLoanInterestScheduleModel interestScheduleModel, final int repaymentIdx, + final double emiValue) { + Assertions.assertEquals(emiValue, toDouble(interestScheduleModel.repaymentPeriods().get(repaymentIdx).getEmi())); + } + private static void checkPeriod(final ProgressiveLoanInterestScheduleModel interestScheduleModel, final int repaymentIdx, final int interestIdx, final double emiValue, final double rateFactor, final double interestDue, final double principalDue, final double remaingBalance) {