FINERACT-1971: Fix final accrual in case of waived charge
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanAccountDomainServiceJpa.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanAccountDomainServiceJpa.java
index d6c542e..b44af4d 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanAccountDomainServiceJpa.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanAccountDomainServiceJpa.java
@@ -936,18 +936,33 @@
             Money penaltyPortion = Money.zero(currency);
 
             for (LoanRepaymentScheduleInstallment loanRepaymentScheduleInstallment : loan.getRepaymentScheduleInstallments()) {
+                // TODO: test with interest waiving
                 interestPortion = interestPortion.add(loanRepaymentScheduleInstallment.getInterestCharged(currency))
                         .minus(loanRepaymentScheduleInstallment.getInterestAccrued(currency))
                         .minus(loanRepaymentScheduleInstallment.getInterestWaived(currency));
-                feePortion = feePortion.add(loanRepaymentScheduleInstallment.getFeeChargesCharged(currency))
-                        .minus(loanRepaymentScheduleInstallment.getFeeAccrued(currency))
-                        .minus(loanRepaymentScheduleInstallment.getFeeChargesWaived(currency))
-                        .minus(loanRepaymentScheduleInstallment.getCreditedFee(currency));
-                penaltyPortion = penaltyPortion.add(loanRepaymentScheduleInstallment.getPenaltyChargesCharged(currency))
-                        .minus(loanRepaymentScheduleInstallment.getPenaltyAccrued(currency))
-                        .minus(loanRepaymentScheduleInstallment.getPenaltyChargesWaived(currency))
-                        .minus(loanRepaymentScheduleInstallment.getCreditedPenalty(currency));
             }
+
+            for (LoanCharge loanCharge : loan.getLoanCharges()) {
+                if (!loanCharge.isActive()) {
+                    continue;
+                }
+                BigDecimal accruedAmount = BigDecimal.ZERO;
+                BigDecimal waivedAmount = BigDecimal.ZERO;
+                for (LoanChargePaidBy loanChargePaidBy : loanCharge.getLoanChargePaidBySet()) {
+                    if (loanChargePaidBy.getLoanTransaction().isAccrual()) {
+                        accruedAmount = accruedAmount.add(loanChargePaidBy.getLoanTransaction().getAmount());
+                    } else if (loanChargePaidBy.getLoanTransaction().isChargesWaiver()) {
+                        waivedAmount = waivedAmount.add(loanChargePaidBy.getLoanTransaction().getAmount());
+                    }
+                }
+                Money needToAccrueAmount = MathUtil.negativeToZero(loanCharge.getAmount(currency).minus(accruedAmount).minus(waivedAmount));
+                if (loanCharge.isPenaltyCharge()) {
+                    penaltyPortion = penaltyPortion.add(needToAccrueAmount);
+                } else if (loanCharge.isFeeCharge()) {
+                    feePortion = feePortion.add(needToAccrueAmount);
+                }
+            }
+
             Money total = interestPortion.plus(feePortion).plus(penaltyPortion);
 
             if (total.isGreaterThanZero()) {
diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanWaiveChargeTest.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanWaiveChargeTest.java
index 9d631d8..8c01ccc 100644
--- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanWaiveChargeTest.java
+++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanWaiveChargeTest.java
@@ -32,6 +32,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.PostLoansRequest;
 import org.apache.fineract.client.models.PostLoansResponse;
 import org.apache.fineract.integrationtests.common.ClientHelper;
@@ -139,4 +140,130 @@
             assertEquals(expected, obligationsMetOnDate);
         });
     }
