FINERACT-2102: Loan Product DownPayment attributes overrided in the Loan

This commit is contained in:
Jose Alberto Hernandez
2024-07-15 10:33:11 +02:00
committed by Adam Saghy
parent 9fceaf98b3
commit 319f457874
6 changed files with 153 additions and 14 deletions
@@ -1261,6 +1261,10 @@ final class LoansApiResourceSwagger {
public Boolean enableInstallmentLevelDelinquency; public Boolean enableInstallmentLevelDelinquency;
@Schema(example = "false") @Schema(example = "false")
public Boolean enableDownPayment; public Boolean enableDownPayment;
@Schema(example = "0.000000")
public BigDecimal disbursedAmountPercentageForDownPayment;
@Schema(example = "false")
public Boolean enableAutoRepaymentForDownPayment;
} }
@Schema(description = "PostLoansResponse") @Schema(description = "PostLoansResponse")
@@ -1412,6 +1416,10 @@ final class LoansApiResourceSwagger {
public Boolean enableInstallmentLevelDelinquency; public Boolean enableInstallmentLevelDelinquency;
@Schema(example = "false") @Schema(example = "false")
public Boolean enableDownPayment; public Boolean enableDownPayment;
@Schema(example = "0.000000")
public BigDecimal disbursedAmountPercentageForDownPayment;
@Schema(example = "false")
public Boolean enableAutoRepaymentForDownPayment;
static final class PutLoansLoanIdChanges { static final class PutLoansLoanIdChanges {
@@ -476,10 +476,18 @@ public class LoanScheduleAssembler {
BigDecimal disbursedAmountPercentageForDownPayment = null; BigDecimal disbursedAmountPercentageForDownPayment = null;
boolean isAutoRepaymentForDownPaymentEnabled = false; boolean isAutoRepaymentForDownPaymentEnabled = false;
if (isDownPaymentEnabled) { 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() disbursedAmountPercentageForDownPayment = loanProduct.getLoanProductRelatedDetail()
.getDisbursedAmountPercentageForDownPayment(); .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(); LoanScheduleType loanScheduleType = loanProduct.getLoanProductRelatedDetail().getLoanScheduleType();
@@ -1397,13 +1405,35 @@ public class LoanScheduleAssembler {
if (!newValue) { if (!newValue) {
loanProductRelatedDetail.setEnableAutoRepaymentForDownPayment(false); loanProductRelatedDetail.setEnableAutoRepaymentForDownPayment(false);
loanProductRelatedDetail.setDisbursedAmountPercentageForDownPayment(null); 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<Loan, Map<String, Object>> assembleLoanApproval(AppUser currentUser, JsonCommand command, Long loanId) { public Pair<Loan, Map<String, Object>> assembleLoanApproval(AppUser currentUser, JsonCommand command, Long loanId) {
@@ -169,7 +169,8 @@ public final class LoanApplicationValidator {
LoanApiConstants.daysInYearTypeParameterName, LoanApiConstants.fixedPrincipalPercentagePerInstallmentParamName, LoanApiConstants.daysInYearTypeParameterName, LoanApiConstants.fixedPrincipalPercentagePerInstallmentParamName,
LoanApiConstants.DISALLOW_EXPECTED_DISBURSEMENTS, LoanApiConstants.FRAUD_ATTRIBUTE_NAME, LoanApiConstants.DISALLOW_EXPECTED_DISBURSEMENTS, LoanApiConstants.FRAUD_ATTRIBUTE_NAME,
LoanProductConstants.LOAN_SCHEDULE_PROCESSING_TYPE, LoanProductConstants.FIXED_LENGTH, 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"; public static final String LOANAPPLICATION_UNDO = "loanapplication.undo";
private final FromJsonHelper fromApiJsonHelper; private final FromJsonHelper fromApiJsonHelper;
@@ -21,6 +21,7 @@ package org.apache.fineract.portfolio.loanaccount.serialization;
import com.google.gson.JsonElement; import com.google.gson.JsonElement;
import com.google.gson.reflect.TypeToken; import com.google.gson.reflect.TypeToken;
import java.lang.reflect.Type; import java.lang.reflect.Type;
import java.math.BigDecimal;
import java.time.LocalDate; import java.time.LocalDate;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
@@ -73,7 +74,8 @@ public final class LoanScheduleValidator {
LoanApiConstants.datatables, LoanApiConstants.isEqualAmortizationParam, LoanProductConstants.RATES_PARAM_NAME, LoanApiConstants.datatables, LoanApiConstants.isEqualAmortizationParam, LoanProductConstants.RATES_PARAM_NAME,
LoanApiConstants.daysInYearTypeParameterName, LoanApiConstants.fixedPrincipalPercentagePerInstallmentParamName, LoanApiConstants.daysInYearTypeParameterName, LoanApiConstants.fixedPrincipalPercentagePerInstallmentParamName,
LoanProductConstants.FIXED_LENGTH, LoanProductConstants.ENABLE_INSTALLMENT_LEVEL_DELINQUENCY, 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; private final FromJsonHelper fromApiJsonHelper;
@@ -182,11 +184,18 @@ public final class LoanScheduleValidator {
public void validateDownPaymentAttribute(final boolean isDownPaymentEnabledInLoanProduct, final JsonElement element) { public void validateDownPaymentAttribute(final boolean isDownPaymentEnabledInLoanProduct, final JsonElement element) {
final Boolean inputIsDownPaymentEnabled = this.fromApiJsonHelper.extractBooleanNamed(LoanProductConstants.ENABLE_DOWN_PAYMENT, final Boolean inputIsDownPaymentEnabled = this.fromApiJsonHelper.extractBooleanNamed(LoanProductConstants.ENABLE_DOWN_PAYMENT,
element); 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", 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", "The Loan can not override the downpayment properties because in the Loan Product the downpayment is disabled",
inputIsDownPaymentEnabled); inputIsDownPaymentEnabled, inputIsAutoDownPaymentEnabled);
} }
} }
} }
@@ -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 // uc140: Negative Test: Advanced payment allocation, with DownPayment disabled in Loan Product but try to enable in
// Loan application // Loan application
// ADVANCED_PAYMENT_ALLOCATION_STRATEGY // ADVANCED_PAYMENT_ALLOCATION_STRATEGY
@@ -4280,8 +4353,8 @@ public class AdvancedPaymentAllocationLoanRepaymentScheduleTest extends BaseLoan
.transactionProcessingStrategyCode(LoanProductTestBuilder.ADVANCED_PAYMENT_ALLOCATION_STRATEGY) .transactionProcessingStrategyCode(LoanProductTestBuilder.ADVANCED_PAYMENT_ALLOCATION_STRATEGY)
.repaymentEvery(15).enableDownPayment(true))); .repaymentEvery(15).enableDownPayment(true)));
Assertions.assertTrue(callFailedRuntimeException.getMessage() Assertions.assertTrue(callFailedRuntimeException.getMessage().contains(
.contains("The Loan can not override the downpayment flag because in the Loan Product the downpayment is disabled")); "The Loan can not override the downpayment properties because in the Loan Product the downpayment is disabled"));
}); });
} }
@@ -86,6 +86,8 @@ public class LoanApplicationTestBuilder {
private String inArrearsTolerance; private String inArrearsTolerance;
private boolean createStandingInstructionAtDisbursement = false; private boolean createStandingInstructionAtDisbursement = false;
private boolean enableDownPayment = 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) { public String build(final String clientID, final String groupID, final String loanProductId, final String savingsID) {
final HashMap<String, Object> map = new HashMap<>(); final HashMap<String, Object> map = new HashMap<>();
@@ -212,6 +214,12 @@ public class LoanApplicationTestBuilder {
if (enableDownPayment == true) { if (enableDownPayment == true) {
map.put("enableDownPayment", enableDownPayment); map.put("enableDownPayment", enableDownPayment);
} }
if (enableAutoRepaymentForDownPayment == true) {
map.put("enableAutoRepaymentForDownPayment", enableAutoRepaymentForDownPayment);
}
if (disbursedAmountPercentageDownPayment != null) {
map.put("disbursedAmountPercentageDownPayment", disbursedAmountPercentageDownPayment);
}
LOG.info("Loan Application request : {} ", map); LOG.info("Loan Application request : {} ", map);
return new Gson().toJson(map); return new Gson().toJson(map);
} }
@@ -450,4 +458,14 @@ public class LoanApplicationTestBuilder {
return this; return this;
} }
public LoanApplicationTestBuilder withEnableAutoRepaymentForDownPayment() {
this.enableAutoRepaymentForDownPayment = true;
return this;
}
public LoanApplicationTestBuilder withDisbursedAmountPercentageDownPayment(final String amount) {
this.disbursedAmountPercentageDownPayment = amount;
return this;
}
} }