From 3bc44900d48c8c8bbbea142a6e500277f59480ac Mon Sep 17 00:00:00 2001
From: Janos Meszaros <janez@outlook.hu>
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 42d77916281..d489a3a3eac 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 4e0383c31af..5e270f56aec 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
@@ -318,7 +318,7 @@ private void checkAndAdjustEmiIfNeededOnRelatedRepaymentPeriods(final Progressiv
             final List<RepaymentPeriod> relatedRepaymentPeriods) {
         MathContext mc = scheduleModel.mc();
         ProgressiveLoanInterestScheduleModel newScheduleModel = null;
-        int adjustCounter = 0;
+        int adjustCounter = 1;
         EmiAdjustment emiAdjustment;
 
         do {
@@ -361,7 +361,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 c6c5589be55..c952e455b06 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<LoanScheduleModelRepaymentPeriod> 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() {
 
@@ -1291,6 +1335,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) {