+
+    @ParameterizedTest
+    @MethodSource("processingStrategy")
+    public void accrualIsCalculatedWhenThereIsWaivedChargeAndLoanIsClosed(boolean advancedPaymentStrategy) {
+        double amount = 1000.0;
+        AtomicLong appliedLoanId = new AtomicLong();
+        String LoanCoBJobName = "Loan COB";
+
+        runAt("01 January 2023", () -> {
+            // Create Client
+            Long clientId = clientHelper.createClient(ClientHelper.defaultClientCreationRequest()).getClientId();
+
+            // Create Loan Product
+            PostLoanProductsRequest product;
+            if (advancedPaymentStrategy) {
+                product = createOnePeriod30DaysLongNoInterestPeriodicAccrualProductWithAdvancedPaymentAllocation();
+            } else {
+                product = createOnePeriod30DaysLongNoInterestPeriodicAccrualProduct();
+            }
+
+            PostLoanProductsResponse loanProductResponse = loanProductHelper.createLoanProduct(product);
+            Long loanProductId = loanProductResponse.getResourceId();
+
+            // Apply and Approve Loan
+
+            PostLoansRequest applicationRequest = applyLoanRequest(clientId, loanProductId, "01 January 2023", amount, 1);
+            if (advancedPaymentStrategy) {
+                applicationRequest = applicationRequest
+                        .transactionProcessingStrategyCode(LoanProductTestBuilder.ADVANCED_PAYMENT_ALLOCATION_STRATEGY);
+            }
+
+            PostLoansResponse postLoansResponse = loanTransactionHelper.applyLoan(applicationRequest);
+
+            PostLoansLoanIdResponse approvedLoanResult = loanTransactionHelper.approveLoan(postLoansResponse.getResourceId(),
+                    approveLoanRequest(amount, "01 January 2023"));
+
+            Long loanId = approvedLoanResult.getLoanId();
+            appliedLoanId.set(loanId);
+
+            // disburse Loan
+            disburseLoan(loanId, BigDecimal.valueOf(amount), "01 January 2023");
+
+            // verify schedule
+            verifyRepaymentSchedule(loanId, //
+                    installment(0.0, null, "01 January 2023"), //
+                    installment(1000.0, 0.0, 0.0, 1000.0, false, "31 January 2023"));
+        });
+
+        runAt("10 January 2023", () -> {
+            Long loanId = appliedLoanId.get();
+
+            // create charge
+            double chargeAmount = 10.0;
+            PostChargesResponse chargeResult = createCharge(chargeAmount);
+            Long chargeId = chargeResult.getResourceId();
+
+            PostLoansLoanIdChargesResponse loanChargeResult = addLoanCharge(loanId, chargeId, "09 January 2023", chargeAmount);
+            loanChargeResult.getResourceId();
+            this.schedulerJobHelper.executeAndAwaitJob(LoanCoBJobName);
+
+            verifyRepaymentSchedule(loanId, //
+                    installment(0.0, null, "01 January 2023"), //
+                    installment(1000.0, 0.0, 10.0, 1010.0, false, "31 January 2023") //
+
+            );
+            verifyTransactions(loanId, //
+                    transaction(1000.0, "Disbursement", "01 January 2023", 1000.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0), //
+                    transaction(10.0, "Accrual", "09 January 2023", 0.0, 0.0, 0.0, 10.0, 0.0, 0.0, 0.0));
+        });
+        runAt("11 January 2023", () -> {
+            Long loanId = appliedLoanId.get();
+
+            // create charge
+            double chargeAmount = 9.0;
+            PostChargesResponse chargeResult = createCharge(chargeAmount);
+            Long chargeId = chargeResult.getResourceId();
+
+            PostLoansLoanIdChargesResponse loanChargeResult = addLoanCharge(loanId, chargeId, "10 January 2023", chargeAmount);
+            Long loanChargeId = loanChargeResult.getResourceId();
+            this.schedulerJobHelper.executeAndAwaitJob(LoanCoBJobName);
+            // waive charge
+            waiveLoanCharge(loanId, loanChargeId, 1);
+
+            verifyTransactions(loanId, //
+                    transaction(1000.0, "Disbursement", "01 January 2023", 1000.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0), //
+                    transaction(10.0, "Accrual", "09 January 2023", 0.0, 0.0, 0.0, 10.0, 0.0, 0.0, 0.0), //
+                    transaction(9.0, "Accrual", "10 January 2023", 0.0, 0.0, 0.0, 9.0, 0.0, 0.0, 0.0), //
+                    transaction(9.0, "Waive loan charges", "10 January 2023", 1000.0, 0.0, 0.0, 9.0, 0.0, 0.0, 0.0) //
+            );
+
+            verifyRepaymentSchedule(loanId, //
+                    installment(0.0, null, "01 January 2023"), //
+                    installment(1000.0, 0.0, 19.0, 1010.0, false, "31 January 2023") //
+            );
+        });
+
+        runAt("12 January 2023", () -> {
+            Long loanId = appliedLoanId.get();
+
+            // create charge
+            double chargeAmount = 8.0;
+            PostChargesResponse chargeResult = createCharge(chargeAmount);
+            Long chargeId = chargeResult.getResourceId();
+
+            PostLoansLoanIdChargesResponse loanChargeResult = addLoanCharge(loanId, chargeId, "11 January 2023", chargeAmount);
+            loanChargeResult.getResourceId();
+
+            loanTransactionHelper.makeLoanRepayment(loanId, new PostLoansLoanIdTransactionsRequest().transactionDate("12 January 2023")
+                    .dateFormat("dd MMMM yyyy").locale("en").transactionAmount(1018.0));
+
+            verifyTransactions(loanId, //
+                    transaction(1000.0, "Disbursement", "01 January 2023", 1000.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0), //
+                    transaction(10.0, "Accrual", "09 January 2023", 0.0, 0.0, 0.0, 10.0, 0.0, 0.0, 0.0), //
+                    transaction(9.0, "Accrual", "10 January 2023", 0.0, 0.0, 0.0, 9.0, 0.0, 0.0, 0.0), //
+                    transaction(9.0, "Waive loan charges", "10 January 2023", 1000.0, 0.0, 0.0, 9.0, 0.0, 0.0, 0.0), //
+                    transaction(1018.0, "Repayment", "12 January 2023", 0.0, 1000.0, 0.0, 18.0, 0.0, 0.0, 0.0), //
+                    transaction(8.0, "Accrual", "12 January 2023", 0.0, 0.0, 0.0, 8.0, 0.0, 0.0, 0.0) //
+            );
+
+            verifyRepaymentSchedule(loanId, //
+                    installment(0.0, null, "01 January 2023"), //
+                    installment(1000.0, 0.0, 27.0, 0.0, true, "31 January 2023") //
+            );
+        });
+
+    }
 }