From e4ab6110ff9090266c211c46b29636235009601a Mon Sep 17 00:00:00 2001 From: mariiaKraievska Date: Mon, 16 Jun 2025 14:31:38 +0300 Subject: [PATCH] FINERACT-2181: Fix validation for add capitalized income to loan transaction --- .../LoanProductGlobalInitializerStep.java | 3 ++ .../features/LoanCapitalizedIncome.feature | 28 +++++++++++++++ ...ogressiveLoanTransactionValidatorImpl.java | 36 ++++++++++++++----- 3 files changed, 59 insertions(+), 8 deletions(-) diff --git a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/initializer/global/LoanProductGlobalInitializerStep.java b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/initializer/global/LoanProductGlobalInitializerStep.java index 5d377abb0..5ebca2ab3 100644 --- a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/initializer/global/LoanProductGlobalInitializerStep.java +++ b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/initializer/global/LoanProductGlobalInitializerStep.java @@ -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"), // diff --git a/fineract-e2e-tests-runner/src/test/resources/features/LoanCapitalizedIncome.feature b/fineract-e2e-tests-runner/src/test/resources/features/LoanCapitalizedIncome.feature index 684782daa..b3dca170c 100644 --- a/fineract-e2e-tests-runner/src/test/resources/features/LoanCapitalizedIncome.feature +++ b/fineract-e2e-tests-runner/src/test/resources/features/LoanCapitalizedIncome.feature @@ -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 \ No newline at end of file diff --git a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/ProgressiveLoanTransactionValidatorImpl.java b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/ProgressiveLoanTransactionValidatorImpl.java index 9abf35df4..842788b39 100644 --- a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/ProgressiveLoanTransactionValidatorImpl.java +++ b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/ProgressiveLoanTransactionValidatorImpl.java @@ -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 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())); + } + } }