FINERACT-1971: Fix loan balance for additional (N+1) installment
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 bfa5b96..ef43af8 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 @@
                 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 @@
                 }
 
                 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 fe7b073..95156bc 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 @@
                                     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 @@
     }
 
     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 @@
         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 e3bebe2..2db311f 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.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 @@
         });
     }
 
+    @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));
+        });
+    }
 }