FINERACT-2308: Allow approval / disbursal above loan applied amount for multidisbursal progressive loan that does expect tranches
This commit is contained in:
committed by
Adam Saghy
parent
cb4c66302a
commit
68bfd7a3f8
+1
@@ -132,6 +132,7 @@ public enum DefaultLoanProduct implements LoanProduct {
|
|||||||
LP2_PROGRESSIVE_ADVANCED_PAYMENT_ALLOCATION_CAPITALIZED_INCOME, //
|
LP2_PROGRESSIVE_ADVANCED_PAYMENT_ALLOCATION_CAPITALIZED_INCOME, //
|
||||||
LP2_PROGRESSIVE_ADVANCED_PAYMENT_ALLOCATION_BUYDOWN_FEES, //
|
LP2_PROGRESSIVE_ADVANCED_PAYMENT_ALLOCATION_BUYDOWN_FEES, //
|
||||||
LP2_PROGRESSIVE_ADVANCED_PAYMENT_ALLOCATION_BUYDOWN_FEES_CHARGE_OFF_REASON, //
|
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_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, //
|
||||||
LP2_ADV_PYMNT_INTEREST_DAILY_EMI_360_30_INTEREST_RECALC_DAILY_CAPITALIZED_INCOME_FEE, //
|
LP2_ADV_PYMNT_INTEREST_DAILY_EMI_360_30_INTEREST_RECALC_DAILY_CAPITALIZED_INCOME_FEE, //
|
||||||
|
|||||||
+37
@@ -3426,6 +3426,43 @@ public class LoanProductGlobalInitializerStep implements FineractGlobalInitializ
|
|||||||
TestContext.INSTANCE.set(TestContextKey.DEFAULT_LOAN_PRODUCT_CREATE_RESPONSE_LP1_MULTIDISBURSAL_EXPECTS_TRANCHES,
|
TestContext.INSTANCE.set(TestContextKey.DEFAULT_LOAN_PRODUCT_CREATE_RESPONSE_LP1_MULTIDISBURSAL_EXPECTS_TRANCHES,
|
||||||
responseLoanProductMultidisbursalExpectTranches);
|
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,
|
public static AdvancedPaymentData createPaymentAllocation(String transactionType, String futureInstallmentAllocationRule,
|
||||||
|
|||||||
+1
@@ -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_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_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_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";
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -251,3 +251,17 @@ Feature: LoanProduct
|
|||||||
Then Loan Product response contains Buy Down Fees flag "false"
|
Then Loan Product response contains Buy Down Fees flag "false"
|
||||||
Then Loan Details 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_principal | 2nd_tranche_disb_expected_date | 2nd_tranche_disb_principal |
|
||||||
|
| 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 | 02 January 2024 | 300.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
|
||||||
|
|
||||||
|
|||||||
+5
-1
@@ -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.EARLIEST_UNPAID_DATE;
|
||||||
import static org.apache.fineract.portfolio.loanaccount.domain.Loan.NEXT_UNPAID_DUE_DATE;
|
import static org.apache.fineract.portfolio.loanaccount.domain.Loan.NEXT_UNPAID_DUE_DATE;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
import java.time.LocalDate;
|
import java.time.LocalDate;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Comparator;
|
import java.util.Comparator;
|
||||||
@@ -148,7 +149,10 @@ public class DelinquencyReadPlatformServiceImpl implements DelinquencyReadPlatfo
|
|||||||
// Overpaid
|
// Overpaid
|
||||||
// loans
|
// loans
|
||||||
collectionData = loanDelinquencyDomainService.getOverdueCollectionData(loan, effectiveDelinquencyList);
|
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));
|
collectionData.setNextPaymentDueDate(possibleNextRepaymentDate(nextPaymentDueDateConfig, loan));
|
||||||
|
|
||||||
final LoanTransaction lastPayment = loan.getLastPaymentTransaction();
|
final LoanTransaction lastPayment = loan.getLastPaymentTransaction();
|
||||||
|
|||||||
-8
@@ -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.overAppliedCalculationType == null || this.overAppliedCalculationType.isEmpty()) {
|
||||||
if (this.allowApprovedDisbursedAmountsOverApplied) {
|
if (this.allowApprovedDisbursedAmountsOverApplied) {
|
||||||
throw new LoanProductGeneralRuleException(
|
throw new LoanProductGeneralRuleException(
|
||||||
|
|||||||
@@ -28,10 +28,8 @@ Feature: Loan Product Validation
|
|||||||
Examples:
|
Examples:
|
||||||
| allowMultipleDisbursal | disallowExpectedDisbursements | allowApprovedDisbursedAmountsOverApplied | overAppliedCalculationType | overAppliedNumber | exception | template |
|
| 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 |
|
| 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 | 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 | 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 | 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 |
|
| true | true | true | flat | -1 | org.apache.fineract.portfolio.loanproduct.exception.LoanProductGeneralRuleException | error.msg.allowApprovedDisbursedAmountsOverApplied.is.set.overAppliedNumber.is.mandatory |
|
||||||
|
|||||||
+152
@@ -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");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user