From 319f457874285bbbf3c06e46053d0f67e7f6386d Mon Sep 17 00:00:00 2001 From: Jose Alberto Hernandez Date: Fri, 12 Jul 2024 00:47:36 -0600 Subject: [PATCH] FINERACT-2102: Loan Product DownPayment attributes overrided in the Loan --- .../api/LoansApiResourceSwagger.java | 8 ++ .../service/LoanScheduleAssembler.java | 44 +++++++++-- .../LoanApplicationValidator.java | 3 +- .../serialization/LoanScheduleValidator.java | 17 +++- ...ntAllocationLoanRepaymentScheduleTest.java | 77 ++++++++++++++++++- .../loans/LoanApplicationTestBuilder.java | 18 +++++ 6 files changed, 153 insertions(+), 14 deletions(-) diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoansApiResourceSwagger.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoansApiResourceSwagger.java index 943303a86..9d6c85099 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoansApiResourceSwagger.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoansApiResourceSwagger.java @@ -1261,6 +1261,10 @@ final class LoansApiResourceSwagger { public Boolean enableInstallmentLevelDelinquency; @Schema(example = "false") public Boolean enableDownPayment; + @Schema(example = "0.000000") + public BigDecimal disbursedAmountPercentageForDownPayment; + @Schema(example = "false") + public Boolean enableAutoRepaymentForDownPayment; } @Schema(description = "PostLoansResponse") @@ -1412,6 +1416,10 @@ final class LoansApiResourceSwagger { public Boolean enableInstallmentLevelDelinquency; @Schema(example = "false") public Boolean enableDownPayment; + @Schema(example = "0.000000") + public BigDecimal disbursedAmountPercentageForDownPayment; + @Schema(example = "false") + public Boolean enableAutoRepaymentForDownPayment; static final class PutLoansLoanIdChanges { diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/service/LoanScheduleAssembler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/service/LoanScheduleAssembler.java index 51cf4edf4..dbed762c6 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/service/LoanScheduleAssembler.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/service/LoanScheduleAssembler.java @@ -476,10 +476,18 @@ public class LoanScheduleAssembler { BigDecimal disbursedAmountPercentageForDownPayment = null; boolean isAutoRepaymentForDownPaymentEnabled = false; if (isDownPaymentEnabled) { + isAutoRepaymentForDownPaymentEnabled = loanProduct.getLoanProductRelatedDetail().isEnableAutoRepaymentForDownPayment(); + if (this.fromApiJsonHelper.parameterExists(LoanProductConstants.ENABLE_AUTO_REPAYMENT_DOWN_PAYMENT, element)) { + isAutoRepaymentForDownPaymentEnabled = this.fromApiJsonHelper + .extractBooleanNamed(LoanProductConstants.ENABLE_AUTO_REPAYMENT_DOWN_PAYMENT, element); + } + disbursedAmountPercentageForDownPayment = loanProduct.getLoanProductRelatedDetail() .getDisbursedAmountPercentageForDownPayment(); - isAutoRepaymentForDownPaymentEnabled = loanProduct.getLoanProductRelatedDetail().isEnableAutoRepaymentForDownPayment(); - + if (this.fromApiJsonHelper.parameterExists(LoanProductConstants.DISBURSED_AMOUNT_PERCENTAGE_DOWN_PAYMENT, element)) { + disbursedAmountPercentageForDownPayment = this.fromApiJsonHelper + .extractBigDecimalWithLocaleNamed(LoanProductConstants.DISBURSED_AMOUNT_PERCENTAGE_DOWN_PAYMENT, element); + } } LoanScheduleType loanScheduleType = loanProduct.getLoanProductRelatedDetail().getLoanScheduleType(); @@ -1397,13 +1405,35 @@ public class LoanScheduleAssembler { if (!newValue) { loanProductRelatedDetail.setEnableAutoRepaymentForDownPayment(false); loanProductRelatedDetail.setDisbursedAmountPercentageForDownPayment(null); - } else { - loanProductRelatedDetail.setEnableAutoRepaymentForDownPayment( - loan.loanProduct().getLoanProductRelatedDetail().isEnableAutoRepaymentForDownPayment()); - loanProductRelatedDetail.setDisbursedAmountPercentageForDownPayment( - loan.loanProduct().getLoanProductRelatedDetail().getDisbursedAmountPercentageForDownPayment()); } } + + if (loanProductRelatedDetail.isEnableDownPayment()) { + Boolean enableAutoRepaymentForDownPayment = loan.loanProduct().getLoanProductRelatedDetail() + .isEnableAutoRepaymentForDownPayment(); + if (this.fromApiJsonHelper.parameterExists(LoanProductConstants.ENABLE_AUTO_REPAYMENT_DOWN_PAYMENT, command.parsedJson())) { + if (command.isChangeInBooleanParameterNamed(LoanProductConstants.ENABLE_AUTO_REPAYMENT_DOWN_PAYMENT, + loanProductRelatedDetail.isEnableAutoRepaymentForDownPayment())) { + enableAutoRepaymentForDownPayment = command + .booleanObjectValueOfParameterNamed(LoanProductConstants.ENABLE_AUTO_REPAYMENT_DOWN_PAYMENT); + changes.put(LoanProductConstants.ENABLE_AUTO_REPAYMENT_DOWN_PAYMENT, enableAutoRepaymentForDownPayment); + } + } + loanProductRelatedDetail.setEnableAutoRepaymentForDownPayment(enableAutoRepaymentForDownPayment); + + BigDecimal disbursedAmountPercentageDownPayment = loan.loanProduct().getLoanProductRelatedDetail() + .getDisbursedAmountPercentageForDownPayment(); + if (this.fromApiJsonHelper.parameterExists(LoanProductConstants.DISBURSED_AMOUNT_PERCENTAGE_DOWN_PAYMENT, + command.parsedJson())) { + if (command.isChangeInBigDecimalParameterNamed(LoanProductConstants.DISBURSED_AMOUNT_PERCENTAGE_DOWN_PAYMENT, + loanProductRelatedDetail.getDisbursedAmountPercentageForDownPayment())) { + disbursedAmountPercentageDownPayment = command + .bigDecimalValueOfParameterNamed(LoanProductConstants.DISBURSED_AMOUNT_PERCENTAGE_DOWN_PAYMENT); + changes.put(LoanProductConstants.DISBURSED_AMOUNT_PERCENTAGE_DOWN_PAYMENT, disbursedAmountPercentageDownPayment); + } + } + loanProductRelatedDetail.setDisbursedAmountPercentageForDownPayment(disbursedAmountPercentageDownPayment); + } } public Pair> assembleLoanApproval(AppUser currentUser, JsonCommand command, Long loanId) { diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/serialization/LoanApplicationValidator.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/serialization/LoanApplicationValidator.java index 55bd3d57a..2405288a8 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/serialization/LoanApplicationValidator.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/serialization/LoanApplicationValidator.java @@ -169,7 +169,8 @@ public final class LoanApplicationValidator { LoanApiConstants.daysInYearTypeParameterName, LoanApiConstants.fixedPrincipalPercentagePerInstallmentParamName, LoanApiConstants.DISALLOW_EXPECTED_DISBURSEMENTS, LoanApiConstants.FRAUD_ATTRIBUTE_NAME, LoanProductConstants.LOAN_SCHEDULE_PROCESSING_TYPE, LoanProductConstants.FIXED_LENGTH, - LoanProductConstants.ENABLE_INSTALLMENT_LEVEL_DELINQUENCY, LoanProductConstants.ENABLE_DOWN_PAYMENT)); + LoanProductConstants.ENABLE_INSTALLMENT_LEVEL_DELINQUENCY, LoanProductConstants.ENABLE_DOWN_PAYMENT, + LoanProductConstants.ENABLE_AUTO_REPAYMENT_DOWN_PAYMENT, LoanProductConstants.DISBURSED_AMOUNT_PERCENTAGE_DOWN_PAYMENT)); public static final String LOANAPPLICATION_UNDO = "loanapplication.undo"; private final FromJsonHelper fromApiJsonHelper; diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/serialization/LoanScheduleValidator.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/serialization/LoanScheduleValidator.java index 88c79f1aa..a7ee22d4e 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/serialization/LoanScheduleValidator.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/serialization/LoanScheduleValidator.java @@ -21,6 +21,7 @@ package org.apache.fineract.portfolio.loanaccount.serialization; import com.google.gson.JsonElement; import com.google.gson.reflect.TypeToken; import java.lang.reflect.Type; +import java.math.BigDecimal; import java.time.LocalDate; import java.util.ArrayList; import java.util.Arrays; @@ -73,7 +74,8 @@ public final class LoanScheduleValidator { LoanApiConstants.datatables, LoanApiConstants.isEqualAmortizationParam, LoanProductConstants.RATES_PARAM_NAME, LoanApiConstants.daysInYearTypeParameterName, LoanApiConstants.fixedPrincipalPercentagePerInstallmentParamName, LoanProductConstants.FIXED_LENGTH, LoanProductConstants.ENABLE_INSTALLMENT_LEVEL_DELINQUENCY, - LoanProductConstants.ENABLE_DOWN_PAYMENT)); + LoanProductConstants.ENABLE_DOWN_PAYMENT, LoanProductConstants.DISBURSED_AMOUNT_PERCENTAGE_DOWN_PAYMENT, + LoanProductConstants.ENABLE_AUTO_REPAYMENT_DOWN_PAYMENT)); private final FromJsonHelper fromApiJsonHelper; @@ -182,11 +184,18 @@ public final class LoanScheduleValidator { public void validateDownPaymentAttribute(final boolean isDownPaymentEnabledInLoanProduct, final JsonElement element) { final Boolean inputIsDownPaymentEnabled = this.fromApiJsonHelper.extractBooleanNamed(LoanProductConstants.ENABLE_DOWN_PAYMENT, element); - if (!isDownPaymentEnabledInLoanProduct && Boolean.TRUE.equals(inputIsDownPaymentEnabled)) { + final Boolean inputIsAutoDownPaymentEnabled = this.fromApiJsonHelper + .extractBooleanNamed(LoanProductConstants.ENABLE_AUTO_REPAYMENT_DOWN_PAYMENT, element); + final BigDecimal disbursedAmountPercentageDownPayment = this.fromApiJsonHelper + .extractBigDecimalWithLocaleNamed(LoanProductConstants.DISBURSED_AMOUNT_PERCENTAGE_DOWN_PAYMENT, element); + + if (!isDownPaymentEnabledInLoanProduct && (Boolean.TRUE.equals(inputIsDownPaymentEnabled) + || Boolean.TRUE.equals(inputIsAutoDownPaymentEnabled) || disbursedAmountPercentageDownPayment != null)) { throw new GeneralPlatformDomainRuleException("error.msg.downpayment.is.not.enabled.in.loan.product", - "The Loan can not override the downpayment flag because in the Loan Product the downpayment is disabled", - inputIsDownPaymentEnabled); + "The Loan can not override the downpayment properties because in the Loan Product the downpayment is disabled", + inputIsDownPaymentEnabled, inputIsAutoDownPaymentEnabled); } + } } diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/AdvancedPaymentAllocationLoanRepaymentScheduleTest.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/AdvancedPaymentAllocationLoanRepaymentScheduleTest.java index aa23b5072..acdb7d060 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/AdvancedPaymentAllocationLoanRepaymentScheduleTest.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/AdvancedPaymentAllocationLoanRepaymentScheduleTest.java @@ -4257,6 +4257,79 @@ public class AdvancedPaymentAllocationLoanRepaymentScheduleTest extends BaseLoan }); } + // UC139c: Advanced payment allocation, with DownPayment enabled in Loan Product and change the auto downpayment and + // percentage amount + // ADVANCED_PAYMENT_ALLOCATION_STRATEGY + // 1. Create a Loan product with Adv. Pment. Alloc. and DownPayment + // 2. Submit Loan with DownPayment and approve + // 3. Modify Loan Application change the auto downpayment and percentage amount + // 4. Disburse only 10 + // 5. Check the repayment schedule is correct, without DownPayment installment + @Test + public void uc139c() { + final String processDate = "23 March 2024"; + final Double amount = 1000.0; + runAt(processDate, () -> { + BigDecimal percentageAmount = BigDecimal.valueOf(25); + PostLoanProductsRequest product = createOnePeriod30DaysLongNoInterestPeriodicAccrualProductWithAdvancedPaymentAllocation() + .installmentAmountInMultiplesOf(null).numberOfRepayments(3).repaymentEvery(15).enableDownPayment(true) + .enableAutoRepaymentForDownPayment(true).disbursedAmountPercentageForDownPayment(percentageAmount); + PostLoanProductsResponse loanProductResponse = loanProductHelper.createLoanProduct(product); + PostLoansRequest applicationRequest = applyLoanRequest(client.getClientId(), loanProductResponse.getResourceId(), processDate, + amount, 4); + + applicationRequest = applicationRequest.numberOfRepayments(3).loanTermFrequency(45) + .transactionProcessingStrategyCode(LoanProductTestBuilder.ADVANCED_PAYMENT_ALLOCATION_STRATEGY).repaymentEvery(15) + .enableDownPayment(true); + + PostLoansResponse loanResponse = loanTransactionHelper.applyLoan(applicationRequest); + + GetLoansLoanIdResponse loanDetails = loanTransactionHelper.getLoanDetails(loanResponse.getLoanId()); + assertNotNull(loanDetails); + assertEquals(true, loanDetails.getEnableDownPayment()); + assertEquals(true, loanDetails.getEnableAutoRepaymentForDownPayment()); + + // verify schedule (with DownPayment and AutoDownPayment to 25%) + verifyRepaymentSchedule(loanResponse.getLoanId(), // + installment(1000, null, "23 March 2024"), // + installment(250.0, 0, 0, 0, 250.0, false, "23 March 2024", 750.0), // + installment(250.0, 0, 0, 0, 250.0, false, "07 April 2024", 500.0), // + installment(250.0, 0, 0, 0, 250.0, false, "22 April 2024", 250.0), // + installment(250.0, 0, 0, 0, 250.0, false, "07 May 2024", 0.0) // + ); + + percentageAmount = BigDecimal.valueOf(15); + loanTransactionHelper.modifyApplicationForLoan(loanResponse.getLoanId(), "modify", + new PutLoansLoanIdRequest().clientId(client.getClientId()).productId(loanProductResponse.getResourceId()) + .transactionProcessingStrategyCode(LoanProductTestBuilder.ADVANCED_PAYMENT_ALLOCATION_STRATEGY) + .repaymentEvery(15).interestCalculationPeriodType(1).interestType(0).expectedDisbursementDate(processDate) + .principal(amount.longValue()).repaymentFrequencyType(0).numberOfRepayments(3).loanTermFrequency(45) + .loanTermFrequencyType(0).enableDownPayment(true).loanType("individual").dateFormat(DATETIME_PATTERN) + .locale("en").enableAutoRepaymentForDownPayment(true) + .disbursedAmountPercentageForDownPayment(percentageAmount)); + + loanTransactionHelper.approveLoan(loanResponse.getLoanId(), new PostLoansLoanIdRequest() + .approvedLoanAmount(BigDecimal.valueOf(1000)).dateFormat(DATETIME_PATTERN).approvedOnDate(processDate).locale("en")); + + loanTransactionHelper.disburseLoan(loanResponse.getLoanId(), new PostLoansLoanIdRequest().actualDisbursementDate(processDate) + .dateFormat(DATETIME_PATTERN).transactionAmount(BigDecimal.valueOf(1000.0)).locale("en")); + + loanDetails = loanTransactionHelper.getLoanDetails(loanResponse.getLoanId()); + assertNotNull(loanDetails); + assertEquals(true, loanDetails.getEnableDownPayment()); + assertEquals(true, loanDetails.getEnableAutoRepaymentForDownPayment()); + + // verify schedule (with DownPayment and AutoDownPayment to 15%) + verifyRepaymentSchedule(loanResponse.getLoanId(), // + installment(1000, null, "23 March 2024"), // + installment(150, 0, 0, 0, 0.0, true, "23 March 2024", 850), // + installment(283.33, 0, 0, 0, 283.33, false, "07 April 2024", 566.67), // + installment(283.33, 0, 0, 0, 283.33, false, "22 April 2024", 283.34), // + installment(283.34, 0, 0, 0, 283.34, false, "07 May 2024", 0.0) // + ); + }); + } + // uc140: Negative Test: Advanced payment allocation, with DownPayment disabled in Loan Product but try to enable in // Loan application // ADVANCED_PAYMENT_ALLOCATION_STRATEGY @@ -4280,8 +4353,8 @@ public class AdvancedPaymentAllocationLoanRepaymentScheduleTest extends BaseLoan .transactionProcessingStrategyCode(LoanProductTestBuilder.ADVANCED_PAYMENT_ALLOCATION_STRATEGY) .repaymentEvery(15).enableDownPayment(true))); - Assertions.assertTrue(callFailedRuntimeException.getMessage() - .contains("The Loan can not override the downpayment flag because in the Loan Product the downpayment is disabled")); + Assertions.assertTrue(callFailedRuntimeException.getMessage().contains( + "The Loan can not override the downpayment properties because in the Loan Product the downpayment is disabled")); }); } diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/loans/LoanApplicationTestBuilder.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/loans/LoanApplicationTestBuilder.java index 879d71f52..469b06cb2 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/loans/LoanApplicationTestBuilder.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/loans/LoanApplicationTestBuilder.java @@ -86,6 +86,8 @@ public class LoanApplicationTestBuilder { private String inArrearsTolerance; private boolean createStandingInstructionAtDisbursement = false; private boolean enableDownPayment = false; + private boolean enableAutoRepaymentForDownPayment = false; + private String disbursedAmountPercentageDownPayment; public String build(final String clientID, final String groupID, final String loanProductId, final String savingsID) { final HashMap map = new HashMap<>(); @@ -212,6 +214,12 @@ public class LoanApplicationTestBuilder { if (enableDownPayment == true) { map.put("enableDownPayment", enableDownPayment); } + if (enableAutoRepaymentForDownPayment == true) { + map.put("enableAutoRepaymentForDownPayment", enableAutoRepaymentForDownPayment); + } + if (disbursedAmountPercentageDownPayment != null) { + map.put("disbursedAmountPercentageDownPayment", disbursedAmountPercentageDownPayment); + } LOG.info("Loan Application request : {} ", map); return new Gson().toJson(map); } @@ -450,4 +458,14 @@ public class LoanApplicationTestBuilder { return this; } + public LoanApplicationTestBuilder withEnableAutoRepaymentForDownPayment() { + this.enableAutoRepaymentForDownPayment = true; + return this; + } + + public LoanApplicationTestBuilder withDisbursedAmountPercentageDownPayment(final String amount) { + this.disbursedAmountPercentageDownPayment = amount; + return this; + } + }