FINERACT-2181: Fix validation for add capitalized income to loan transaction

This commit is contained in:
mariiaKraievska
2025-06-22 22:35:36 +02:00
committed by Adam Saghy
parent a76c189a04
commit e4ab6110ff
3 changed files with 59 additions and 8 deletions
@@ -2385,6 +2385,9 @@ public class LoanProductGlobalInitializerStep implements FineractGlobalInitializ
.name(name97)//
.transactionProcessingStrategyCode(ADVANCED_PAYMENT_ALLOCATION.getValue())//
.loanScheduleType("PROGRESSIVE") //
.allowApprovedDisbursedAmountsOverApplied(false)//
.overAppliedCalculationType(null)//
.overAppliedNumber(null)//
.paymentAllocation(List.of(//
createPaymentAllocation("DEFAULT", "NEXT_INSTALLMENT"), //
createPaymentAllocation("GOODWILL_CREDIT", "LAST_INSTALLMENT"), //
@@ -6994,3 +6994,31 @@ Feature: Capitalized Income
When Customer undo "1"th "Capitalized Income" transaction made on "01 January 2024"
And Admin sets the business date to "05 January 2024"
And Admin runs inline COB job for Loan
@TestRailId:C3758
Scenario: Verify validation of capitalized income amount with disbursement amount not exceed approved over applied amount for multidisbursal progressive loan - failed scenario
When Admin sets the business date to "1 January 2024"
And Admin creates a client with random data
And Admin creates a fully customized loan with the following data:
| LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy |
| LP2_ADV_PYMNT_INTEREST_DAILY_RECALC_EMI_360_30_MULTIDISB_OVER_APPLIED_PERCENTAGE_CAPITALIZED_INCOME | 01 January 2024 | 1000 | 7 | DECLINING_BALANCE | DAILY | EQUAL_INSTALLMENTS | 6 | MONTHS | 1 | MONTHS | 6 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION |
And Admin successfully approves the loan on "1 January 2024" with "1000" amount and expected disbursement date on "1 January 2024"
And Admin successfully disburse the loan on "1 January 2024" with "1000" EUR transaction amount
Then Loan status will be "ACTIVE"
When Admin sets the business date to "2 January 2024"
And Admin successfully disburse the loan on "2 January 2024" with "300" EUR transaction amount
Then Capitalized income with payment type "AUTOPAY" on "2 January 2024" is forbidden with amount "300" while exceed approved amount
@TestRailId:C3759
Scenario: Verify validation of capitalized income amount with disbursement amount not exceed approved over applied amount for multidisbursal progressive loan - successful scenario
When Admin sets the business date to "1 January 2024"
And Admin creates a client with random data
And Admin creates a fully customized loan with the following data:
| LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy |
| LP2_ADV_PYMNT_INTEREST_DAILY_RECALC_EMI_360_30_MULTIDISB_OVER_APPLIED_PERCENTAGE_CAPITALIZED_INCOME | 01 January 2024 | 1000 | 7 | DECLINING_BALANCE | DAILY | EQUAL_INSTALLMENTS | 6 | MONTHS | 1 | MONTHS | 6 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION |
And Admin successfully approves the loan on "1 January 2024" with "1000" amount and expected disbursement date on "1 January 2024"
And Admin successfully disburse the loan on "1 January 2024" with "1000" EUR transaction amount
Then Loan status will be "ACTIVE"
When Admin sets the business date to "2 January 2024"
And Admin successfully disburse the loan on "2 January 2024" with "300" EUR transaction amount
And Admin adds capitalized income with "AUTOPAY" payment type to the loan on "02 January 2024" with "200" EUR transaction amount
@@ -40,6 +40,7 @@ import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
import org.apache.fineract.infrastructure.core.service.DateUtils;
import org.apache.fineract.infrastructure.core.service.MathUtil;
import org.apache.fineract.organisation.holiday.domain.Holiday;
import org.apache.fineract.organisation.monetary.domain.MoneyHelper;
import org.apache.fineract.organisation.workingdays.domain.WorkingDays;
import org.apache.fineract.portfolio.common.service.Validator;
import org.apache.fineract.portfolio.loanaccount.data.ScheduleGeneratorDTO;
@@ -54,6 +55,7 @@ import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRepositor
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionType;
import org.apache.fineract.portfolio.loanaccount.repository.LoanCapitalizedIncomeBalanceRepository;
import org.apache.fineract.portfolio.loanaccount.serialization.LoanTransactionValidator;
import org.apache.fineract.portfolio.loanproduct.domain.LoanProduct;
@Slf4j
@RequiredArgsConstructor
@@ -121,17 +123,23 @@ public class ProgressiveLoanTransactionValidatorImpl implements ProgressiveLoanT
final BigDecimal transactionAmount = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed("transactionAmount", element);
baseDataValidator.reset().parameter("transactionAmount").value(transactionAmount).notNull().positiveAmount();
// Validate total disbursement + capitalized income <= approved amount
// Validate total disbursement + capitalized income <= applied amount
if (transactionAmount != null) {
final BigDecimal totalDisbursed = loan.getDisbursedAmount();
final BigDecimal existingCapitalizedIncomeBalance = loanCapitalizedIncomeBalanceRepository.findAllByLoanId(loanId).stream()
.map(LoanCapitalizedIncomeBalance::getAmount).reduce(BigDecimal.ZERO, BigDecimal::add);
final BigDecimal approvedAmount = loan.getApprovedPrincipal();
final BigDecimal newTotal = totalDisbursed.add(existingCapitalizedIncomeBalance).add(transactionAmount);
final BigDecimal capitalizedIncome = loan.getSummary().getTotalCapitalizedIncome();
final BigDecimal newTotal = totalDisbursed.add(capitalizedIncome).add(transactionAmount);
if (newTotal.compareTo(approvedAmount) > 0) {
baseDataValidator.reset().parameter("transactionAmount").failWithCode("exceeds.approved.amount",
"Sum of disbursed amount and capitalized income cannot exceed approved amount");
if (loan.loanProduct().isAllowApprovedDisbursedAmountsOverApplied()) {
final BigDecimal maxAppliedAmount = getOverAppliedMax(loan);
if (newTotal.compareTo(maxAppliedAmount) > 0) {
baseDataValidator.reset().parameter("transactionAmount").failWithCode("exceeds.approved.amount",
"Sum of disbursed amount and capitalized income can't be greater than maximum applied loan amount calculation.");
}
} else {
if (newTotal.compareTo(loan.getApprovedPrincipal()) > 0) {
baseDataValidator.reset().parameter("transactionAmount").failWithCode("exceeds.approved.amount",
"Sum of disbursed amount and capitalized income can't be greater than approved loan principal.");
}
}
}
@@ -428,4 +436,16 @@ public class ProgressiveLoanTransactionValidatorImpl implements ProgressiveLoanT
private Set<String> getContractTerminationUndoParameters() {
return new HashSet<>(Arrays.asList("note", "reversalExternalId"));
}
private BigDecimal getOverAppliedMax(final Loan loan) {
final LoanProduct loanProduct = loan.getLoanProduct();
if ("percentage".equals(loanProduct.getOverAppliedCalculationType())) {
final BigDecimal overAppliedNumber = BigDecimal.valueOf(loanProduct.getOverAppliedNumber());
final BigDecimal totalPercentage = BigDecimal.valueOf(1)
.add(overAppliedNumber.divide(BigDecimal.valueOf(100L), MoneyHelper.getMathContext()));
return loan.getProposedPrincipal().multiply(totalPercentage);
} else {
return loan.getProposedPrincipal().add(BigDecimal.valueOf(loanProduct.getOverAppliedNumber()));
}
}
}