FINERACT-2308: Allow approval and disbursal above loan applied amount with non multibursal loan product
This commit is contained in:
committed by
Adam Saghy
parent
38a5686df8
commit
bb29932658
+1
-1
@@ -410,7 +410,7 @@ public class LoanProduct extends AbstractPersistableCustom<Long> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (this.allowApprovedDisbursedAmountsOverApplied) {
|
if (this.allowApprovedDisbursedAmountsOverApplied) {
|
||||||
if (!this.disallowExpectedDisbursements) {
|
if (this.isMultiDisburseLoan() && !this.disallowExpectedDisbursements) {
|
||||||
throw new LoanProductGeneralRuleException(
|
throw new LoanProductGeneralRuleException(
|
||||||
"disallowExpectedDisbursements.not.set.allowApprovedDisbursedAmountsOverApplied.cant.be.set",
|
"disallowExpectedDisbursements.not.set.allowApprovedDisbursedAmountsOverApplied.cant.be.set",
|
||||||
"Disallow Expected Disbursals Not Set - Allow Approved / Disbursed Amounts Over Applied Can't Be Set");
|
"Disallow Expected Disbursals Not Set - Allow Approved / Disbursed Amounts Over Applied Can't Be Set");
|
||||||
|
|||||||
+1
-1
@@ -2124,7 +2124,7 @@ public final class LoanApplicationValidator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void compareApprovedToProposedPrincipal(Loan loan, BigDecimal approvedLoanAmount) {
|
private void compareApprovedToProposedPrincipal(Loan loan, BigDecimal approvedLoanAmount) {
|
||||||
if (loan.loanProduct().isDisallowExpectedDisbursements() && loan.loanProduct().isAllowApprovedDisbursedAmountsOverApplied()) {
|
if (loan.loanProduct().isAllowApprovedDisbursedAmountsOverApplied()) {
|
||||||
BigDecimal maxApprovedLoanAmount = getOverAppliedMax(loan);
|
BigDecimal maxApprovedLoanAmount = getOverAppliedMax(loan);
|
||||||
if (approvedLoanAmount.compareTo(maxApprovedLoanAmount) > 0) {
|
if (approvedLoanAmount.compareTo(maxApprovedLoanAmount) > 0) {
|
||||||
final String errorMessage = "Loan approved amount can't be greater than maximum applied loan amount calculation.";
|
final String errorMessage = "Loan approved amount can't be greater than maximum applied loan amount calculation.";
|
||||||
|
|||||||
+23
-15
@@ -37,26 +37,34 @@ public final class LoanDisbursementValidator {
|
|||||||
final BigDecimal totalDisbursed) {
|
final BigDecimal totalDisbursed) {
|
||||||
final BigDecimal capitalizedIncome = loan.getSummary().getTotalCapitalizedIncome();
|
final BigDecimal capitalizedIncome = loan.getSummary().getTotalCapitalizedIncome();
|
||||||
if (loan.loanProduct().isDisallowExpectedDisbursements() && loan.loanProduct().isAllowApprovedDisbursedAmountsOverApplied()) {
|
if (loan.loanProduct().isDisallowExpectedDisbursements() && loan.loanProduct().isAllowApprovedDisbursedAmountsOverApplied()) {
|
||||||
final BigDecimal maxDisbursedAmount = loanApplicationValidator.getOverAppliedMax(loan);
|
validateOverMaximumAmount(loan, totalDisbursed, capitalizedIncome);
|
||||||
if (totalDisbursed.add(capitalizedIncome).compareTo(maxDisbursedAmount) > 0) {
|
|
||||||
final String errorMessage = String.format(
|
|
||||||
"Loan disbursal amount can't be greater than maximum applied loan amount calculation. "
|
|
||||||
+ "Total disbursed amount: %s Maximum disbursal amount: %s",
|
|
||||||
totalDisbursed.stripTrailingZeros().toPlainString(), maxDisbursedAmount.stripTrailingZeros().toPlainString());
|
|
||||||
throw new InvalidLoanStateTransitionException("disbursal",
|
|
||||||
"amount.can't.be.greater.than.maximum.applied.loan.amount.calculation", errorMessage, disbursedAmount,
|
|
||||||
maxDisbursedAmount);
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
if ((totalDisbursed.compareTo(loan.getApprovedPrincipal()) > 0)
|
if (loan.loanProduct().isAllowApprovedDisbursedAmountsOverApplied()) {
|
||||||
|| (totalDisbursed.add(capitalizedIncome).compareTo(loan.getApprovedPrincipal()) > 0)) {
|
validateOverMaximumAmount(loan, disbursedAmount, capitalizedIncome);
|
||||||
final String errorMsg = "Loan can't be disbursed,disburse amount is exceeding approved principal ";
|
} else {
|
||||||
throw new LoanDisbursalException(errorMsg, "disburse.amount.must.be.less.than.approved.principal", totalDisbursed,
|
if ((totalDisbursed.compareTo(loan.getApprovedPrincipal()) > 0)
|
||||||
loan.getApprovedPrincipal());
|
|| (totalDisbursed.add(capitalizedIncome).compareTo(loan.getApprovedPrincipal()) > 0)) {
|
||||||
|
final String errorMsg = "Loan can't be disbursed,disburse amount is exceeding approved principal ";
|
||||||
|
throw new LoanDisbursalException(errorMsg, "disburse.amount.must.be.less.than.approved.principal", totalDisbursed,
|
||||||
|
loan.getApprovedPrincipal());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void validateOverMaximumAmount(final Loan loan, final BigDecimal totalDisbursed, final BigDecimal capitalizedIncome) {
|
||||||
|
final BigDecimal maxDisbursedAmount = loanApplicationValidator.getOverAppliedMax(loan);
|
||||||
|
if (totalDisbursed.add(capitalizedIncome).compareTo(maxDisbursedAmount) > 0) {
|
||||||
|
final String errorMessage = String.format(
|
||||||
|
"Loan disbursal amount can't be greater than maximum applied loan amount calculation. "
|
||||||
|
+ "Total disbursed amount: %s Maximum disbursal amount: %s",
|
||||||
|
totalDisbursed.stripTrailingZeros().toPlainString(), maxDisbursedAmount.stripTrailingZeros().toPlainString());
|
||||||
|
throw new InvalidLoanStateTransitionException("disbursal",
|
||||||
|
"amount.can't.be.greater.than.maximum.applied.loan.amount.calculation", errorMessage, totalDisbursed,
|
||||||
|
maxDisbursedAmount);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void validateDisburseAmountNotExceedingApprovedAmount(final Loan loan, final BigDecimal diff,
|
public void validateDisburseAmountNotExceedingApprovedAmount(final Loan loan, final BigDecimal diff,
|
||||||
final BigDecimal principalDisbursed) {
|
final BigDecimal principalDisbursed) {
|
||||||
if (!loan.loanProduct().isMultiDisburseLoan() && diff.compareTo(BigDecimal.ZERO) < 0) {
|
if (!loan.loanProduct().isMultiDisburseLoan() && diff.compareTo(BigDecimal.ZERO) < 0) {
|
||||||
|
|||||||
+2
-3
@@ -146,10 +146,10 @@ public class LoanDisbursementService {
|
|||||||
disbursementDetails.updatePrincipal(principalDisbursed);
|
disbursementDetails.updatePrincipal(principalDisbursed);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
BigDecimal totalAmount = BigDecimal.ZERO;
|
||||||
if (loan.loanProduct().isMultiDisburseLoan()) {
|
if (loan.loanProduct().isMultiDisburseLoan()) {
|
||||||
Collection<LoanDisbursementDetails> loanDisburseDetails = loan.getDisbursementDetails();
|
Collection<LoanDisbursementDetails> loanDisburseDetails = loan.getDisbursementDetails();
|
||||||
BigDecimal setPrincipalAmount = BigDecimal.ZERO;
|
BigDecimal setPrincipalAmount = BigDecimal.ZERO;
|
||||||
BigDecimal totalAmount = BigDecimal.ZERO;
|
|
||||||
for (LoanDisbursementDetails disbursementDetails : loanDisburseDetails) {
|
for (LoanDisbursementDetails disbursementDetails : loanDisburseDetails) {
|
||||||
if (disbursementDetails.actualDisbursementDate() != null) {
|
if (disbursementDetails.actualDisbursementDate() != null) {
|
||||||
setPrincipalAmount = setPrincipalAmount.add(disbursementDetails.principal());
|
setPrincipalAmount = setPrincipalAmount.add(disbursementDetails.principal());
|
||||||
@@ -157,12 +157,11 @@ public class LoanDisbursementService {
|
|||||||
totalAmount = totalAmount.add(disbursementDetails.principal());
|
totalAmount = totalAmount.add(disbursementDetails.principal());
|
||||||
}
|
}
|
||||||
loan.getLoanRepaymentScheduleDetail().setPrincipal(setPrincipalAmount);
|
loan.getLoanRepaymentScheduleDetail().setPrincipal(setPrincipalAmount);
|
||||||
loanDisbursementValidator.compareDisbursedToApprovedOrProposedPrincipal(loan, disburseAmount.getAmount(), totalAmount);
|
|
||||||
} else {
|
} else {
|
||||||
loan.getLoanRepaymentScheduleDetail()
|
loan.getLoanRepaymentScheduleDetail()
|
||||||
.setPrincipal(loan.getLoanRepaymentScheduleDetail().getPrincipal().minus(diff).getAmount());
|
.setPrincipal(loan.getLoanRepaymentScheduleDetail().getPrincipal().minus(diff).getAmount());
|
||||||
}
|
}
|
||||||
loanDisbursementValidator.validateDisburseAmountNotExceedingApprovedAmount(loan, diff, principalDisbursed);
|
loanDisbursementValidator.compareDisbursedToApprovedOrProposedPrincipal(loan, disburseAmount.getAmount(), totalAmount);
|
||||||
}
|
}
|
||||||
return disburseAmount;
|
return disburseAmount;
|
||||||
}
|
}
|
||||||
|
|||||||
+55
@@ -5946,6 +5946,61 @@ public class AdvancedPaymentAllocationLoanRepaymentScheduleTest extends BaseLoan
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// UC155: Validate allowApprovedDisbursedAmountsOverApplied setting on Non MultiDisbursement Loan Product
|
||||||
|
// 1. Create a Loan product with allowApprovedDisbursedAmountsOverApplied in true using overAppliedCalculationType
|
||||||
|
// flat amount
|
||||||
|
// 2. Submit Loan with 1,000
|
||||||
|
// 3. Approve the Loan with 1,300
|
||||||
|
// 4. Disburse the Loan with 1,450
|
||||||
|
@Test
|
||||||
|
public void uc155() {
|
||||||
|
final String operationDate = "1 January 2024";
|
||||||
|
AtomicLong createdLoanId = new AtomicLong();
|
||||||
|
runAt(operationDate, () -> {
|
||||||
|
Long clientId = clientHelper.createClient(ClientHelper.defaultClientCreationRequest()).getClientId();
|
||||||
|
PostLoanProductsRequest product = createOnePeriod30DaysLongNoInterestPeriodicAccrualProductWithAdvancedPaymentAllocation()
|
||||||
|
.interestRatePerPeriod(12.0).interestCalculationPeriodType(RepaymentFrequencyType.DAYS).numberOfRepayments(4)//
|
||||||
|
.repaymentEvery(1)//
|
||||||
|
.repaymentFrequencyType(1L)//
|
||||||
|
.allowPartialPeriodInterestCalcualtion(false)//
|
||||||
|
.installmentAmountInMultiplesOf(null)//
|
||||||
|
.multiDisburseLoan(false)//
|
||||||
|
.disallowExpectedDisbursements(null)//
|
||||||
|
.allowApprovedDisbursedAmountsOverApplied(true)//
|
||||||
|
.overAppliedCalculationType("flat")//
|
||||||
|
.overAppliedNumber(500)//
|
||||||
|
;//
|
||||||
|
PostLoanProductsResponse loanProductResponse = loanProductHelper.createLoanProduct(product);
|
||||||
|
PostLoansRequest applicationRequest = applyLoanRequest(clientId, loanProductResponse.getResourceId(), operationDate, 1000.0, 4)
|
||||||
|
.transactionProcessingStrategyCode("advanced-payment-allocation-strategy")//
|
||||||
|
.interestRatePerPeriod(BigDecimal.valueOf(12.0));
|
||||||
|
|
||||||
|
PostLoansResponse loanResponse = loanTransactionHelper.applyLoan(applicationRequest);
|
||||||
|
createdLoanId.set(loanResponse.getLoanId());
|
||||||
|
|
||||||
|
loanTransactionHelper.approveLoan(loanResponse.getLoanId(), new PostLoansLoanIdRequest()
|
||||||
|
.approvedLoanAmount(BigDecimal.valueOf(1300)).dateFormat(DATETIME_PATTERN).approvedOnDate(operationDate).locale("en"));
|
||||||
|
GetLoansLoanIdResponse loanDetails = loanTransactionHelper.getLoanDetails(createdLoanId.get());
|
||||||
|
assertTrue(loanDetails.getStatus().getWaitingForDisbursal());
|
||||||
|
assertEquals("1300.000000", loanDetails.getApprovedPrincipal().toString());
|
||||||
|
|
||||||
|
CallFailedRuntimeException callFailedRuntimeException = Assertions.assertThrows(CallFailedRuntimeException.class,
|
||||||
|
() -> loanTransactionHelper.disburseLoan(loanResponse.getLoanId(),
|
||||||
|
new PostLoansLoanIdRequest().actualDisbursementDate(operationDate).dateFormat(DATETIME_PATTERN).locale("en")
|
||||||
|
.transactionAmount(BigDecimal.valueOf(1550.0))));
|
||||||
|
|
||||||
|
Assertions.assertTrue(callFailedRuntimeException.getMessage()
|
||||||
|
.contains("Loan disbursal amount can't be greater than maximum applied loan amount calculation"));
|
||||||
|
|
||||||
|
loanTransactionHelper.disburseLoan(loanResponse.getLoanId(), new PostLoansLoanIdRequest().actualDisbursementDate(operationDate)
|
||||||
|
.dateFormat(DATETIME_PATTERN).locale("en").transactionAmount(BigDecimal.valueOf(1450.0)));
|
||||||
|
|
||||||
|
loanDetails = loanTransactionHelper.getLoanDetails(createdLoanId.get());
|
||||||
|
assertTrue(loanDetails.getStatus().getActive());
|
||||||
|
assertEquals("1450.000000", loanDetails.getSummary().getPrincipalOutstanding().toString());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
private Long applyAndApproveLoanProgressiveAdvancedPaymentAllocationStrategyMonthlyRepayments(Long clientId, Long loanProductId,
|
private Long applyAndApproveLoanProgressiveAdvancedPaymentAllocationStrategyMonthlyRepayments(Long clientId, Long loanProductId,
|
||||||
Integer numberOfRepayments, String loanDisbursementDate, double amount) {
|
Integer numberOfRepayments, String loanDisbursementDate, double amount) {
|
||||||
LOG.info("------------------------------APPLY AND APPROVE LOAN ---------------------------------------");
|
LOG.info("------------------------------APPLY AND APPROVE LOAN ---------------------------------------");
|
||||||
|
|||||||
Reference in New Issue
Block a user