diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/serialization/LoanDisbursementValidator.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/serialization/LoanDisbursementValidator.java index e6aef7259..2de131c56 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/serialization/LoanDisbursementValidator.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/serialization/LoanDisbursementValidator.java @@ -35,9 +35,10 @@ public final class LoanDisbursementValidator { public void compareDisbursedToApprovedOrProposedPrincipal(final Loan loan, final BigDecimal disbursedAmount, final BigDecimal totalDisbursed) { + final BigDecimal capitalizedIncome = loan.getSummary().getTotalCapitalizedIncome(); if (loan.loanProduct().isDisallowExpectedDisbursements() && loan.loanProduct().isAllowApprovedDisbursedAmountsOverApplied()) { final BigDecimal maxDisbursedAmount = loanApplicationValidator.getOverAppliedMax(loan); - if (totalDisbursed.compareTo(maxDisbursedAmount) > 0) { + if (totalDisbursed.add(capitalizedIncome).compareTo(maxDisbursedAmount) > 0) { final String errorMessage = String.format( "Loan disbursal amount can't be greater than maximum applied loan amount calculation. " + "Total disbursed amount: %s Maximum disbursal amount: %s", @@ -47,7 +48,8 @@ public final class LoanDisbursementValidator { maxDisbursedAmount); } } else { - if (totalDisbursed.compareTo(loan.getApprovedPrincipal()) > 0) { + if ((totalDisbursed.compareTo(loan.getApprovedPrincipal()) > 0) + || (totalDisbursed.add(capitalizedIncome).compareTo(loan.getApprovedPrincipal()) > 0)) { final String errorMsg = "Loan can't be disbursed,disburse amount is exceeding approved principal "; throw new LoanDisbursalException(errorMsg, "disburse.amount.must.be.less.than.approved.principal", totalDisbursed, loan.getApprovedPrincipal()); diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/serialization/LoanTransactionValidatorImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/serialization/LoanTransactionValidatorImpl.java index 3e6911a8b..e91852a6a 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/serialization/LoanTransactionValidatorImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/serialization/LoanTransactionValidatorImpl.java @@ -106,6 +106,7 @@ public final class LoanTransactionValidatorImpl implements LoanTransactionValida private final EntityDatatableChecksWritePlatformService entityDatatableChecksWritePlatformService; private final CalendarInstanceRepository calendarInstanceRepository; private final LoanDownPaymentTransactionValidator loanDownPaymentTransactionValidator; + private final LoanDisbursementValidator loanDisbursementValidator; private void throwExceptionIfValidationWarningsExist(final List dataValidationErrors) { if (!dataValidationErrors.isEmpty()) { @@ -157,6 +158,9 @@ public final class LoanTransactionValidatorImpl implements LoanTransactionValida validateLoanClientIsActive(loan); validateLoanGroupIsActive(loan); + final BigDecimal disbursedAmount = loan.getDisbursedAmount(); + loanDisbursementValidator.compareDisbursedToApprovedOrProposedPrincipal(loan, principal, disbursedAmount); + if (loan.isChargedOff()) { throw new GeneralPlatformDomainRuleException("error.msg.loan.disbursal.not.allowed.on.charged.off", "Loan: " + loan.getId() + " disbursement is not allowed on charged-off loan."); @@ -173,7 +177,6 @@ public final class LoanTransactionValidatorImpl implements LoanTransactionValida baseDataValidator.getDataValidationErrors().add(error); } - final BigDecimal disbursedAmount = loan.getDisbursedAmount(); final Set loanCollateralManagements = loan.getLoanCollateralManagements(); if ((loanCollateralManagements != null && !loanCollateralManagements.isEmpty()) && loan.getLoanType().isIndividualAccount()) { diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanCapitalizedIncomeTest.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanCapitalizedIncomeTest.java index 43b9390a9..087bde1af 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanCapitalizedIncomeTest.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanCapitalizedIncomeTest.java @@ -27,6 +27,7 @@ import org.apache.fineract.client.models.PostClientsResponse; import org.apache.fineract.client.models.PostLoanProductsRequest; import org.apache.fineract.client.models.PostLoanProductsResponse; import org.apache.fineract.client.models.PostLoansLoanIdTransactionsResponse; +import org.apache.fineract.client.util.CallFailedRuntimeException; import org.apache.fineract.integrationtests.common.BusinessStepHelper; import org.apache.fineract.integrationtests.common.ClientHelper; import org.junit.jupiter.api.Assertions; @@ -102,6 +103,38 @@ public class LoanCapitalizedIncomeTest extends BaseLoanIntegrationTest { }); } + @Test + public void testLoanDisbursementWithCapitalizedIncome() { + final AtomicReference loanIdRef = new AtomicReference<>(); + final AtomicReference capitalizedIncomeIdRef = new AtomicReference<>(); + + final PostClientsResponse client = clientHelper.createClient(ClientHelper.defaultClientCreationRequest()); + + final PostLoanProductsResponse loanProductsResponse = loanProductHelper + .createLoanProduct(create4IProgressive().enableIncomeCapitalization(true) + .capitalizedIncomeCalculationType(PostLoanProductsRequest.CapitalizedIncomeCalculationTypeEnum.FLAT) + .capitalizedIncomeStrategy(PostLoanProductsRequest.CapitalizedIncomeStrategyEnum.EQUAL_AMORTIZATION) + .deferredIncomeLiabilityAccountId(deferredIncomeLiabilityAccount.getAccountID().longValue()) + .incomeFromCapitalizationAccountId(feeIncomeAccount.getAccountID().longValue()) + .capitalizedIncomeType(PostLoanProductsRequest.CapitalizedIncomeTypeEnum.FEE).overAppliedNumber(3)); + + runAt("1 April 2024", () -> { + Long loanId = applyAndApproveProgressiveLoan(client.getClientId(), loanProductsResponse.getResourceId(), "1 January 2024", + 500.0, 7.0, 3, null); + loanIdRef.set(loanId); + + disburseLoan(loanId, BigDecimal.valueOf(300), "1 January 2024"); + PostLoansLoanIdTransactionsResponse capitalizedIncomeResponse = loanTransactionHelper.addCapitalizedIncome(loanId, + "1 January 2024", 50.0); + + CallFailedRuntimeException callFailedRuntimeException = Assertions.assertThrows(CallFailedRuntimeException.class, + () -> disburseLoan(loanId, BigDecimal.valueOf(200), "1 February 2024")); + + Assertions.assertTrue(callFailedRuntimeException.getMessage() + .contains("Loan disbursal amount can't be greater than maximum applied loan amount calculation")); + }); + } + @Test public void testLoanCapitalizedIncomeAdjustment() { final AtomicReference loanIdRef = new AtomicReference<>();