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 68bfd7a3f83413316044e6f1ce51f18ca0dbe644 Author: mariiaKraievska <[email protected]> AuthorDate: Fri Jul 25 16:24:06 2025 +0300 FINERACT-2308: Allow approval / disbursal above loan applied amount for multidisbursal progressive loan that does expect tranches --- .../test/data/loanproduct/DefaultLoanProduct.java | 1 + .../global/LoanProductGlobalInitializerStep.java | 37 +++++ .../fineract/test/support/TestContextKey.java | 1 + .../test/resources/features/LoanProduct.feature | 14 ++ .../DelinquencyReadPlatformServiceImpl.java | 6 +- .../portfolio/loanproduct/domain/LoanProduct.java | 8 -- .../portfolio/loanproduct.validation.feature | 2 - .../LoanProductOverAppliedAmountTest.java | 152 +++++++++++++++++++++ 8 files changed, 210 insertions(+), 11 deletions(-) diff --git a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/data/loanproduct/DefaultLoanProduct.java b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/data/loanproduct/DefaultLoanProduct.java index 43dbdab2ee..157415377b 100644 --- a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/data/loanproduct/DefaultLoanProduct.java +++ b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/data/loanproduct/DefaultLoanProduct.java @@ -132,6 +132,7 @@ public enum DefaultLoanProduct implements LoanProduct { LP2_PROGRESSIVE_ADVANCED_PAYMENT_ALLOCATION_CAPITALIZED_INCOME, // LP2_PROGRESSIVE_ADVANCED_PAYMENT_ALLOCATION_BUYDOWN_FEES, // LP2_PROGRESSIVE_ADVANCED_PAYMENT_ALLOCATION_BUYDOWN_FEES_CHARGE_OFF_REASON, // + LP2_PROGRESSIVE_ADV_PYMNT_INTEREST_RECALC_360_30_MULTIDISB_OVER_APPLIED_EXPECTED_TRANCHES, // LP2_ADV_PYMNT_INTEREST_DAILY_EMI_360_30_INTEREST_RECALC_DAILY_CAPITALIZED_INCOME_ADJ_CUSTOM_ALLOC, // LP2_ADV_PYMNT_INTEREST_DAILY_EMI_360_30_INTEREST_RECALC_DAILY_CAPITALIZED_INCOME, // LP2_ADV_PYMNT_INTEREST_DAILY_EMI_360_30_INTEREST_RECALC_DAILY_CAPITALIZED_INCOME_FEE, // diff --git a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/initializer/global/LoanProductGlobalInitializerStep.java b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/initializer/global/LoanProductGlobalInitializerStep.java index afd90b0d4d..d7637bcd92 100644 --- a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/initializer/global/LoanProductGlobalInitializerStep.java +++ b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/initializer/global/LoanProductGlobalInitializerStep.java @@ -3426,6 +3426,43 @@ public class LoanProductGlobalInitializerStep implements FineractGlobalInitializ TestContext.INSTANCE.set(TestContextKey.DEFAULT_LOAN_PRODUCT_CREATE_RESPONSE_LP1_MULTIDISBURSAL_EXPECTS_TRANCHES, responseLoanProductMultidisbursalExpectTranches); + // LP2 with progressive loan schedule + horizontal + interest EMI + 360/30 + // + interest recalculation, preClosureInterestCalculationStrategy= till preclose + // Frequency for recalculate Outstanding Principal: Daily, Frequency Interval for recalculation: 1 + // allow approved/disbursed amount over applied amount is enabled with percentage + // multidisbursal loan that expects tranches + // type + final String name131 = DefaultLoanProduct.LP2_PROGRESSIVE_ADV_PYMNT_INTEREST_RECALC_360_30_MULTIDISB_OVER_APPLIED_EXPECTED_TRANCHES + .getName(); + final PostLoanProductsRequest loanProductsRequestLP2ProgressiveAdvPymnt36030InterestRecalcMultidisbApprovedOverAppliedExpectTranches = loanProductsRequestFactory + .defaultLoanProductsRequestLP2EmiCapitalizedIncome()// + .name(name131)// + .daysInYearType(DaysInYearType.DAYS360.value)// + .daysInMonthType(DaysInMonthType.DAYS30.value)// + .isInterestRecalculationEnabled(true)// + .preClosureInterestCalculationStrategy(1)// + .rescheduleStrategyMethod(4)// + .interestRecalculationCompoundingMethod(0)// + .recalculationRestFrequencyType(2)// + .recalculationRestFrequencyInterval(1)// + .paymentAllocation(List.of(// + createPaymentAllocation("DEFAULT", "NEXT_INSTALLMENT"), // + createPaymentAllocation("GOODWILL_CREDIT", "LAST_INSTALLMENT"), // + createPaymentAllocation("MERCHANT_ISSUED_REFUND", "REAMORTIZATION"), // + createPaymentAllocation("PAYOUT_REFUND", "NEXT_INSTALLMENT"))) // + .allowApprovedDisbursedAmountsOverApplied(true)// + .overAppliedCalculationType(OverAppliedCalculationType.PERCENTAGE.value)// + .overAppliedNumber(50)// + .multiDisburseLoan(true)// + .disallowExpectedDisbursements(false)// + .maxTrancheCount(10)// + .outstandingLoanBalance(10000.0);// + final Response<PostLoanProductsResponse> responseLoanProductsRequestLP2ProgressiveAdvPymnt36030InterestRecalcMultidisbApprovedOverAppliedExpectTranches = loanProductsApi + .createLoanProduct(loanProductsRequestLP2ProgressiveAdvPymnt36030InterestRecalcMultidisbApprovedOverAppliedExpectTranches) + .execute(); + TestContext.INSTANCE.set( + TestContextKey.DEFAULT_LOAN_PRODUCT_CREATE_RESPONSE_LP2_PROGRESSIVE_ADV_PYMNT_INTEREST_RECALC_360_30_MULTIDISB_OVER_APPLIED_EXPECTED_TRANCHES, + responseLoanProductsRequestLP2ProgressiveAdvPymnt36030InterestRecalcMultidisbApprovedOverAppliedExpectTranches); } public static AdvancedPaymentData createPaymentAllocation(String transactionType, String futureInstallmentAllocationRule, diff --git a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/support/TestContextKey.java b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/support/TestContextKey.java index a70ddc138a..e404e50432 100644 --- a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/support/TestContextKey.java +++ b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/support/TestContextKey.java @@ -255,4 +255,5 @@ public abstract class TestContextKey { public static final String DEFAULT_LOAN_PRODUCT_CREATE_RESPONSE_LP2_ADV_PYMNT_INTEREST_DAILY_INSTALLMENT_FEE_PERCENT_AMOUNT_INTEREST_CHARGES = "loanProductCreateResponseLP2AdvancedPaymentInterestDailyInstallmentFeeAmountPlusPercentageInterestCharges"; public static final String DEFAULT_LOAN_PRODUCT_CREATE_RESPONSE_LP2_ADV_PYMNT_INTEREST_DAILY_INSTALLMENT_FEE_ALL_CHARGES = "loanProductCreateResponseLP2AdvancedPaymentInterestDailyInstallmentFeeAllCharges"; public static final String DEFAULT_LOAN_PRODUCT_CREATE_RESPONSE_LP2_ADV_PYMNT_INTEREST_DAILY_INSTALLMENT_FEE_FLAT_INTEREST_CHARGES_TRANCHE = "loanProductCreateResponseLP2AdvancedPaymentInterestDailyInstallmentFeeFlatPlusPercentageInterestChargesMultidisbursal"; + public static final String DEFAULT_LOAN_PRODUCT_CREATE_RESPONSE_LP2_PROGRESSIVE_ADV_PYMNT_INTEREST_RECALC_360_30_MULTIDISB_OVER_APPLIED_EXPECTED_TRANCHES = "loanProductCreateResponseLP2ProgressiveAdvancedPaymentInterestRecalculationMultidisbursalApprovedOverAppliedAmountExpectedTransches"; } diff --git a/fineract-e2e-tests-runner/src/test/resources/features/LoanProduct.feature b/fineract-e2e-tests-runner/src/test/resources/features/LoanProduct.feature index 5c9efe1440..4808c43a30 100644 --- a/fineract-e2e-tests-runner/src/test/resources/features/LoanProduct.feature +++ b/fineract-e2e-tests-runner/src/test/resources/features/LoanProduct.feature @@ -251,3 +251,17 @@ Feature: LoanProduct Then Loan Product response contains Buy Down Fees flag "false" Then Loan Details response contains Buy Down Fees flag "false" + @TestRailId:C3884 + Scenario: As a user I would like to verify multi-disburse loan product with over-applied amount and expected tranches can be created + When Admin sets the business date to "1 January 2024" + And Admin creates a client with random data + When Admin creates a fully customized loan with disbursements details and following data: + | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | 1st_tranche_disb_expected_date |1st_tranche_disb [...] + | LP2_PROGRESSIVE_ADV_PYMNT_INTEREST_RECALC_360_30_MULTIDISB_OVER_APPLIED_EXPECTED_TRANCHES | 01 January 2024 | 1000 | 7 | DECLINING_BALANCE | DAILY | EQUAL_INSTALLMENTS | 6 | MONTHS | 1 | MONTHS | 6 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | 01 January 2024 | 700.0 [...] + And Admin successfully approves the loan on "1 January 2024" with "1000" amount and expected disbursement date on "1 January 2024" + And Admin successfully disburse the loan on "1 January 2024" with "1000" EUR transaction amount + Then Loan status will be "ACTIVE" + When Admin sets the business date to "2 January 2024" + And Admin successfully disburse the loan on "2 January 2024" with "300" EUR transaction amount + And Admin adds capitalized income with "AUTOPAY" payment type to the loan on "02 January 2024" with "200" EUR transaction amount + diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/delinquency/service/DelinquencyReadPlatformServiceImpl.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/delinquency/service/DelinquencyReadPlatformServiceImpl.java index 7406584aff..62bbbe2fa1 100644 --- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/delinquency/service/DelinquencyReadPlatformServiceImpl.java +++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/delinquency/service/DelinquencyReadPlatformServiceImpl.java @@ -21,6 +21,7 @@ package org.apache.fineract.portfolio.delinquency.service; import static org.apache.fineract.portfolio.loanaccount.domain.Loan.EARLIEST_UNPAID_DATE; import static org.apache.fineract.portfolio.loanaccount.domain.Loan.NEXT_UNPAID_DUE_DATE; +import java.math.BigDecimal; import java.time.LocalDate; import java.util.Collection; import java.util.Comparator; @@ -148,7 +149,10 @@ public class DelinquencyReadPlatformServiceImpl implements DelinquencyReadPlatfo // Overpaid // loans collectionData = loanDelinquencyDomainService.getOverdueCollectionData(loan, effectiveDelinquencyList); - collectionData.setAvailableDisbursementAmount(loan.getApprovedPrincipal().subtract(loan.getDisbursedAmount())); + // Ensure availableDisbursementAmount is never negative + final BigDecimal availableAmount = loan.getApprovedPrincipal().subtract(loan.getDisbursedAmount()); + collectionData + .setAvailableDisbursementAmount(availableAmount.compareTo(BigDecimal.ZERO) < 0 ? BigDecimal.ZERO : availableAmount); collectionData.setNextPaymentDueDate(possibleNextRepaymentDate(nextPaymentDueDateConfig, loan)); final LoanTransaction lastPayment = loan.getLastPaymentTransaction(); diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/domain/LoanProduct.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/domain/LoanProduct.java index 5f73f1985f..c53c0f6b9a 100644 --- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/domain/LoanProduct.java +++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/domain/LoanProduct.java @@ -414,14 +414,6 @@ public class LoanProduct extends AbstractPersistableCustom<Long> { } } - if (this.allowApprovedDisbursedAmountsOverApplied) { - if (this.isMultiDisburseLoan() && !this.disallowExpectedDisbursements) { - throw new LoanProductGeneralRuleException( - "disallowExpectedDisbursements.not.set.allowApprovedDisbursedAmountsOverApplied.cant.be.set", - "Disallow Expected Disbursals Not Set - Allow Approved / Disbursed Amounts Over Applied Can't Be Set"); - } - } - if (this.overAppliedCalculationType == null || this.overAppliedCalculationType.isEmpty()) { if (this.allowApprovedDisbursedAmountsOverApplied) { throw new LoanProductGeneralRuleException( diff --git a/fineract-provider/src/test/resources/features/portfolio/loanproduct.validation.feature b/fineract-provider/src/test/resources/features/portfolio/loanproduct.validation.feature index 36a7a0f2ba..906b17aa51 100644 --- a/fineract-provider/src/test/resources/features/portfolio/loanproduct.validation.feature +++ b/fineract-provider/src/test/resources/features/portfolio/loanproduct.validation.feature @@ -28,10 +28,8 @@ Feature: Loan Product Validation Examples: | allowMultipleDisbursal | disallowExpectedDisbursements | allowApprovedDisbursedAmountsOverApplied | overAppliedCalculationType | overAppliedNumber | exception | template | | false | true | false | - | -1 | org.apache.fineract.portfolio.loanproduct.exception.LoanProductGeneralRuleException | error.msg.allowMultipleDisbursals.not.set.disallowExpectedDisbursements.cant.be.set | - | true | false | true | - | -1 | org.apache.fineract.portfolio.loanproduct.exception.LoanProductGeneralRuleException | error.msg.disallowExpectedDisbursements.not.set.allowApprovedDisbursedAmountsOverApplied.cant.be.set | | true | true | true | - | -1 | org.apache.fineract.portfolio.loanproduct.exception.LoanProductGeneralRuleException | error.msg.allowApprovedDisbursedAmountsOverApplied.is.set.overAppliedCalculationType.is.mandatory | | true | true | true | - | -1 | org.apache.fineract.portfolio.loanproduct.exception.LoanProductGeneralRuleException | error.msg.allowApprovedDisbursedAmountsOverApplied.is.set.overAppliedCalculationType.is.mandatory | - | true | false | true | - | -1 | org.apache.fineract.portfolio.loanproduct.exception.LoanProductGeneralRuleException | error.msg.disallowExpectedDisbursements.not.set.allowApprovedDisbursedAmountsOverApplied.cant.be.set | | true | true | true | notflat | -1 | org.apache.fineract.portfolio.loanproduct.exception.LoanProductGeneralRuleException | error.msg.overAppliedCalculationType.must.be.percentage.or.flat | | true | true | false | flat | -1 | org.apache.fineract.portfolio.loanproduct.exception.LoanProductGeneralRuleException | error.msg.allowApprovedDisbursedAmountsOverApplied.is.not.set.overAppliedCalculationType.cant.be.entered | | true | true | true | flat | -1 | org.apache.fineract.portfolio.loanproduct.exception.LoanProductGeneralRuleException | error.msg.allowApprovedDisbursedAmountsOverApplied.is.set.overAppliedNumber.is.mandatory | diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanProductOverAppliedAmountTest.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanProductOverAppliedAmountTest.java new file mode 100644 index 0000000000..489f70ac95 --- /dev/null +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanProductOverAppliedAmountTest.java @@ -0,0 +1,152 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.integrationtests; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.math.BigDecimal; +import java.math.RoundingMode; +import java.util.List; +import org.apache.fineract.client.models.GetLoanProductsProductIdResponse; +import org.apache.fineract.client.models.GetLoansLoanIdResponse; +import org.apache.fineract.client.models.PostLoanProductsRequest; +import org.apache.fineract.client.models.PostLoanProductsResponse; +import org.apache.fineract.client.models.PostLoansDisbursementData; +import org.apache.fineract.client.models.PutLoanProductsProductIdRequest; +import org.apache.fineract.client.models.PutLoanProductsProductIdResponse; +import org.apache.fineract.integrationtests.common.ClientHelper; +import org.apache.fineract.portfolio.loanaccount.domain.LoanStatus; +import org.junit.jupiter.api.Test; + +public class LoanProductOverAppliedAmountTest extends BaseLoanIntegrationTest { + + @Test + public void testCreateMultiDisburseLoanProductWithOverAppliedAmountAndExpectedTranches() { + runAt("01 January 2024", () -> { + // Create Loan Product with multi-disburse, expected tranches, and over-applied amount + final PostLoanProductsRequest loanProductRequest = create4IProgressive().multiDisburseLoan(true).maxTrancheCount(5) + .outstandingLoanBalance(10000.0).disallowExpectedDisbursements(false) // Expected tranches enabled + .allowApprovedDisbursedAmountsOverApplied(true).overAppliedCalculationType("percentage").overAppliedNumber(50); + + // This should not throw an exception + final PostLoanProductsResponse loanProductResponse = assertDoesNotThrow( + () -> loanProductHelper.createLoanProduct(loanProductRequest)); + + assertNotNull(loanProductResponse); + assertNotNull(loanProductResponse.getResourceId()); + + // Retrieve the created loan product to verify settings + final GetLoanProductsProductIdResponse retrievedProduct = loanProductHelper + .retrieveLoanProductById(loanProductResponse.getResourceId()); + + // Verify the loan product was created with correct settings + assertEquals(true, retrievedProduct.getMultiDisburseLoan()); + assertEquals(false, retrievedProduct.getDisallowExpectedDisbursements()); + assertEquals(true, retrievedProduct.getAllowApprovedDisbursedAmountsOverApplied()); + assertEquals("percentage", retrievedProduct.getOverAppliedCalculationType()); + }); + } + + @Test + public void testModifyMultiDisburseLoanProductWithOverAppliedAmountAndExpectedTranches() { + runAt("01 January 2024", () -> { + // Create initial loan product without over-applied amount + final PostLoanProductsRequest initialLoanProductRequest = create4IProgressive().multiDisburseLoan(true).maxTrancheCount(5) + .outstandingLoanBalance(10000.0).disallowExpectedDisbursements(false).allowApprovedDisbursedAmountsOverApplied(false) + .overAppliedCalculationType(null).overAppliedNumber(null); + + final PostLoanProductsResponse initialLoanProductResponse = loanProductHelper.createLoanProduct(initialLoanProductRequest); + final Long loanProductId = initialLoanProductResponse.getResourceId(); + + // Modify loan product to enable over-applied amount + final PutLoanProductsProductIdRequest modifyRequest = new PutLoanProductsProductIdRequest() + .allowApprovedDisbursedAmountsOverApplied(true).overAppliedCalculationType("flat").overAppliedNumber(200).locale("en"); + + final PutLoanProductsProductIdResponse modifyResponse = assertDoesNotThrow( + () -> loanProductHelper.updateLoanProductById(loanProductId, modifyRequest)); + + assertNotNull(modifyResponse); + + // Retrieve the updated loan product to verify settings + final GetLoanProductsProductIdResponse retrievedProduct = loanProductHelper.retrieveLoanProductById(loanProductId); + assertEquals(true, retrievedProduct.getMultiDisburseLoan()); + assertEquals(false, retrievedProduct.getDisallowExpectedDisbursements()); + assertEquals(true, retrievedProduct.getAllowApprovedDisbursedAmountsOverApplied()); + assertEquals("flat", retrievedProduct.getOverAppliedCalculationType()); + }); + } + + @Test + public void testAvailableDisbursementAmountNotNegativeWhenDisbursedAmountExceedsApprovedAmount() { + runAt("01 January 2024", () -> { + // Create Loan Product with over-applied amount enabled + final PostLoanProductsRequest loanProductRequest = create4IProgressive().multiDisburseLoan(true).maxTrancheCount(5) + .outstandingLoanBalance(10000.0).disallowExpectedDisbursements(false) // Expected tranches enabled + .allowApprovedDisbursedAmountsOverApplied(true).overAppliedCalculationType("percentage").overAppliedNumber(50); + + final PostLoanProductsResponse loanProductResponse = loanProductHelper.createLoanProduct(loanProductRequest); + final Long loanProductId = loanProductResponse.getResourceId(); + + // Create client + final Long clientId = clientHelper.createClient(ClientHelper.defaultClientCreationRequest()).getClientId(); + assertNotNull(clientId); + + // Create and approve loan with amount 1000 + final Long loanId = applyAndApproveProgressiveLoan(clientId, loanProductId, "1 January 2024", 1000.0, 7.0, 6, + request -> request.disbursementData(List.of(new PostLoansDisbursementData().expectedDisbursementDate("1 January 2024") + .principal(BigDecimal.valueOf(1000.0))))); + + // Disburse loan with amount 1500 (exceeds approved amount, but allowed due to over-applied setting) + disburseLoan(loanId, BigDecimal.valueOf(1500.0), "01 January 2024"); + + // Verify loan is active + verifyLoanStatus(loanId, LoanStatus.ACTIVE); + + final GetLoansLoanIdResponse loanDetails = loanTransactionHelper.getLoanDetails(loanId); + assertNotNull(loanDetails); + + // Verify that the loan was created and disbursed successfully + assert loanDetails.getStatus() != null; + assertEquals(Boolean.TRUE, loanDetails.getStatus().getActive()); + + // Verify the amounts + assert loanDetails.getApprovedPrincipal() != null; + assertEquals(BigDecimal.valueOf(1000.0), loanDetails.getApprovedPrincipal().setScale(1, RoundingMode.HALF_UP)); + assert loanDetails.getDisbursementDetails() != null; + double disbursementPrincipalSum = loanDetails.getDisbursementDetails().stream().mapToDouble(detail -> { + assert detail.getPrincipal() != null; + return detail.getPrincipal(); + }).sum(); + assertEquals(1500.0, disbursementPrincipalSum); + + // The key test: availableDisbursementAmount should be 0 (not negative) + // since disbursed amount (1500) > approved amount (1000) + assert loanDetails.getDelinquent() != null; + final BigDecimal availableDisbursementAmount = loanDetails.getDelinquent().getAvailableDisbursementAmount(); + assertNotNull(availableDisbursementAmount); + assertTrue(availableDisbursementAmount.compareTo(BigDecimal.ZERO) >= 0, + "availableDisbursementAmount should not be negative. Expected >= 0, but was: " + availableDisbursementAmount); + assertEquals(BigDecimal.ZERO, availableDisbursementAmount, + "availableDisbursementAmount should be 0 when disbursed amount exceeds approved amount"); + }); + } +}
