From a1d51e1e34adac36f0bd245eb35057a90ec3410f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Soma=20S=C3=B6r=C3=B6s?= Date: Wed, 16 Apr 2025 10:12:22 +0200 Subject: [PATCH] FINERACT-2265: Progressive Loan - Interest Recalculation fails for - RecalculationRestFrequencyType SameAsRepaymentPeriod --- ...edPaymentScheduleTransactionProcessor.java | 41 ++++++---------- .../LoanCOBCreateAccrualsTest.java | 4 +- .../LoanInterestRecalculationCOBTest.java | 47 +++++++++++++++++++ 3 files changed, 63 insertions(+), 29 deletions(-) diff --git a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/AdvancedPaymentScheduleTransactionProcessor.java b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/AdvancedPaymentScheduleTransactionProcessor.java index 603141a0a..e3a5ae453 100644 --- a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/AdvancedPaymentScheduleTransactionProcessor.java +++ b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/AdvancedPaymentScheduleTransactionProcessor.java @@ -1220,44 +1220,31 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep LocalDate toDate = currentInstallment.getDueDate(); boolean hasUpdate = false; - if (currentInstallment.getLoan().getLoanInterestRecalculationDetails().getRestFrequencyType().isSameAsRepayment()) { + if (!currentDate.equals(ctx.getLastOverdueBalanceChange())) { // if we have same date for fromDate & last overdue balance change then it means we have the up-to-date // model. - if (ctx.getLastOverdueBalanceChange() == null || fromDate.isAfter(ctx.getLastOverdueBalanceChange())) { - emiCalculator.addBalanceCorrection(ctx.getModel(), fromDate, overduePrincipal); - ctx.setLastOverdueBalanceChange(fromDate); - hasUpdate = true; - - if (currentDate.isAfter(fromDate) && !currentDate.isAfter(toDate)) { - emiCalculator.addBalanceCorrection(ctx.getModel(), currentInstallment.getDueDate(), - aggregatedOverDuePrincipal.negated()); - ctx.setLastOverdueBalanceChange(currentInstallment.getDueDate()); - } - } - } - - if (currentInstallment.getLoan().getLoanInterestRecalculationDetails().getRestFrequencyType().isDaily() - // if we have same date for currentDate & last overdue balance change then it meas we have the - // up-to-date model. - && !currentDate.equals(ctx.getLastOverdueBalanceChange())) { if (ctx.getLastOverdueBalanceChange() == null || currentInstallment.getFromDate().isAfter(ctx.getLastOverdueBalanceChange())) { // first overdue hit for installment. setting overdue balance correction from instalment from date. emiCalculator.addBalanceCorrection(ctx.getModel(), fromDate, overduePrincipal); } else { // not the first balance correction on installment period, then setting overdue balance correction from - // last balance change's current date. previous interest period already has the correct balanec - // correction + // last balance change's current date. previous interest period already has the correct balance + // correction. emiCalculator.addBalanceCorrection(ctx.getModel(), ctx.getLastOverdueBalanceChange(), overduePrincipal); } - // setting negative correction for the period from current date, expecting the overdue balance's full - // repayment on that day. - // TODO: we might need to do it outside of this method only for the current date at the end - if (currentDate.isAfter(currentInstallment.getFromDate()) && !currentDate.isAfter(currentInstallment.getDueDate())) { - emiCalculator.addBalanceCorrection(ctx.getModel(), currentDate, aggregatedOverDuePrincipal.negated()); - ctx.setLastOverdueBalanceChange(currentDate); - } hasUpdate = true; + + if (currentDate.isAfter(fromDate) && !currentDate.isAfter(toDate)) { + LocalDate lastOverdueBalanceChange; + if (currentInstallment.getLoan().getLoanInterestRecalculationDetails().getRestFrequencyType().isSameAsRepayment()) { + lastOverdueBalanceChange = toDate; + } else { + lastOverdueBalanceChange = currentDate; + } + emiCalculator.addBalanceCorrection(ctx.getModel(), lastOverdueBalanceChange, aggregatedOverDuePrincipal.negated()); + ctx.setLastOverdueBalanceChange(lastOverdueBalanceChange); + } } if (hasUpdate) { diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanCOBCreateAccrualsTest.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanCOBCreateAccrualsTest.java index 7db24009b..93f89b0a0 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanCOBCreateAccrualsTest.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanCOBCreateAccrualsTest.java @@ -675,8 +675,8 @@ public class LoanCOBCreateAccrualsTest extends BaseLoanIntegrationTest { validateTransactionsExist(loanDetails, // transaction(0.30, "Accrual", "19 February 2025", 0.0, 0.0, 0.30, 0.0, 0.0, 0.0, 0.0), // transaction(0.30, "Accrual", "20 February 2025", 0.0, 0.0, 0.30, 0.0, 0.0, 0.0, 0.0), // - transaction(0.23, "Accrual", "21 February 2025", 0.0, 0.0, 0.23, 0.0, 0.0, 0.0, 0.0), // - transaction(0.22, "Accrual", "22 February 2025", 0.0, 0.0, 0.22, 0.0, 0.0, 0.0, 0.0)); // + transaction(0.33, "Accrual", "21 February 2025", 0.0, 0.0, 0.33, 0.0, 0.0, 0.0, 0.0), // + transaction(0.34, "Accrual", "22 February 2025", 0.0, 0.0, 0.34, 0.0, 0.0, 0.0, 0.0)); // }); } diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanInterestRecalculationCOBTest.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanInterestRecalculationCOBTest.java index c78c51027..343e5063b 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanInterestRecalculationCOBTest.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanInterestRecalculationCOBTest.java @@ -93,6 +93,53 @@ public class LoanInterestRecalculationCOBTest extends BaseLoanIntegrationTest { period.getPrincipalDue(), period.getFeeChargesDue(), period.getPenaltyChargesDue(), period.getInterestDue())); } + @Test + public void testInterestRecalculationInCaseOfTinyAmountOfRepaymentsEveryRepaymentPeriodForProgressiveLoanSameAsRepaymentPeriod() { + AtomicReference loanIdRef = new AtomicReference<>(); + runAt("1 January 2023", () -> { + PostLoanProductsResponse loanProduct = loanProductHelper.createLoanProduct(create4IProgressive() // + .recalculationRestFrequencyType(RecalculationRestFrequencyType.SAME_AS_REPAYMENT_PERIOD) // + );// + Long loanId = applyAndApproveProgressiveLoan(client.getClientId(), loanProduct.getResourceId(), "1 January 2023", 10000.0, + 86.42, 6, null); + loanIdRef.set(loanId); + disburseLoan(loanId, BigDecimal.valueOf(8000), "1 January 2023"); + }); + runAt("1 February 2023", () -> { + Long loanId = loanIdRef.get(); + loanTransactionHelper.makeLoanRepayment(loanId, "Repayment", "1 February 2023", 0.01); + }); + runAt("1 March 2023", () -> { + Long loanId = loanIdRef.get(); + loanTransactionHelper.makeLoanRepayment(loanId, "Repayment", "1 March 2023", 0.01); + }); + runAt("1 April 2023", () -> { + Long loanId = loanIdRef.get(); + loanTransactionHelper.makeLoanRepayment(loanId, "Repayment", "1 April 2023", 0.01); + }); + runAt("1 May 2023", () -> { + Long loanId = loanIdRef.get(); + loanTransactionHelper.makeLoanRepayment(loanId, "Repayment", "1 May 2023", 0.01); + }); + runAt("1 June 2023", () -> { + Long loanId = loanIdRef.get(); + loanTransactionHelper.makeLoanRepayment(loanId, "Repayment", "1 June 2023", 0.01); + }); + runAt("17 June 2023", () -> { + Long loanId = loanIdRef.get(); + inlineLoanCOBHelper.executeInlineCOB(List.of(loanId)); + verifyRepaymentSchedule(loanId, // + installment(8000.0, null, "01 January 2023"), // + installment(1112.7, 576.13, 1688.78, false, "01 February 2023"), // + installment(1112.7, 576.13, 1688.83, false, "01 March 2023"), // + installment(1112.7, 576.13, 1688.83, false, "01 April 2023"), // + installment(1112.7, 576.13, 1688.83, false, "01 May 2023"), // + installment(1112.7, 576.13, 1688.83, false, "01 June 2023"), // + installment(2436.5, 576.13, 3012.63, false, "01 July 2023") // + ); + }); + } + @Test public void verifyLoanInstallmentRecalculatedIfThereIsOverdueInstallmentOn4IProgressiveLoanCOBStepDaily() { AtomicReference loanIdRef = new AtomicReference<>();