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()));
+        }
+    }
 }

Reply via email to