diff --git a/fineract-provider/src/main/java/org/apache/fineract/cob/loan/LoanInterestRecalculationCOBBusinessStep.java b/fineract-provider/src/main/java/org/apache/fineract/cob/loan/LoanInterestRecalculationCOBBusinessStep.java index 099e7cc2e..188098635 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/cob/loan/LoanInterestRecalculationCOBBusinessStep.java +++ b/fineract-provider/src/main/java/org/apache/fineract/cob/loan/LoanInterestRecalculationCOBBusinessStep.java @@ -21,6 +21,7 @@ package org.apache.fineract.cob.loan; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.apache.fineract.infrastructure.core.domain.ActionContext; +import org.apache.fineract.infrastructure.core.service.DateUtils; import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil; import org.apache.fineract.portfolio.loanaccount.domain.Loan; import org.apache.fineract.portfolio.loanaccount.service.LoanWritePlatformService; @@ -35,22 +36,29 @@ public class LoanInterestRecalculationCOBBusinessStep implements LoanCOBBusiness @Override public Loan execute(Loan loan) { - if (!loan.getStatus().isActive() || loan.isNpa() || loan.isChargedOff() || !loan.isInterestBearingAndInterestRecalculationEnabled() - || loan.getLoanInterestRecalculationDetails().disallowInterestCalculationOnPastDue()) { - log.debug( - "Skip processing loan interest recalculation [{}] - Possible reasons: Loan is not an interest bearing loan, Loan is not active, Interest recalculation on past due is disabled on this loan", - loan.getId()); + try { + ThreadLocalContextUtil.setActionContext(ActionContext.DEFAULT); + if (!loan.getStatus().isActive() || loan.isNpa() || loan.isChargedOff() + || !loan.isInterestBearingAndInterestRecalculationEnabled() + || loan.getLoanInterestRecalculationDetails().disallowInterestCalculationOnPastDue() || !hasOverdueInstallment(loan)) { + log.debug( + "Skip processing loan interest recalculation [{}] - Possible reasons: Loan is not an interest bearing loan, Loan is not active, Interest recalculation on past due is disabled on this loan", + loan.getId()); + return loan; + } + + log.debug("Start processing loan interest recalculation [{}]", loan.getId()); + loan = loanWritePlatformService.recalculateInterest(loan); + log.debug("End processing loan interest recalculation [{}]", loan.getId()); return loan; + } finally { + ThreadLocalContextUtil.setActionContext(ActionContext.COB); } + } - log.debug("Start processing loan interest recalculation [{}]", loan.getId()); - - ThreadLocalContextUtil.setActionContext(ActionContext.DEFAULT); - - loan = loanWritePlatformService.recalculateInterest(loan); - - log.debug("End processing loan interest recalculation [{}]", loan.getId()); - return loan; + private boolean hasOverdueInstallment(Loan loan) { + return loan.getRepaymentScheduleInstallments().stream() + .anyMatch(installment -> DateUtils.isBeforeBusinessDate(installment.getDueDate()) && !installment.isObligationsMet()); } @Override 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 93f89b0a0..b54b02a2d 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 @@ -705,4 +705,27 @@ public class LoanCOBCreateAccrualsTest extends BaseLoanIntegrationTest { Assertions.assertTrue(found, "Required transaction not found: " + tr + " on loan " + loanDetails.getId()); }); } + + @Test + public void shouldSkipInterestRecalculationWhenNoOverdueInstallments() { + setup(); + AtomicReference loanIdRef = new AtomicReference<>(); + runAt("01 April 2025", () -> { + // Create and disburse a loan with a single installment due in the future + Long loanId = applyAndApproveProgressiveLoan(client.getClientId(), loanProduct.getResourceId(), "01 April 2025", 100.0, 0.0, 1, + null); + loanIdRef.set(loanId); + disburseLoan(loanId, BigDecimal.valueOf(100), "01 April 2025"); + }); + runAt("02 April 2025", () -> { + Long loanId = loanIdRef.get(); + // No overdue installments: installment due in the future + executeInlineCOB(loanId); + GetLoansLoanIdResponse loanDetails = loanTransactionHelper.getLoanDetails(loanId); + // There should be only the disbursement transaction, no accrual/interest recalculation + Assertions.assertEquals(1, loanDetails.getTransactions().size(), + "No interest recalculation/accrual should occur if there are no overdue installments"); + Assertions.assertEquals("Disbursement", loanDetails.getTransactions().get(0).getType().getValue()); + }); + } }