You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@fineract.apache.org by ad...@apache.org on 2025/08/05 11:49:47 UTC

(fineract) 01/02: FINERACT-1968: Progressive Loan schedule handling - interest handling (DECLINING BALANCE interest, Same as repayment period, Equal installments)

This is an automated email from the ASF dual-hosted git repository.

adamsaghy pushed a commit to branch develop
in repository https://gitbox.apache.org/repos/asf/fineract.git

commit dfab66132baac1a013789a2cc3bda7d360153a2f
Author: Soma Sörös <so...@dpc.hu>
AuthorDate: Mon Jul 7 14:33:42 2025 +0200

    FINERACT-1968: Progressive Loan schedule handling - interest handling (DECLINING BALANCE interest, Same as repayment period, Equal installments)
---
 .../calc/ProgressiveEMICalculatorTest.java         | 186 +++++++++++++++++++++
 1 file changed, 186 insertions(+)

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 ae11225e3e..35a4ca6e20 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
@@ -104,6 +104,7 @@ class ProgressiveEMICalculatorTest {
     public void setupTestDefaults() {
         Mockito.when(loanProductRelatedDetail.isInterestRecognitionOnDisbursementDate()).thenReturn(false);
         Mockito.when(loanProductRelatedDetail.getDaysInYearCustomStrategy()).thenReturn(null);
+        Mockito.when(loanProductRelatedDetail.getCurrencyData()).thenReturn(currency);
         Mockito.when(loanProductRelatedDetail.getInterestMethod()).thenReturn(InterestMethod.DECLINING_BALANCE);
         Mockito.when(loanProductRelatedDetail.getInterestCalculationPeriodMethod()).thenReturn(InterestCalculationPeriodMethod.DAILY);
         Mockito.when(loanProductRelatedDetail.isAllowPartialPeriodInterestCalculation()).thenReturn(true);
@@ -3767,6 +3768,191 @@ class ProgressiveEMICalculatorTest {
         }
     }
 
+    @Nested
+    public class DecliningBalanceSameAsRepaymentPeriod {
+
+        @BeforeEach
+        void beforeEach() {
+            Mockito.when(loanProductRelatedDetail.getInterestMethod()).thenReturn(InterestMethod.DECLINING_BALANCE);
+            Mockito.when(loanProductRelatedDetail.getInterestCalculationPeriodMethod())
+                    .thenReturn(InterestCalculationPeriodMethod.SAME_AS_REPAYMENT_PERIOD);
+            Mockito.when(loanProductRelatedDetail.getDaysInYearType()).thenReturn(DaysInYearType.ACTUAL.getValue());
+            Mockito.when(loanProductRelatedDetail.getDaysInMonthType()).thenReturn(DaysInMonthType.ACTUAL.getValue());
+        }
+
+        @Test
+        void test_3_of_1_month_period_multi_disbursement__exact_days() {
+
+            final BigDecimal interestRate = BigDecimal.valueOf(12.0);
+            final LocalDate disbursementDate = LocalDate.of(2024, 6, 1);
+
+            Mockito.when(loanProductRelatedDetail.getAnnualNominalInterestRate()).thenReturn(interestRate);
+            Mockito.when(loanProductRelatedDetail.getRepaymentPeriodFrequencyType()).thenReturn(PeriodFrequencyType.MONTHS);
+            Mockito.when(loanProductRelatedDetail.getNumberOfRepayments()).thenReturn(3);
+            Mockito.when(loanProductRelatedDetail.getRepayEvery()).thenReturn(1);
+
+            Mockito.when(loanProductRelatedDetail.isAllowPartialPeriodInterestCalculation()).thenReturn(true);
+
+            final List<LoanScheduleModelRepaymentPeriod> expectedRepaymentPeriods = generateExpectedRepaymentPeriods(disbursementDate);
+            final Integer installmentAmountInMultiplesOf = null;
+
+            final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generatePeriodInterestScheduleModel(
+                    expectedRepaymentPeriods, loanProductRelatedDetail, List.of(), installmentAmountInMultiplesOf, mc);
+
+            emiCalculator.addDisbursement(interestSchedule, disbursementDate, toMoney(500.0));
+            checkPeriod(interestSchedule, 0, 170.01, 5.00, 165.01, 334.99, false); //
+            checkPeriod(interestSchedule, 1, 170.01, 3.35, 166.66, 168.33, false); //
+            checkPeriod(interestSchedule, 2, 170.01, 1.68, 168.33, 0.00, false); //
+
+            emiCalculator.addDisbursement(interestSchedule, disbursementDate, toMoney(500.0));
+
+            checkPeriod(interestSchedule, 0, 340.02, 10.00, 330.02, 669.98, false); //
+            checkPeriod(interestSchedule, 1, 340.02, 6.70, 333.32, 336.66, false); //
+            checkPeriod(interestSchedule, 2, 340.03, 3.37, 336.66, 0.00, false); //
+
+            final LocalDate disbursementDate2 = LocalDate.of(2024, 7, 15);
+            emiCalculator.addDisbursement(interestSchedule, disbursementDate2, toMoney(250.0));
+            checkPeriod(interestSchedule, 0, 340.02, 10.00, 330.02, 669.98, false); //
+            checkPeriod(interestSchedule, 1, 466.33, 8.07, 458.26, 461.72, false); //
+            checkPeriod(interestSchedule, 2, 466.34, 4.62, 461.72, 0.00, false); //
+        }
+
+        @Test
+        void test_3_of_1_month_period_multi_disbursement__exact_days__on_period_due_date() {
+
+            final BigDecimal interestRate = BigDecimal.valueOf(12.0);
+            final LocalDate disbursementDate = LocalDate.of(2024, 6, 1);
+
+            Mockito.when(loanProductRelatedDetail.getAnnualNominalInterestRate()).thenReturn(interestRate);
+            Mockito.when(loanProductRelatedDetail.getRepaymentPeriodFrequencyType()).thenReturn(PeriodFrequencyType.MONTHS);
+            Mockito.when(loanProductRelatedDetail.getNumberOfRepayments()).thenReturn(3);
+            Mockito.when(loanProductRelatedDetail.getRepayEvery()).thenReturn(1);
+
+            Mockito.when(loanProductRelatedDetail.isAllowPartialPeriodInterestCalculation()).thenReturn(true);
+
+            final List<LoanScheduleModelRepaymentPeriod> expectedRepaymentPeriods = generateExpectedRepaymentPeriods(disbursementDate);
+            final Integer installmentAmountInMultiplesOf = null;
+
+            final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generatePeriodInterestScheduleModel(
+                    expectedRepaymentPeriods, loanProductRelatedDetail, List.of(), installmentAmountInMultiplesOf, mc);
+
+            emiCalculator.addDisbursement(interestSchedule, disbursementDate, toMoney(500.0));
+            checkPeriod(interestSchedule, 0, 170.01, 5.00, 165.01, 334.99, false); //
+            checkPeriod(interestSchedule, 1, 170.01, 3.35, 166.66, 168.33, false); //
+            checkPeriod(interestSchedule, 2, 170.01, 1.68, 168.33, 0.00, false); //
+
+            emiCalculator.addDisbursement(interestSchedule, disbursementDate, toMoney(500.0));
+            checkPeriod(interestSchedule, 0, 340.02, 10.00, 330.02, 669.98, false); //
+            checkPeriod(interestSchedule, 1, 340.02, 6.70, 333.32, 336.66, false); //
+            checkPeriod(interestSchedule, 2, 340.03, 3.37, 336.66, 0.00, false); //
+
+            final LocalDate disbursementDate2 = LocalDate.of(2024, 7, 1);
+            emiCalculator.addDisbursement(interestSchedule, disbursementDate2, toMoney(250.0));
+            checkPeriod(interestSchedule, 0, 340.02, 10.00, 330.02, 919.98, false); //
+            checkPeriod(interestSchedule, 1, 466.90, 9.20, 457.70, 462.28, false); //
+            checkPeriod(interestSchedule, 2, 466.90, 4.62, 462.28, 0.00, false); //
+        }
+
+        @Test
+        void test_3_of_1_month_period_multi_disbursement__NOT_exact_days__different_days() {
+
+            final BigDecimal interestRate = BigDecimal.valueOf(12.0);
+            final LocalDate disbursementDate = LocalDate.of(2024, 6, 1);
+
+            Mockito.when(loanProductRelatedDetail.getAnnualNominalInterestRate()).thenReturn(interestRate);
+            Mockito.when(loanProductRelatedDetail.getRepaymentPeriodFrequencyType()).thenReturn(PeriodFrequencyType.MONTHS);
+            Mockito.when(loanProductRelatedDetail.getNumberOfRepayments()).thenReturn(3);
+            Mockito.when(loanProductRelatedDetail.getRepayEvery()).thenReturn(1);
+
+            Mockito.when(loanProductRelatedDetail.isAllowPartialPeriodInterestCalculation()).thenReturn(false);
+
+            final List<LoanScheduleModelRepaymentPeriod> expectedRepaymentPeriods = generateExpectedRepaymentPeriods(disbursementDate);
+            final Integer installmentAmountInMultiplesOf = null;
+
+            final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generatePeriodInterestScheduleModel(
+                    expectedRepaymentPeriods, loanProductRelatedDetail, List.of(), installmentAmountInMultiplesOf, mc);
+
+            emiCalculator.addDisbursement(interestSchedule, disbursementDate, toMoney(500.0));
+            checkPeriod(interestSchedule, 0, 170.01, 5.00, 165.01, 334.99, false); //
+            checkPeriod(interestSchedule, 1, 170.01, 3.35, 166.66, 168.33, false); //
+            checkPeriod(interestSchedule, 2, 170.01, 1.68, 168.33, 0.00, false); //
+
+            emiCalculator.addDisbursement(interestSchedule, disbursementDate, toMoney(500.0));
+            checkPeriod(interestSchedule, 0, 340.02, 10.00, 330.02, 669.98, false); //
+            checkPeriod(interestSchedule, 1, 340.02, 6.70, 333.32, 336.66, false); //
+            checkPeriod(interestSchedule, 2, 340.03, 3.37, 336.66, 0.00, false); //
+
+            final LocalDate disbursementDate2 = LocalDate.of(2024, 7, 1);
+            final LocalDate disbursementDate3 = LocalDate.of(2024, 7, 15);
+            final LocalDate disbursementDate4 = LocalDate.of(2024, 7, 23);
+            emiCalculator.addDisbursement(interestSchedule, disbursementDate2, toMoney(50.0));
+            checkPeriod(interestSchedule, 0, 340.02, 10.00, 330.02, 719.98, false); //
+            checkPeriod(interestSchedule, 1, 365.40, 7.20, 358.20, 361.78, false); //
+            checkPeriod(interestSchedule, 2, 365.40, 3.62, 361.78, 0.00, false); //
+
+            emiCalculator.addDisbursement(interestSchedule, disbursementDate3, toMoney(50.0));
+            checkPeriod(interestSchedule, 0, 340.02, 10.00, 330.02, 769.98, false); //
+            checkPeriod(interestSchedule, 1, 390.77, 7.70, 383.07, 386.91, false); //
+            checkPeriod(interestSchedule, 2, 390.78, 3.87, 386.91, 0.00, false); //
+
+            emiCalculator.addDisbursement(interestSchedule, disbursementDate3, toMoney(50.0));
+            checkPeriod(interestSchedule, 0, 340.02, 10.00, 330.02, 819.98, false); //
+            checkPeriod(interestSchedule, 1, 416.15, 8.20, 407.95, 412.03, false); //
+            checkPeriod(interestSchedule, 2, 416.15, 4.12, 412.03, 0.00, false); //
+
+            emiCalculator.addDisbursement(interestSchedule, disbursementDate4, toMoney(50.0));
+            checkPeriod(interestSchedule, 0, 340.02, 10.00, 330.02, 869.98, false); //
+            checkPeriod(interestSchedule, 1, 441.53, 8.70, 432.83, 437.15, false); //
+            checkPeriod(interestSchedule, 2, 441.52, 4.37, 437.15, 0.00, false); //
+
+            emiCalculator.addDisbursement(interestSchedule, disbursementDate4, toMoney(50.0));
+            checkPeriod(interestSchedule, 0, 340.02, 10.00, 330.02, 919.98, false); //
+            checkPeriod(interestSchedule, 1, 466.90, 9.20, 457.70, 462.28, false); //
+            checkPeriod(interestSchedule, 2, 466.90, 4.62, 462.28, 0.00, false); //
+        }
+
+        @Test
+        void test_3_of_1_month_period_multi_disbursement__NOT_exact_days() {
+
+            final BigDecimal interestRate = BigDecimal.valueOf(12.0);
+            final LocalDate disbursementDate = LocalDate.of(2024, 6, 1);
+
+            Mockito.when(loanProductRelatedDetail.getAnnualNominalInterestRate()).thenReturn(interestRate);
+            Mockito.when(loanProductRelatedDetail.getRepaymentPeriodFrequencyType()).thenReturn(PeriodFrequencyType.MONTHS);
+            Mockito.when(loanProductRelatedDetail.getNumberOfRepayments()).thenReturn(3);
+            Mockito.when(loanProductRelatedDetail.getRepayEvery()).thenReturn(1);
+
+            Mockito.when(loanProductRelatedDetail.isAllowPartialPeriodInterestCalculation()).thenReturn(false);
+
+            final List<LoanScheduleModelRepaymentPeriod> expectedRepaymentPeriods = generateExpectedRepaymentPeriods(disbursementDate);
+            final Integer installmentAmountInMultiplesOf = null;
+
+            final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generatePeriodInterestScheduleModel(
+                    expectedRepaymentPeriods, loanProductRelatedDetail, List.of(), installmentAmountInMultiplesOf, mc);
+
+            emiCalculator.addDisbursement(interestSchedule, disbursementDate, toMoney(500.0));
+            checkPeriod(interestSchedule, 0, 170.01, 5.00, 165.01, 334.99, false); //
+            checkPeriod(interestSchedule, 1, 170.01, 3.35, 166.66, 168.33, false); //
+            checkPeriod(interestSchedule, 2, 170.01, 1.68, 168.33, 0.00, false); //
+
+            emiCalculator.addDisbursement(interestSchedule, disbursementDate, toMoney(500.0));
+            checkPeriod(interestSchedule, 0, 340.02, 10.00, 330.02, 669.98, false); //
+            checkPeriod(interestSchedule, 1, 340.02, 6.70, 333.32, 336.66, false); //
+            checkPeriod(interestSchedule, 2, 340.03, 3.37, 336.66, 0.00, false); //
+
+            final LocalDate disbursementDate2 = LocalDate.of(2024, 7, 15);
+            emiCalculator.addDisbursement(interestSchedule, disbursementDate2, toMoney(130.0));
+            checkPeriod(interestSchedule, 0, 340.02, 10.00, 330.02, 799.98, false); //
+            checkPeriod(interestSchedule, 1, 406.00, 8.00, 398.00, 401.98, false); //
+            checkPeriod(interestSchedule, 2, 406.00, 4.02, 401.98, 0.00, false); //
+
+            emiCalculator.addDisbursement(interestSchedule, disbursementDate2, toMoney(120.0));
+            checkPeriod(interestSchedule, 0, 340.02, 10.00, 330.02, 919.98, false); //
+            checkPeriod(interestSchedule, 1, 466.90, 9.20, 457.70, 462.28, false); //
+            checkPeriod(interestSchedule, 2, 466.90, 4.62, 462.28, 0.00, false); //
+        }
+    }
+
     // utilities
     private List<LoanScheduleModelRepaymentPeriod> generateExpectedRepaymentPeriods(LocalDate disbursementDate) {
         return switch (loanProductRelatedDetail.getRepaymentPeriodFrequencyType()) {