Skip to content

Commit 870a781

Browse files
committed
Fix disbursement validation to respect over-applied loan product configuration
1 parent ef58439 commit 870a781

1 file changed

Lines changed: 47 additions & 16 deletions

File tree

fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/serialization/LoanDisbursementValidator.java

Lines changed: 47 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import org.apache.fineract.infrastructure.core.service.DateUtils;
2525
import org.apache.fineract.infrastructure.core.service.MathUtil;
2626
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
27+
import org.apache.fineract.portfolio.loanproduct.domain.LoanProduct;
2728
import org.apache.fineract.portfolio.loanaccount.exception.InvalidLoanStateTransitionException;
2829
import org.apache.fineract.portfolio.loanaccount.exception.LoanDisbursalException;
2930
import org.springframework.stereotype.Component;
@@ -39,29 +40,59 @@ public void compareDisbursedToApprovedOrProposedPrincipal(final Loan loan, final
3940
final BigDecimal totalCapitalizedIncomeAdjustment = MathUtil.nullToZero(loan.getSummary().getTotalCapitalizedIncomeAdjustment());
4041
final BigDecimal netCapitalizedIncome = totalCapitalizedIncome.subtract(totalCapitalizedIncomeAdjustment);
4142

42-
if (loan.loanProduct().isAllowApprovedDisbursedAmountsOverApplied()) {
43-
// Validate total disbursed amount (after this transaction) against max allowed
44-
validateOverMaximumAmount(loan, totalDisbursed, netCapitalizedIncome);
45-
} else {
46-
if ((totalDisbursed.compareTo(loan.getApprovedPrincipal()) > 0)
47-
|| (totalDisbursed.add(netCapitalizedIncome).compareTo(loan.getApprovedPrincipal()) > 0)) {
48-
final String errorMsg = "Loan can't be disbursed, disburse amount is exceeding approved principal.";
49-
throw new LoanDisbursalException(errorMsg, "disburse.amount.must.be.less.than.approved.principal", totalDisbursed,
50-
loan.getApprovedPrincipal());
43+
// Always use the same logic for over-applied config and fallback
44+
LoanProduct loanProduct = loan.getLoanProduct();
45+
BigDecimal approvedPrincipal = loan.getApprovedPrincipal();
46+
BigDecimal maxAllowed = approvedPrincipal;
47+
48+
if (loanProduct.getOverAppliedCalculationType() != null && loanProduct.getOverAppliedNumber() != null) {
49+
BigDecimal overAppliedNumber = BigDecimal.valueOf(loanProduct.getOverAppliedNumber());
50+
if ("percentage".equalsIgnoreCase(loanProduct.getOverAppliedCalculationType())) {
51+
BigDecimal extra = approvedPrincipal.multiply(overAppliedNumber).divide(BigDecimal.valueOf(100));
52+
maxAllowed = approvedPrincipal.add(extra);
53+
} else {
54+
// ABSOLUTE (flat)
55+
maxAllowed = approvedPrincipal.add(overAppliedNumber);
5156
}
5257
}
58+
59+
BigDecimal total = totalDisbursed.add(netCapitalizedIncome);
60+
if (maxAllowed == null) {
61+
maxAllowed = approvedPrincipal;
62+
}
63+
if (total.compareTo(maxAllowed) > 0) {
64+
throw new LoanDisbursalException(
65+
"Disbursement exceeds allowed limit including over-applied threshold",
66+
"disburse.amount.must.be.less.than.or.equal.to.max.allowed", total, maxAllowed);
67+
}
5368
}
5469

5570
public void validateOverMaximumAmount(final Loan loan, final BigDecimal totalDisbursed, final BigDecimal capitalizedIncome) {
56-
final BigDecimal maxDisbursedAmount = loanApplicationValidator.getOverAppliedMax(loan);
57-
if (totalDisbursed.add(capitalizedIncome).compareTo(maxDisbursedAmount) > 0) {
71+
LoanProduct loanProduct = loan.getLoanProduct();
72+
BigDecimal approvedPrincipal = loan.getApprovedPrincipal();
73+
BigDecimal maxAllowed = approvedPrincipal;
74+
75+
if (loanProduct.getOverAppliedCalculationType() != null && loanProduct.getOverAppliedNumber() != null) {
76+
BigDecimal overAppliedNumber = BigDecimal.valueOf(loanProduct.getOverAppliedNumber());
77+
if ("percentage".equalsIgnoreCase(loanProduct.getOverAppliedCalculationType())) {
78+
BigDecimal extra = approvedPrincipal.multiply(overAppliedNumber).divide(BigDecimal.valueOf(100));
79+
maxAllowed = approvedPrincipal.add(extra);
80+
} else {
81+
// ABSOLUTE (flat)
82+
maxAllowed = approvedPrincipal.add(overAppliedNumber);
83+
}
84+
}
85+
86+
BigDecimal total = totalDisbursed.add(capitalizedIncome);
87+
if (maxAllowed == null) {
88+
maxAllowed = approvedPrincipal;
89+
}
90+
if (total.compareTo(maxAllowed) > 0) {
5891
final String errorMessage = String.format(
59-
"Loan disbursal amount can't be greater than maximum applied loan amount calculation. "
60-
+ "Total disbursed amount: %s Maximum disbursal amount: %s",
61-
totalDisbursed.stripTrailingZeros().toPlainString(), maxDisbursedAmount.stripTrailingZeros().toPlainString());
92+
"Disbursement exceeds allowed limit including over-applied threshold. Total disbursed amount: %s Maximum allowed: %s",
93+
total.stripTrailingZeros().toPlainString(), maxAllowed.stripTrailingZeros().toPlainString());
6294
throw new InvalidLoanStateTransitionException("disbursal",
63-
"amount.can't.be.greater.than.maximum.applied.loan.amount.calculation", errorMessage, totalDisbursed,
64-
maxDisbursedAmount);
95+
"amount.can't.be.greater.than.maximum.applied.loan.amount.calculation", errorMessage, total, maxAllowed);
6596
}
6697
}
6798

0 commit comments

Comments
 (0)