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


The following commit(s) were added to refs/heads/develop by this push:
     new aac6f7cd2 FINERACT-1971: Fix loan balance for additional (N+1) 
installment
aac6f7cd2 is described below

commit aac6f7cd21a06c0cdc6384a1d2e84228535e3817
Author: Adam Saghy <[email protected]>
AuthorDate: Tue Mar 19 18:17:52 2024 +0100

    FINERACT-1971: Fix loan balance for additional (N+1) installment
---
 .../service/LoanReadPlatformServiceImpl.java       |  18 +--
 .../integrationtests/BaseLoanIntegrationTest.java  |  26 +++-
 .../loan/reaging/LoanReAgingIntegrationTest.java   | 144 +++++++++++++++++++++
 3 files changed, 170 insertions(+), 18 deletions(-)

diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java
 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java
index bfa5b96b5..ef43af882 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java
@@ -1225,16 +1225,14 @@ public class LoanReadPlatformServiceImpl implements 
LoanReadPlatformService, Loa
                 final boolean complete = rs.getBoolean("complete");
                 final boolean isAdditional = rs.getBoolean("isAdditional");
                 BigDecimal disbursedAmount = BigDecimal.ZERO;
-                if (!isAdditional) {
-                    disbursedAmount = 
processDisbursementData(loanScheduleType, disbursementData, fromDate, dueDate, 
disbursementPeriodIds,
-                            disbursementChargeAmount, waivedChargeAmount, 
periods);
 
-                }
+                disbursedAmount = processDisbursementData(loanScheduleType, 
disbursementData, fromDate, dueDate, disbursementPeriodIds,
+                        disbursementChargeAmount, waivedChargeAmount, periods);
+
                 // Add the Charge back or Credits to the initial amount to 
avoid negative balance
                 final BigDecimal credits = 
JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "totalCredits");
-                if (!isAdditional) {
-                    this.outstandingLoanPrincipalBalance = 
this.outstandingLoanPrincipalBalance.add(credits);
-                }
+
+                this.outstandingLoanPrincipalBalance = 
this.outstandingLoanPrincipalBalance.add(credits);
 
                 totalPrincipalDisbursed = 
totalPrincipalDisbursed.add(disbursedAmount);
 
@@ -1313,16 +1311,10 @@ public class LoanReadPlatformServiceImpl implements 
LoanReadPlatformService, Loa
                 }
 
                 BigDecimal outstandingPrincipalBalanceOfLoan = 
this.outstandingLoanPrincipalBalance.subtract(principalDue);
-                if (isAdditional) {
-                    outstandingPrincipalBalanceOfLoan = 
this.outstandingLoanPrincipalBalance.add(principalDue);
-                }
 
                 // update based on current period values
                 this.lastDueDate = dueDate;
                 this.outstandingLoanPrincipalBalance = 
this.outstandingLoanPrincipalBalance.subtract(principalDue);
-                if (isAdditional) {
-                    this.outstandingLoanPrincipalBalance = 
this.outstandingLoanPrincipalBalance.add(principalDue);
-                }
 
                 final boolean isDownPayment = rs.getBoolean("isDownPayment");
 
diff --git 
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/BaseLoanIntegrationTest.java
 
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/BaseLoanIntegrationTest.java
index fe7b07331..95156bc93 100644
--- 
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/BaseLoanIntegrationTest.java
+++ 
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/BaseLoanIntegrationTest.java
@@ -547,6 +547,13 @@ public abstract class BaseLoanIntegrationTest {
                                     outstandingTotalExpected, 
outstandingTotal));
                 }
 
+                Double loanBalanceExpected = installments[i].loanBalance;
+                Double loanBalance = 
period.getPrincipalLoanBalanceOutstanding();
+                if (loanBalanceExpected != null) {
+                    Assertions.assertEquals(loanBalanceExpected, loanBalance,
+                            "%d. installment's loan balance is different, 
expected: %.2f, actual: %.2f".formatted(i, loanBalanceExpected,
+                                    loanBalance));
+                }
                 installmentNumber++;
                 Assertions.assertEquals(installmentNumber, period.getPeriod());
             }
