This is an automated email from the ASF dual-hosted git repository. adamsaghy pushed a commit to branch develop in repository https://gitbox.apache.org/repos/asf/fineract.git
commit e4ab6110ff9090266c211c46b29636235009601a Author: mariiaKraievska <[email protected]> AuthorDate: Mon Jun 16 14:31:38 2025 +0300 FINERACT-2181: Fix validation for add capitalized income to loan transaction --- .../global/LoanProductGlobalInitializerStep.java | 3 ++ .../features/LoanCapitalizedIncome.feature | 28 ++++++++++++++++ .../ProgressiveLoanTransactionValidatorImpl.java | 38 +++++++++++++++++----- 3 files changed, 60 insertions(+), 9 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 5d377abb0a..5ebca2ab33 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 684782daaf..b3dca170c0 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 9abf35df46..842788b39e 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); - - if (newTotal.compareTo(approvedAmount) > 0) { - baseDataValidator.reset().parameter("transactionAmount").failWithCode("exceeds.approved.amount", - "Sum of disbursed amount and capitalized income cannot exceed approved amount"); + final BigDecimal capitalizedIncome = loan.getSummary().getTotalCapitalizedIncome(); + final BigDecimal newTotal = totalDisbursed.add(capitalizedIncome).add(transactionAmount); + + 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())); + } + } }
