FINERACT-2102: Loan Product enableDownPayment overrided in the Loan application

This commit is contained in:
Jose Alberto Hernandez
2024-07-08 10:29:02 +02:00
committed by Adam Saghy
parent f5add87f2a
commit 5093ca7363
6 changed files with 225 additions and 3 deletions
@@ -1245,6 +1245,8 @@ final class LoansApiResourceSwagger {
public String loanScheduleProcessingType;
@Schema(example = "false")
public Boolean enableInstallmentLevelDelinquency;
@Schema(example = "false")
public Boolean enableDownPayment;
}
@Schema(description = "PostLoansResponse")
@@ -1394,6 +1396,8 @@ final class LoansApiResourceSwagger {
public String loanScheduleProcessingType;
@Schema(example = "false")
public Boolean enableInstallmentLevelDelinquency;
@Schema(example = "false")
public Boolean enableDownPayment;
static final class PutLoansLoanIdChanges {
@@ -466,7 +466,11 @@ public class LoanScheduleAssembler {
final boolean isPrincipalCompoundingDisabledForOverdueLoans = this.configurationDomainService
.isPrincipalCompoundingDisabledForOverdueLoans();
final boolean isDownPaymentEnabled = loanProduct.getLoanProductRelatedDetail().isEnableDownPayment();
boolean isDownPaymentEnabled = loanProduct.getLoanProductRelatedDetail().isEnableDownPayment();
if (this.fromApiJsonHelper.parameterExists(LoanProductConstants.ENABLE_DOWN_PAYMENT, element)) {
isDownPaymentEnabled = this.fromApiJsonHelper.extractBooleanNamed(LoanProductConstants.ENABLE_DOWN_PAYMENT, element);
}
BigDecimal disbursedAmountPercentageForDownPayment = null;
boolean isAutoRepaymentForDownPaymentEnabled = false;
if (isDownPaymentEnabled) {
@@ -1379,6 +1383,22 @@ public class LoanScheduleAssembler {
changes.put(LoanProductConstants.IS_EQUAL_AMORTIZATION_PARAM, newValue);
loanProductRelatedDetail.setEqualAmortization(newValue);
}
if (command.isChangeInBooleanParameterNamed(LoanProductConstants.ENABLE_DOWN_PAYMENT,
loanProductRelatedDetail.isEnableDownPayment())) {
final boolean newValue = command.booleanPrimitiveValueOfParameterNamed(LoanProductConstants.ENABLE_DOWN_PAYMENT);
changes.put(LoanProductConstants.ENABLE_DOWN_PAYMENT, newValue);
loanProductRelatedDetail.setEnableDownPayment(newValue);
if (!newValue) {
loanProductRelatedDetail.setEnableAutoRepaymentForDownPayment(false);
loanProductRelatedDetail.setDisbursedAmountPercentageForDownPayment(null);
} else {
loanProductRelatedDetail.setEnableAutoRepaymentForDownPayment(
loan.loanProduct().getLoanProductRelatedDetail().isEnableAutoRepaymentForDownPayment());
loanProductRelatedDetail.setDisbursedAmountPercentageForDownPayment(
loan.loanProduct().getLoanProductRelatedDetail().getDisbursedAmountPercentageForDownPayment());
}
}
}
public Pair<Loan, Map<String, Object>> assembleLoanApproval(AppUser currentUser, JsonCommand command, Long loanId) {
@@ -1446,4 +1466,5 @@ public class LoanScheduleAssembler {
return Pair.of(loan, actualChanges);
}
}
@@ -169,7 +169,7 @@ 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_INSTALLMENT_LEVEL_DELINQUENCY, LoanProductConstants.ENABLE_DOWN_PAYMENT));
public static final String LOANAPPLICATION_UNDO = "loanapplication.undo";
private final FromJsonHelper fromApiJsonHelper;
@@ -741,6 +741,8 @@ public final class LoanApplicationValidator {
}
}
loanScheduleValidator.validateDownPaymentAttribute(loanProduct.getLoanProductRelatedDetail().isEnableDownPayment(), element);
checkForProductMixRestrictions(element);
validateSubmittedOnDate(element, submittedOnDate, loanProduct);
validateDisbursementDetails(loanProduct, element);
@@ -1396,6 +1398,8 @@ public final class LoanApplicationValidator {
}
}
loanScheduleValidator.validateDownPaymentAttribute(loanProduct.getLoanProductRelatedDetail().isEnableDownPayment(), element);
validateDisbursementDetails(loanProduct, element);
validateSubmittedOnDate(element, loan.getSubmittedOnDate(), loanProduct);
validateClientOrGroup(client, group, productId);
@@ -30,6 +30,7 @@ import java.util.Map;
import java.util.Set;
import org.apache.commons.lang3.StringUtils;
import org.apache.fineract.infrastructure.core.data.ApiParameterError;
import org.apache.fineract.infrastructure.core.exception.GeneralPlatformDomainRuleException;
import org.apache.fineract.infrastructure.core.exception.InvalidJsonException;
import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
@@ -71,7 +72,8 @@ public final class LoanScheduleValidator {
LoanApiConstants.repaymentFrequencyDayOfWeekTypeParameterName, LoanApiConstants.isTopup, LoanApiConstants.loanIdToClose,
LoanApiConstants.datatables, LoanApiConstants.isEqualAmortizationParam, LoanProductConstants.RATES_PARAM_NAME,
LoanApiConstants.daysInYearTypeParameterName, LoanApiConstants.fixedPrincipalPercentagePerInstallmentParamName,
LoanProductConstants.FIXED_LENGTH, LoanProductConstants.ENABLE_INSTALLMENT_LEVEL_DELINQUENCY));
LoanProductConstants.FIXED_LENGTH, LoanProductConstants.ENABLE_INSTALLMENT_LEVEL_DELINQUENCY,
LoanProductConstants.ENABLE_DOWN_PAYMENT));
private final FromJsonHelper fromApiJsonHelper;
@@ -176,4 +178,15 @@ public final class LoanScheduleValidator {
dataValidationErrors.add(error);
}
}
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)) {
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);
}
}
}
@@ -57,6 +57,7 @@ import org.apache.fineract.client.models.PostLoansLoanIdTransactionsTransactionI
import org.apache.fineract.client.models.PostLoansRequest;
import org.apache.fineract.client.models.PostLoansResponse;
import org.apache.fineract.client.models.PostUpdateRescheduleLoansRequest;
import org.apache.fineract.client.models.PutLoansLoanIdRequest;
import org.apache.fineract.client.util.CallFailedRuntimeException;
import org.apache.fineract.infrastructure.businessdate.domain.BusinessDateType;
import org.apache.fineract.integrationtests.common.BusinessDateHelper;
@@ -77,6 +78,7 @@ import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.imp
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleProcessingType;
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleType;
import org.apache.fineract.portfolio.loanproduct.domain.PaymentAllocationType;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
@@ -4113,6 +4115,174 @@ public class AdvancedPaymentAllocationLoanRepaymentScheduleTest extends BaseLoan
});
}
// UC138: Advanced payment allocation, with DownPayment enabled in Loan Product but disabled in Loan application
// ADVANCED_PAYMENT_ALLOCATION_STRATEGY
// 1. Create a Loan product with Adv. Pment. Alloc. and DownPayment
// 2. Submit Loan without DownPayment and approve
// 3. Disburse only 10
// 4. Check the repayment schedule is correct, withoutDownPayment installment
@Test
public void uc138() {
runAt("23 March 2024", () -> {
PostLoanProductsRequest product = createOnePeriod30DaysLongNoInterestPeriodicAccrualProductWithAdvancedPaymentAllocation()
.installmentAmountInMultiplesOf(null).numberOfRepayments(3).repaymentEvery(15).enableDownPayment(true)
.enableAutoRepaymentForDownPayment(true).disbursedAmountPercentageForDownPayment(BigDecimal.valueOf(25));
PostLoanProductsResponse loanProductResponse = loanProductHelper.createLoanProduct(product);
PostLoansRequest applicationRequest = applyLoanRequest(client.getClientId(), loanProductResponse.getResourceId(),
"23 March 2024", 1000.0, 4);
applicationRequest = applicationRequest.numberOfRepayments(3).loanTermFrequency(45)
.transactionProcessingStrategyCode(LoanProductTestBuilder.ADVANCED_PAYMENT_ALLOCATION_STRATEGY).repaymentEvery(15)
.enableDownPayment(false);
PostLoansResponse loanResponse = loanTransactionHelper.applyLoan(applicationRequest);
loanTransactionHelper.approveLoan(loanResponse.getLoanId(), new PostLoansLoanIdRequest()
.approvedLoanAmount(BigDecimal.valueOf(10)).dateFormat(DATETIME_PATTERN).approvedOnDate("23 March 2024").locale("en"));
loanTransactionHelper.disburseLoan(loanResponse.getLoanId(),
new PostLoansLoanIdRequest().actualDisbursementDate("23 March 2024").dateFormat(DATETIME_PATTERN)
.transactionAmount(BigDecimal.valueOf(10.0)).locale("en"));
// verify schedule (without DownPayment)
verifyRepaymentSchedule(loanResponse.getLoanId(), //
installment(10, null, "23 March 2024"), //
installment(3.33, 0, 0, 0, 3.33, false, "07 April 2024", 6.67), //
installment(3.33, 0, 0, 0, 3.33, false, "22 April 2024", 3.34), //
installment(3.34, 0, 0, 0, 3.34, false, "07 May 2024", 0.0) //
);
});
}
// UC139a: Advanced payment allocation, with DownPayment enabled in Loan Product but disabled and enabled in Loan
// application
// ADVANCED_PAYMENT_ALLOCATION_STRATEGY
// 1. Create a Loan product with Adv. Pment. Alloc. and DownPayment
// 2. Submit Loan without DownPayment and approve
// 3. Modify Loan Application to enable DownPayment
// 4. Disburse only 10
// 5. Check the repayment schedule is correct, with DownPayment installment
@Test
public void uc139a() {
final String processDate = "23 March 2024";
final Double amount = 1000.0;
runAt(processDate, () -> {
PostLoanProductsRequest product = createOnePeriod30DaysLongNoInterestPeriodicAccrualProductWithAdvancedPaymentAllocation()
.installmentAmountInMultiplesOf(null).numberOfRepayments(3).repaymentEvery(15).enableDownPayment(true)
.enableAutoRepaymentForDownPayment(true).disbursedAmountPercentageForDownPayment(BigDecimal.valueOf(25));
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(false);
PostLoansResponse loanResponse = loanTransactionHelper.applyLoan(applicationRequest);
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"));
loanTransactionHelper.approveLoan(loanResponse.getLoanId(), new PostLoansLoanIdRequest()
.approvedLoanAmount(BigDecimal.valueOf(10)).dateFormat(DATETIME_PATTERN).approvedOnDate(processDate).locale("en"));
loanTransactionHelper.disburseLoan(loanResponse.getLoanId(), new PostLoansLoanIdRequest().actualDisbursementDate(processDate)
.dateFormat(DATETIME_PATTERN).transactionAmount(BigDecimal.valueOf(10.0)).locale("en"));
// verify schedule (with DownPayment)
verifyRepaymentSchedule(loanResponse.getLoanId(), //
installment(10, null, "23 March 2024"), //
installment(2.5, 0, 0, 0, 0.0, true, "23 March 2024", 7.5), //
installment(2.5, 0, 0, 0, 2.5, false, "07 April 2024", 5.0), //
installment(2.5, 0, 0, 0, 2.5, false, "22 April 2024", 2.5), //
installment(2.5, 0, 0, 0, 2.5, false, "07 May 2024", 0.0) //
);
});
}
// UC139b: Advanced payment allocation, with DownPayment enabled in Loan Product and enabled then disabled in Loan
// application
// 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 to disable DownPayment
// 4. Disburse only 10
// 5. Check the repayment schedule is correct, without DownPayment installment
@Test
public void uc139b() {
final String processDate = "23 March 2024";
final Double amount = 1000.0;
runAt(processDate, () -> {
PostLoanProductsRequest product = createOnePeriod30DaysLongNoInterestPeriodicAccrualProductWithAdvancedPaymentAllocation()
.installmentAmountInMultiplesOf(null).numberOfRepayments(3).repaymentEvery(15).enableDownPayment(true)
.enableAutoRepaymentForDownPayment(true).disbursedAmountPercentageForDownPayment(BigDecimal.valueOf(25));
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);
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(false).loanType("individual").dateFormat(DATETIME_PATTERN)
.locale("en"));
loanTransactionHelper.approveLoan(loanResponse.getLoanId(), new PostLoansLoanIdRequest()
.approvedLoanAmount(BigDecimal.valueOf(10)).dateFormat(DATETIME_PATTERN).approvedOnDate(processDate).locale("en"));
loanTransactionHelper.disburseLoan(loanResponse.getLoanId(), new PostLoansLoanIdRequest().actualDisbursementDate(processDate)
.dateFormat(DATETIME_PATTERN).transactionAmount(BigDecimal.valueOf(10.0)).locale("en"));
// verify schedule (without DownPayment)
verifyRepaymentSchedule(loanResponse.getLoanId(), //
installment(10, null, "23 March 2024"), //
installment(3.33, 0, 0, 0, 3.33, false, "07 April 2024", 6.67), //
installment(3.33, 0, 0, 0, 3.33, false, "22 April 2024", 3.34), //
installment(3.34, 0, 0, 0, 3.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
// 1. Create a Loan product with Adv. Pment. Alloc. and without DownPayment
// 2. Try to summit the Loan Application with DownPayment expecting and Exception
@Test
public void uc140() {
runAt("23 March 2024", () -> {
PostLoanProductsRequest product = createOnePeriod30DaysLongNoInterestPeriodicAccrualProductWithAdvancedPaymentAllocation()
.installmentAmountInMultiplesOf(null).numberOfRepayments(3).repaymentEvery(15).enableDownPayment(false);
PostLoanProductsResponse loanProductResponse = loanProductHelper.createLoanProduct(product);
PostLoansRequest applicationRequest = applyLoanRequest(client.getClientId(), loanProductResponse.getResourceId(),
"23 March 2024", 1000.0, 3)
.transactionProcessingStrategyCode(LoanProductTestBuilder.ADVANCED_PAYMENT_ALLOCATION_STRATEGY).repaymentEvery(15)
.enableDownPayment(true);
Long clientId = client.getClientId();
CallFailedRuntimeException callFailedRuntimeException = Assertions.assertThrows(CallFailedRuntimeException.class,
() -> loanTransactionHelper
.applyLoan(applyLoanRequest(clientId, loanProductResponse.getResourceId(), "23 March 2024", 1000.0, 3)
.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"));
});
}
@Test
public void testChargebackTransactionForProgressiveWithLoanAdvancedPaymentAllocationStrategyAndChargebackAllocation() {
runAt("15 January 2023", () -> {
@@ -85,6 +85,7 @@ public class LoanApplicationTestBuilder {
private String linkAccountId;
private String inArrearsTolerance;
private boolean createStandingInstructionAtDisbursement = false;
private boolean enableDownPayment = false;
public String build(final String clientID, final String groupID, final String loanProductId, final String savingsID) {
final HashMap<String, Object> map = new HashMap<>();
@@ -208,6 +209,9 @@ public class LoanApplicationTestBuilder {
if (createStandingInstructionAtDisbursement == true) {
map.put("createStandingInstructionAtDisbursement", true);
}
if (enableDownPayment == true) {
map.put("enableDownPayment", enableDownPayment);
}
LOG.info("Loan Application request : {} ", map);
return new Gson().toJson(map);
}
@@ -440,4 +444,10 @@ public class LoanApplicationTestBuilder {
this.createStandingInstructionAtDisbursement = true;
return this;
}
public LoanApplicationTestBuilder withEnableDownPayment() {
this.enableDownPayment = true;
return this;
}
}