From 28747fecd15935f0f71c580174d0cef80113569f Mon Sep 17 00:00:00 2001 From: "abraham.menyhart" Date: Tue, 4 Jul 2023 13:07:20 +0200 Subject: [PATCH] FINERACT-1724: Approval limit calculation modification --- .../portfolio/loanaccount/domain/Loan.java | 7 +- ...oanDisbursementDetailsIntegrationTest.java | 93 +++++++++++++++++++ .../common/loans/LoanProductTestBuilder.java | 4 +- .../common/loans/LoanTransactionHelper.java | 6 ++ 4 files changed, 106 insertions(+), 4 deletions(-) diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java index d69e1149f..dd9cd2bd1 100644 --- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java +++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java @@ -2668,8 +2668,11 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom { if (this.loanProduct().isDisallowExpectedDisbursements() && this.loanProduct().isAllowApprovedDisbursedAmountsOverApplied()) { BigDecimal maxDisbursedAmount = getOverAppliedMax(); - if (disbursedAmount.compareTo(maxDisbursedAmount) > 0) { - final String errorMessage = "Loan disbursal amount can't be greater than maximum applied loan amount calculation."; + if (totalDisbursed.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", + totalDisbursed.stripTrailingZeros().toPlainString(), maxDisbursedAmount.stripTrailingZeros().toPlainString()); throw new InvalidLoanStateTransitionException("disbursal", "amount.can't.be.greater.than.maximum.applied.loan.amount.calculation", errorMessage, disbursedAmount, maxDisbursedAmount); diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanDisbursementDetailsIntegrationTest.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanDisbursementDetailsIntegrationTest.java index 3d6cdbe5b..6ce5f9937 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanDisbursementDetailsIntegrationTest.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanDisbursementDetailsIntegrationTest.java @@ -18,6 +18,8 @@ */ package org.apache.fineract.integrationtests; +import static java.lang.Double.parseDouble; +import static org.hamcrest.Matchers.equalTo; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -36,6 +38,7 @@ import java.util.List; import java.util.Locale; import lombok.extern.slf4j.Slf4j; import org.apache.fineract.client.models.GetLoanProductsProductIdResponse; +import org.apache.fineract.client.models.GetLoansLoanIdDisbursementDetails; import org.apache.fineract.client.models.GetLoansLoanIdRepaymentPeriod; import org.apache.fineract.client.models.GetLoansLoanIdRepaymentSchedule; import org.apache.fineract.client.models.GetLoansLoanIdResponse; @@ -316,6 +319,89 @@ public class LoanDisbursementDetailsIntegrationTest { log.info("-----------MULTI DISBURSAL LOAN EQUAL INSTALLMENTS SUCCESSFULLY-------"); } + @Test + public void disburseLoanWithExceededOverAppliedAmountFails() { + final String operationDate = "01 January 2014"; + final String principal = "1000"; + final String firstDisbursedPrincipal = "900"; + final String secondDisbursedPrincipal = "1101"; + + final Integer clientId = ClientHelper.createClient(this.requestSpec, this.responseSpec, operationDate); + log.info("-----------------CLIENT CREATED WITH ID------------------- {}", clientId); + + final String loanProductJSON = new LoanProductTestBuilder().withAmortizationTypeAsEqualInstallments() // + .withInterestTypeAsDecliningBalance().withMoratorium("", "").withInterestCalculationPeriodTypeAsRepaymentPeriod(true) + .withInterestTypeAsDecliningBalance() // + .withMultiDisburse() // + .withDisallowExpectedDisbursements(true) // + .build(null); + log.info("Product {}", loanProductJSON); + final Integer loanProductId = this.loanTransactionHelper.getLoanProductId(loanProductJSON); + log.info("------------------LOAN PRODUCT CREATED WITH ID----------- {}", loanProductId); + + final Integer loanId = applyForMultiTrancheLoanApplication(clientId.toString(), loanProductId.toString(), principal, operationDate); + + log.info("-------------------LOAN CREATED WITH loanId----------------- {}", loanId); + + this.loanTransactionHelper.approveLoanWithApproveAmount(operationDate, expectedDisbursementDate, principal, loanId, null); + log.info("-------------------MULTI DISBURSAL LOAN APPROVED SUCCESSFULLY-------"); + + GetLoansLoanIdResponse getLoansLoanIdResponse = this.loanTransactionHelper.getLoan(requestSpec, responseSpec, loanId); + assertNotNull(getLoansLoanIdResponse); + + this.loanTransactionHelper.printRepaymentSchedule(getLoansLoanIdResponse); + + loanTransactionHelper.disburseLoanWithTransactionAmount(operationDate, loanId, firstDisbursedPrincipal); + log.info("-------------------MULTI DISBURSAL LOAN DISBURSED SUCCESSFULLY-------"); + + loanTransactionHelper.disburseLoanWithTransactionAmount(operationDate, loanId, secondDisbursedPrincipal, + overAppliedAmountFailedResponseSpec()); + log.info("-------------------MULTI DISBURSAL LOAN DISBURSEMENT FAILED-------"); + } + + @Test + public void disburseLoanWithExceededOverAppliedAmountSucceed() { + final String operationDate = "01 January 2014"; + final String principal = "1000"; + final String firstDisbursedPrincipal = "900"; + final String secondDisbursedPrincipal = "1100"; + + final Integer clientId = ClientHelper.createClient(this.requestSpec, this.responseSpec, operationDate); + log.info("-----------------CLIENT CREATED WITH ID------------------- {}", clientId); + + final String loanProductJSON = new LoanProductTestBuilder().withAmortizationTypeAsEqualInstallments() // + .withInterestTypeAsDecliningBalance().withMoratorium("", "").withInterestCalculationPeriodTypeAsRepaymentPeriod(true) + .withInterestTypeAsDecliningBalance() // + .withMultiDisburse() // + .withDisallowExpectedDisbursements(true) // + .build(null); + log.info("Product {}", loanProductJSON); + final Integer loanProductId = this.loanTransactionHelper.getLoanProductId(loanProductJSON); + log.info("------------------LOAN PRODUCT CREATED WITH ID----------- {}", loanProductId); + + final Integer loanId = applyForMultiTrancheLoanApplication(clientId.toString(), loanProductId.toString(), principal, operationDate); + + log.info("-------------------LOAN CREATED WITH loanId----------------- {}", loanId); + + this.loanTransactionHelper.approveLoanWithApproveAmount(operationDate, expectedDisbursementDate, principal, loanId, null); + log.info("-------------------MULTI DISBURSAL LOAN APPROVED SUCCESSFULLY-------"); + + GetLoansLoanIdResponse getLoansLoanIdResponse = this.loanTransactionHelper.getLoan(requestSpec, responseSpec, loanId); + assertNotNull(getLoansLoanIdResponse); + + this.loanTransactionHelper.printRepaymentSchedule(getLoansLoanIdResponse); + + loanTransactionHelper.disburseLoanWithTransactionAmount(operationDate, loanId, firstDisbursedPrincipal); + log.info("-------------------MULTI DISBURSAL LOAN DISBURSED SUCCESSFULLY-FIRST-------"); + + loanTransactionHelper.disburseLoanWithTransactionAmount(operationDate, loanId, secondDisbursedPrincipal); + log.info("-------------------MULTI DISBURSAL LOAN DISBURSED SUCCESSFULLY-SECOND-------"); + + double disbursementPrincipalSum = this.loanTransactionHelper.getLoan(requestSpec, responseSpec, loanId).getDisbursementDetails() + .stream().map(GetLoansLoanIdDisbursementDetails::getPrincipal).mapToDouble(p -> p).sum(); + assertEquals(parseDouble(firstDisbursedPrincipal) + parseDouble(secondDisbursedPrincipal), disbursementPrincipalSum); + } + @Test public void createApproveAndValidateMultiDisburseLoan() throws ParseException { @@ -599,4 +685,11 @@ public class LoanDisbursementDetailsIntegrationTest { } } } + + private ResponseSpecification overAppliedAmountFailedResponseSpec() { + return new ResponseSpecBuilder().expectBody("userMessageGlobalisationCode", equalTo("validation.msg.domain.rule.violation")) + .expectBody("errors[0].userMessageGlobalisationCode", + equalTo("error.msg.loan.disbursal.amount.can't.be.greater.than.maximum.applied.loan.amount.calculation")) + .expectStatusCode(403).build(); + } } diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/loans/LoanProductTestBuilder.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/loans/LoanProductTestBuilder.java index d06dbd24d..a13b01e6c 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/loans/LoanProductTestBuilder.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/loans/LoanProductTestBuilder.java @@ -435,8 +435,8 @@ public class LoanProductTestBuilder { return this; } - public LoanProductTestBuilder withDisallowExpectedDisbursements(boolean disallowExpectectedDisbursements) { - this.disallowExpectedDisbursements = disallowExpectectedDisbursements; + public LoanProductTestBuilder withDisallowExpectedDisbursements(boolean disallowExpectedDisbursements) { + this.disallowExpectedDisbursements = disallowExpectedDisbursements; if (this.disallowExpectedDisbursements) { this.allowApprovedDisbursedAmountsOverApplied = true; this.overAppliedCalculationType = "percentage"; diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/loans/LoanTransactionHelper.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/loans/LoanTransactionHelper.java index 16c1a1708..b162694cd 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/loans/LoanTransactionHelper.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/loans/LoanTransactionHelper.java @@ -356,6 +356,12 @@ public class LoanTransactionHelper extends IntegrationTest { getDisburseLoanAsJSON(date, transactionAmount, null, externalId), ""); } + public Object disburseLoanWithTransactionAmount(final String date, final Integer loanID, final String transactionAmount, + ResponseSpecification responseSpec) { + return performLoanTransaction(createLoanOperationURL(DISBURSE_LOAN_COMMAND, loanID), + getDisburseLoanAsJSON(date, transactionAmount, null), responseSpec); + } + public HashMap disburseLoanWithTransactionAmount(final String date, final Integer loanID, final String transactionAmount) { return performLoanTransaction(createLoanOperationURL(DISBURSE_LOAN_COMMAND, loanID), getDisburseLoanAsJSON(date, transactionAmount, null));