@@ -716,27 +723,35 @@ public abstract class BaseLoanIntegrationTest {
     }
 
     protected Installment installment(double principalAmount, Boolean 
completed, String dueDate) {
-        return new Installment(principalAmount, null, null, null, null, 
completed, dueDate, null);
+        return new Installment(principalAmount, null, null, null, null, 
completed, dueDate, null, null);
     }
 
     protected Installment installment(double principalAmount, double 
interestAmount, double totalOutstandingAmount, Boolean completed,
             String dueDate) {
-        return new Installment(principalAmount, interestAmount, null, null, 
totalOutstandingAmount, completed, dueDate, null);
+        return new Installment(principalAmount, interestAmount, null, null, 
totalOutstandingAmount, completed, dueDate, null, null);
     }
 
     protected Installment installment(double principalAmount, double 
interestAmount, double feeAmount, double totalOutstandingAmount,
             Boolean completed, String dueDate) {
-        return new Installment(principalAmount, interestAmount, feeAmount, 
null, totalOutstandingAmount, completed, dueDate, null);
+        return new Installment(principalAmount, interestAmount, feeAmount, 
null, totalOutstandingAmount, completed, dueDate, null, null);
     }
 
     protected Installment installment(double principalAmount, double 
interestAmount, double feeAmount, double penaltyAmount,
             double totalOutstandingAmount, Boolean completed, String dueDate) {
-        return new Installment(principalAmount, interestAmount, feeAmount, 
penaltyAmount, totalOutstandingAmount, completed, dueDate, null);
+        return new Installment(principalAmount, interestAmount, feeAmount, 
penaltyAmount, totalOutstandingAmount, completed, dueDate, null,
+                null);
     }
 
     protected Installment installment(double principalAmount, double 
interestAmount, double feeAmount, double penaltyAmount,
             OutstandingAmounts outstandingAmounts, Boolean completed, String 
dueDate) {
-        return new Installment(principalAmount, interestAmount, feeAmount, 
penaltyAmount, null, completed, dueDate, outstandingAmounts);
+        return new Installment(principalAmount, interestAmount, feeAmount, 
penaltyAmount, null, completed, dueDate, outstandingAmounts,
+                null);
+    }
+
+    protected Installment installment(double principalAmount, double 
interestAmount, double feeAmount, double penaltyAmount,
+            double totalOutstanding, Boolean completed, String dueDate, double 
loanBalance) {
+        return new Installment(principalAmount, interestAmount, feeAmount, 
penaltyAmount, totalOutstanding, completed, dueDate, null,
+                loanBalance);
     }
 
     protected OutstandingAmounts outstanding(double principal, double fee, 
double penalty, double total) {
@@ -921,6 +936,7 @@ public abstract class BaseLoanIntegrationTest {
         Boolean completed;
         String dueDate;
         OutstandingAmounts outstandingAmounts;
+        Double loanBalance;
     }
 
     @AllArgsConstructor
diff --git 
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/loan/reaging/LoanReAgingIntegrationTest.java
 
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/loan/reaging/LoanReAgingIntegrationTest.java
index e3bebe228..2db311f86 100644
--- 
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/loan/reaging/LoanReAgingIntegrationTest.java
+++ 
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/loan/reaging/LoanReAgingIntegrationTest.java
@@ -28,6 +28,7 @@ import 
org.apache.fineract.client.models.PostLoanProductsResponse;
 import org.apache.fineract.client.models.PostLoansLoanIdChargesResponse;
 import org.apache.fineract.client.models.PostLoansLoanIdResponse;
 import org.apache.fineract.client.models.PostLoansLoanIdTransactionsRequest;
+import 
org.apache.fineract.client.models.PostLoansLoanIdTransactionsTransactionIdRequest;
 import org.apache.fineract.client.models.PostLoansRequest;
 import org.apache.fineract.client.models.PostLoansResponse;
 import org.apache.fineract.integrationtests.BaseLoanIntegrationTest;
@@ -262,4 +263,147 @@ public class LoanReAgingIntegrationTest extends 
BaseLoanIntegrationTest {
         });
     }
 
