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 ad8fe13064c47a91bfb094748fedf9c37a6bca39
Author: mark.vituska <[email protected]>
AuthorDate: Wed May 28 14:17:22 2025 +0200

    FINERACT-2232: fix capitalized income balance handling during reverse 
replay situations
---
 .../serialization/LoanRefundValidator.java         |  4 +-
 .../service/LoanDownPaymentHandlerServiceImpl.java |  4 +-
 .../LoanCapitalizedIncomeTest.java                 | 74 ++++++++++++++++++++++
 3 files changed, 76 insertions(+), 6 deletions(-)

diff --git 
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/serialization/LoanRefundValidator.java
 
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/serialization/LoanRefundValidator.java
index a8f9bad5bc..3a9c72e427 100644
--- 
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/serialization/LoanRefundValidator.java
+++ 
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/serialization/LoanRefundValidator.java
@@ -104,9 +104,7 @@ public final class LoanRefundValidator {
         if (loan.getLoanProduct().isMultiDisburseLoan() && adjustedTransaction 
== null) {
             final BigDecimal totalDisbursed = loan.getDisbursedAmount();
             final BigDecimal totalPrincipalAdjusted = 
loan.getSummary().getTotalPrincipalAdjustments();
-            final BigDecimal totalCapitalizedIncome = 
loan.getLoanTransactions(LoanTransaction::isCapitalizedIncome).stream()
-                    
.filter(LoanTransaction::isNotReversed).map(LoanTransaction::getPrincipalPortion)
-                    .reduce(BigDecimal.ZERO, BigDecimal::add);
+            final BigDecimal totalCapitalizedIncome = 
loan.getSummary().getTotalCapitalizedIncome();
             final BigDecimal totalPrincipalCredited = 
totalDisbursed.add(totalPrincipalAdjusted).add(totalCapitalizedIncome);
             if 
(totalPrincipalCredited.compareTo(loan.getSummary().getTotalPrincipalRepaid()) 
< 0) {
                 final String errorMessage = "The transaction amount cannot 
exceed threshold.";
diff --git 
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanDownPaymentHandlerServiceImpl.java
 
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanDownPaymentHandlerServiceImpl.java
index 271756c407..fb4b8d80f2 100644
--- 
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanDownPaymentHandlerServiceImpl.java
+++ 
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanDownPaymentHandlerServiceImpl.java
@@ -174,9 +174,7 @@ public class LoanDownPaymentHandlerServiceImpl implements 
LoanDownPaymentHandler
         if (loan.getLoanProduct().isMultiDisburseLoan()) {
             final BigDecimal totalDisbursed = loan.getDisbursedAmount();
             final BigDecimal totalPrincipalAdjusted = 
loan.getSummary().getTotalPrincipalAdjustments();
-            final BigDecimal totalCapitalizedIncome = 
loan.getLoanTransactions(LoanTransaction::isCapitalizedIncome).stream()
-                    
.filter(LoanTransaction::isNotReversed).map(LoanTransaction::getPrincipalPortion)
-                    .reduce(BigDecimal.ZERO, BigDecimal::add);
+            final BigDecimal totalCapitalizedIncome = 
loan.getSummary().getTotalCapitalizedIncome();
             final BigDecimal totalPrincipalCredited = 
totalDisbursed.add(totalPrincipalAdjusted).add(totalCapitalizedIncome);
             if 
(totalPrincipalCredited.compareTo(loan.getSummary().getTotalPrincipalRepaid()) 
< 0
                     && 
loan.getLoanProductRelatedDetail().getPrincipal().minus(totalDisbursed).isGreaterThanZero())
 {
diff --git 
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanCapitalizedIncomeTest.java
 
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanCapitalizedIncomeTest.java
index b92341ec2f..eecb08bac6 100644
--- 
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanCapitalizedIncomeTest.java
+++ 
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanCapitalizedIncomeTest.java
@@ -21,6 +21,7 @@ package org.apache.fineract.integrationtests;
 import static org.junit.jupiter.api.Assertions.assertTrue;
 
 import java.math.BigDecimal;
+import java.math.RoundingMode;
 import java.util.Optional;
 import java.util.concurrent.atomic.AtomicReference;
 import org.apache.fineract.client.models.GetLoansLoanIdResponse;
@@ -30,6 +31,7 @@ import 
org.apache.fineract.client.models.PostLoanProductsRequest;
 import org.apache.fineract.client.models.PostLoanProductsResponse;
 import org.apache.fineract.client.models.PostLoansLoanIdTransactionsRequest;
 import org.apache.fineract.client.models.PostLoansLoanIdTransactionsResponse;
+import org.apache.fineract.client.models.PostLoansResponse;
 import org.apache.fineract.client.util.CallFailedRuntimeException;
 import org.apache.fineract.integrationtests.common.BusinessStepHelper;
 import org.apache.fineract.integrationtests.common.ClientHelper;
@@ -683,4 +685,76 @@ public class LoanCapitalizedIncomeTest extends 
BaseLoanIntegrationTest {
         });
     }
 
+
+    @Test
+    public void 
testOverpaymentAmountWhenCapitalizedIncomeTransactionsAreReversed() {
+        final AtomicReference<Long> loanIdRef = new AtomicReference<>();
+        final PostClientsResponse client = 
clientHelper.createClient(ClientHelper.defaultClientCreationRequest());
+        runAt("01 March 2023", () -> {
+            final PostLoanProductsResponse loanProductsResponse = 
loanProductHelper
+                    
.createLoanProduct(create4IProgressiveWithCapitalizedIncome());
+            PostLoansResponse postLoansResponse = 
loanTransactionHelper.applyLoan(applyLP2ProgressiveLoanRequest(client.getClientId(),
+                    loanProductsResponse.getResourceId(), "01 March 2023", 
10000.00, 12.00, 4, null));
+            Long loanId = postLoansResponse.getLoanId();
+            loanIdRef.set(loanId);
+
+            loanTransactionHelper.approveLoan(loanId, 
approveLoanRequest(10000.00, "01 March 2023"));
+            disburseLoan(loanId, BigDecimal.valueOf(1000.00), "01 March 2023");
+
+            loanTransactionHelper.addCapitalizedIncome(loanId, "01 March 
2023", 500.00);
+            PostLoansLoanIdTransactionsResponse transactionsResponse = 
loanTransactionHelper.addCapitalizedIncome(loanId, "01 March 2023",
+                    500.00);
+
+            loanTransactionHelper.makeLoanRepayment(loanId, "Repayment", "1 
March 2023", 2000.00);
+            loanTransactionHelper.reverseLoanTransaction(loanId, 
transactionsResponse.getResourceId(), "1 March 2023");
+        });
+
+        BigDecimal zero = BigDecimal.ZERO;
+        BigDecimal thousand = BigDecimal.valueOf(1000.0);
+        BigDecimal fiveHundred = BigDecimal.valueOf(500.0);
+        BigDecimal thousandFiveHundred = BigDecimal.valueOf(1500.0);
+
+        GetLoansLoanIdResponse loanDetails = 
loanTransactionHelper.getLoanDetails(loanIdRef.get());
+        Assertions.assertEquals(thousand, 
loanDetails.getPrincipal().setScale(1, RoundingMode.HALF_UP));
+        Assertions.assertEquals(thousand, 
loanDetails.getSummary().getPrincipalDisbursed().setScale(1, 
RoundingMode.HALF_UP));
+        Assertions.assertEquals(fiveHundred, 
loanDetails.getSummary().getTotalCapitalizedIncome().setScale(1, 
RoundingMode.HALF_UP));
+        Assertions.assertEquals(thousandFiveHundred, 
loanDetails.getSummary().getTotalPrincipal().setScale(1, RoundingMode.HALF_UP));
+        Assertions.assertEquals(zero, 
loanDetails.getSummary().getPrincipalOutstanding().setScale(0, 
RoundingMode.HALF_UP));
+
+        Assertions.assertEquals(fiveHundred, 
loanDetails.getTotalOverpaid().setScale(1, RoundingMode.HALF_UP));
+    }
+
+    @Test
+    public void 
testOverpaymentAmountCorrectlyCalculatedWhenBackdatedRepaymentIsMade() {
+        final AtomicReference<Long> loanIdRef = new AtomicReference<>();
+        final PostClientsResponse client = 
clientHelper.createClient(ClientHelper.defaultClientCreationRequest());
+        runAt("01 March 2023", () -> {
+            final PostLoanProductsResponse loanProductsResponse = 
loanProductHelper
+                    
.createLoanProduct(create4IProgressiveWithCapitalizedIncome());
+            PostLoansResponse postLoansResponse = 
loanTransactionHelper.applyLoan(applyLP2ProgressiveLoanRequest(client.getClientId(),
+                    loanProductsResponse.getResourceId(), "01 March 2023", 
10000.00, 12.00, 4, null));
+            Long loanId = postLoansResponse.getLoanId();
+            loanIdRef.set(loanId);
+
+            loanTransactionHelper.approveLoan(loanId, 
approveLoanRequest(10000.00, "01 March 2023"));
+            disburseLoan(loanId, BigDecimal.valueOf(1000.00), "01 March 2023");
+        });
+
+        runAt("15 March 2023", () -> {
+            loanTransactionHelper.addCapitalizedIncome(loanIdRef.get(), "15 
March 2023", 500.00);
+            loanTransactionHelper.makeLoanRepayment(loanIdRef.get(), 
"Repayment", "1 March 2023", 1500.00);
+        });
+
+        BigDecimal zero = BigDecimal.ZERO;
+        BigDecimal thousand = BigDecimal.valueOf(1000.0);
+        BigDecimal fiveHundred = BigDecimal.valueOf(500.0);
+        BigDecimal thousandFiveHundred = BigDecimal.valueOf(1500.0);
+
+        GetLoansLoanIdResponse loanDetails = 
loanTransactionHelper.getLoanDetails(loanIdRef.get());
+        Assertions.assertEquals(thousand, 
loanDetails.getPrincipal().setScale(1, RoundingMode.HALF_UP));
+        Assertions.assertEquals(thousand, 
loanDetails.getSummary().getPrincipalDisbursed().setScale(1, 
RoundingMode.HALF_UP));
+        Assertions.assertEquals(fiveHundred, 
loanDetails.getSummary().getTotalCapitalizedIncome().setScale(1, 
RoundingMode.HALF_UP));
+        Assertions.assertEquals(thousandFiveHundred, 
loanDetails.getSummary().getTotalPrincipal().setScale(1, RoundingMode.HALF_UP));
+        Assertions.assertEquals(zero, 
loanDetails.getSummary().getPrincipalOutstanding().setScale(0, 
RoundingMode.HALF_UP));
+    }
 }

Reply via email to