+    @Test
+    public void test_LoanReAgeTransaction_WithChargeback_Works() {
+        AtomicLong createdLoanId = new AtomicLong();
+
+        runAt("01 January 2023", () -> {
+            // Create Client
+            Long clientId = 
clientHelper.createClient(ClientHelper.defaultClientCreationRequest()).getClientId();
+
+            int numberOfRepayments = 3;
+            int repaymentEvery = 1;
+
+            // Create Loan Product
+            PostLoanProductsRequest product = 
createOnePeriod30DaysLongNoInterestPeriodicAccrualProductWithAdvancedPaymentAllocation()
 //
+                    .numberOfRepayments(numberOfRepayments) //
+                    .repaymentEvery(repaymentEvery) //
+                    .installmentAmountInMultiplesOf(null) //
+                    .enableDownPayment(true) //
+                    
.disbursedAmountPercentageForDownPayment(BigDecimal.valueOf(25)) //
+                    .enableAutoRepaymentForDownPayment(true) //
+                    
.repaymentFrequencyType(RepaymentFrequencyType.MONTHS.longValue()); //
+
+            PostLoanProductsResponse loanProductResponse = 
loanProductHelper.createLoanProduct(product);
+            Long loanProductId = loanProductResponse.getResourceId();
+
+            // Apply and Approve Loan
+            double amount = 1250.0;
+
+            PostLoansRequest applicationRequest = applyLoanRequest(clientId, 
loanProductId, "01 January 2023", amount, numberOfRepayments)//
+                    
.transactionProcessingStrategyCode(LoanProductTestBuilder.ADVANCED_PAYMENT_ALLOCATION_STRATEGY)//
+                    .repaymentEvery(repaymentEvery)//
+                    .loanTermFrequency(numberOfRepayments)//
+                    .repaymentFrequencyType(RepaymentFrequencyType.MONTHS)//
+                    .loanTermFrequencyType(RepaymentFrequencyType.MONTHS);
+
+            PostLoansResponse postLoansResponse = 
loanTransactionHelper.applyLoan(applicationRequest);
+
+            PostLoansLoanIdResponse approvedLoanResult = 
loanTransactionHelper.approveLoan(postLoansResponse.getResourceId(),
+                    approveLoanRequest(amount, "01 January 2023"));
+
+            Long loanId = approvedLoanResult.getLoanId();
+
+            // disburse Loan
+            disburseLoan(loanId, BigDecimal.valueOf(1250.0), "01 January 
2023");
+
+            // verify transactions
+            verifyTransactions(loanId, //
+                    transaction(1250.0, "Disbursement", "01 January 2023"), //
+                    transaction(312.5, "Down Payment", "01 January 2023") //
+            );
+
+            // verify schedule
+            verifyRepaymentSchedule(loanId, //
+                    installment(1250, null, "01 January 2023"), //
+                    installment(312.5, true, "01 January 2023"), //
+                    installment(312.5, false, "01 February 2023"), //
+                    installment(312.5, false, "01 March 2023"), //
+                    installment(312.5, false, "01 April 2023") //
+            );
+            checkMaturityDates(loanId, LocalDate.of(2023, 4, 1), 
LocalDate.of(2023, 4, 1));
+            createdLoanId.set(loanId);
+        });
+
+        String repaymentExternalId = UUID.randomUUID().toString();
+        runAt("01 February 2023", () -> {
+            long loanId = createdLoanId.get();
+
+            loanTransactionHelper.makeLoanRepayment(loanId, new 
PostLoansLoanIdTransactionsRequest().dateFormat(DATETIME_PATTERN)
+                    .transactionDate("01 February 
2023").locale("en").transactionAmount(100.0).externalId(repaymentExternalId));
+
+            // verify transactions
+            verifyTransactions(loanId, //
+                    transaction(1250.0, "Disbursement", "01 January 2023"), //
+                    transaction(312.5, "Down Payment", "01 January 2023"), //
+                    transaction(100.0, "Repayment", "01 February 2023") //
+            );
+
+            // verify schedule
+            verifyRepaymentSchedule(loanId, //
+                    installment(1250, null, "01 January 2023"), //
+                    installment(312.5, 0, 0, 0, 0.0, true, "01 January 2023"), 
//
+                    installment(312.5, 0, 0, 0, 212.5, false, "01 February 
2023"), //
+                    installment(312.5, 0, 0, 0, 312.5, false, "01 March 
2023"), //
+                    installment(312.5, 0, 0, 0, 312.5, false, "01 April 2023") 
//
+            );
+        });
+
+        runAt("10 April 2023", () -> {
+            long loanId = createdLoanId.get();
+
+            // disburse Loan
+            loanTransactionHelper.chargebackLoanTransaction(loanId, 
repaymentExternalId,
+                    new 
PostLoansLoanIdTransactionsTransactionIdRequest().locale("en").transactionAmount(100.0));
+
+            // verify transactions
+            verifyTransactions(loanId, //
+                    transaction(1250.0, "Disbursement", "01 January 2023"), //
+                    transaction(312.5, "Down Payment", "01 January 2023"), //
+                    transaction(100.0, "Repayment", "01 February 2023"), //
+                    transaction(100.0, "Chargeback", "10 April 2023") //
+            );
+
+            // verify schedule
+            verifyRepaymentSchedule(loanId, //
+                    installment(1250, null, "01 January 2023"), //
+                    installment(312.5, 0, 0, 0, 0.0, true, "01 January 2023", 
937.5), //
+                    installment(312.5, 0, 0, 0, 212.5, false, "01 February 
2023", 625.0), //
+                    installment(312.5, 0, 0, 0, 312.5, false, "01 March 2023", 
312.5), //
+                    installment(312.5, 0, 0, 0, 312.5, false, "01 April 2023", 
0.0), //
+                    installment(100.0, 0.0, 0.0, 0.0, 100.0, false, "10 April 
2023", 0.0) //
+            );
+            checkMaturityDates(loanId, LocalDate.of(2023, 4, 1), 
LocalDate.of(2023, 4, 1));
+        });
+
+        runAt("12 April 2023", () -> {
+            long loanId = createdLoanId.get();
+
+            // create re-age transaction
+            reAgeLoan(loanId, RepaymentFrequencyType.MONTHS_STRING, 1, "12 
April 2023", 4);
+
+            // verify transactions
+            verifyTransactions(loanId, //
+                    transaction(1250.0, "Disbursement", "01 January 2023"), //
+                    transaction(312.5, "Down Payment", "01 January 2023"), //
+                    transaction(100.0, "Repayment", "01 February 2023"), //
+                    transaction(100.0, "Chargeback", "10 April 2023"), //
+                    transaction(937.5, "Re-age", "12 April 2023") //
+            );
+
+            verifyRepaymentSchedule(loanId, //
+                    installment(1250, null, "01 January 2023"), //
+                    installment(312.5, 0, 0, 0, 0.0, true, "01 January 2023", 
937.5), //
+                    installment(100.0, 0, 0, 0, 0.0, true, "01 February 2023", 
837.5), //
+                    installment(0.0, 0, 0, 0, 0.0, true, "01 March 2023", 
837.5), //
+                    installment(0.0, 0, 0, 0, 0.0, true, "01 April 2023", 
837.5), //
+                    installment(0.0, 0.0, 0.0, 0.0, 0.0, true, "10 April 
2023", 937.5), //
+                    installment(234.38, 0.0, 0.0, 0.0, 234.38, false, "12 
April 2023", 703.12), //
+                    installment(234.38, 0.0, 0.0, 0.0, 234.38, false, "12 May 
2023", 468.74), //
+                    installment(234.38, 0.0, 0.0, 0.0, 234.38, false, "12 June 
2023", 234.36), //
+                    installment(234.36, 0.0, 0.0, 0.0, 234.36, false, "12 July 
2023", 0.0) //
+            );
+            checkMaturityDates(loanId, LocalDate.of(2023, 7, 12), 
LocalDate.of(2023, 7, 12));
+        });
+    }
 }

Reply via email to