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 833f4a0d33 FINERACT-2228: Fix incorrect current balance calculation 
and unintentional transaction reversals
833f4a0d33 is described below

commit 833f4a0d33a9cbaa19c7426fcc33412fe697211b
Author: Adam Saghy <[email protected]>
AuthorDate: Fri Mar 28 21:36:14 2025 +0100

    FINERACT-2228: Fix incorrect current balance calculation and unintentional 
transaction reversals
---
 .../test/resources/features/LoanRepayment.feature  | 120 ++++++++++++++++++++-
 .../ReprocessLoanTransactionsServiceImpl.java      |   1 -
 .../domain/ProgressiveLoanScheduleGenerator.java   |   3 +-
 .../portfolio/loanproduct/calc/EMICalculator.java  |   3 +-
 .../loanproduct/calc/ProgressiveEMICalculator.java | 105 +++++++++++-------
 .../data/ProgressiveLoanInterestScheduleModel.java |   9 ++
 .../loanproduct/calc/data/RepaymentPeriod.java     |   4 +
 .../LoanChargeWritePlatformServiceImpl.java        |   2 +-
 .../adjustment/LoanAdjustmentServiceImpl.java      |   4 +-
 9 files changed, 202 insertions(+), 49 deletions(-)

diff --git 
a/fineract-e2e-tests-runner/src/test/resources/features/LoanRepayment.feature 
b/fineract-e2e-tests-runner/src/test/resources/features/LoanRepayment.feature
index 27d875a38a..0f67b5a1a8 100644
--- 
a/fineract-e2e-tests-runner/src/test/resources/features/LoanRepayment.feature
+++ 
b/fineract-e2e-tests-runner/src/test/resources/features/LoanRepayment.feature
@@ -4449,4 +4449,122 @@ Feature: LoanRepayment
       | Transaction date | Transaction Type | Amount | Principal | Interest | 
Fees | Penalties | Loan Balance | Reverted | Replayed |
       | 01 January 2024  | Disbursement     | 100.0  | 0.0       | 0.0      | 
0.0  | 0.0       | 100.0        | false    | false    |
       | 01 February 2024 | Repayment        | 20.0   | 19.42     | 0.58     | 
0.0  | 0.0       | 80.58        | false    | false    |
-    When Admin set 
"LP2_ADV_CUSTOM_PMT_ALLOC_PROGRESSIVE_LOAN_SCHEDULE_HORIZONTAL" loan product 
"DEFAULT" transaction type to "NEXT_INSTALLMENT" future installment allocation 
rule
\ No newline at end of file
+    When Admin set 
"LP2_ADV_CUSTOM_PMT_ALLOC_PROGRESSIVE_LOAN_SCHEDULE_HORIZONTAL" loan product 
"DEFAULT" transaction type to "NEXT_INSTALLMENT" future installment allocation 
rule
+
+  @TestRailId:C3569
+  Scenario: Verify Loan is fully paid and closed after partial repayments, 
Merchant issued refund which overpays the loan, partial CBR and reversal of 1st 
repayment
+    When Admin sets the business date to "28 March 2025"
+    When Admin creates a client with random data
+    When 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_CUSTOM_PMT_ALLOC_PROGRESSIVE_LOAN_SCHEDULE_HORIZONTAL | 26 
March 2025     | 120            | 35.29                  | DECLINING_BALANCE | 
DAILY                       | EQUAL_INSTALLMENTS | 3                 | MONTHS   
             | 1              | MONTHS                 | 3                  | 0 
                      | 0                      | 0                    | 
ADVANCED_PAYMENT_ALLOCATION |
+    And Admin successfully approves the loan on "26 March 2025" with "120" 
amount and expected disbursement date on "26 March 2025"
+    When Admin successfully disburse the loan on "26 March 2025" with "120" 
EUR transaction amount
+    Then Loan Repayment schedule has 3 periods, with the following data for 
periods:
+      | Nr | Days | Date             | Paid date        | Balance of loan | 
Principal due | Interest | Fees | Penalties | Due   | Paid  | In advance | Late 
| Outstanding |
+      |    |      | 26 March 2025    |                  | 120.0           |    
           |          | 0.0  |           | 0.0   | 0.0   |            |      |  
           |
+      | 1  | 31   | 26 April 2025    |                  | 81.15           | 
38.85         | 3.53     | 0.0  | 0.0       | 42.38 | 0.0   | 0.0        | 0.0  
| 42.38       |
+      | 2  | 30   | 26 May 2025      |                  | 41.16           | 
39.99         | 2.39     | 0.0  | 0.0       | 42.38 | 0.0   | 0.0        | 0.0  
| 42.38       |
+      | 3  | 31   | 26 June 2025     |                  |   0.0           | 
41.16         | 1.21     | 0.0  | 0.0       | 42.37 | 0.0   | 0.0        | 0.0  
| 42.37       |
+    Then Loan Repayment schedule has the following data in Total row:
+      | Principal due | Interest | Fees | Penalties | Due    | Paid | In 
advance | Late | Outstanding |
+      | 120.0         | 7.13     | 0.0  | 0.0       | 127.13 |  0.0 | 0.0      
  | 0.0  | 127.13      |
+    Then Loan Transactions tab has the following data:
+      | Transaction date | Transaction Type | Amount | Principal | Interest | 
Fees | Penalties | Loan Balance | Reverted | Replayed |
+      | 26 March 2025    | Disbursement     | 120.0  | 0.0       | 0.0      | 
0.0  | 0.0       | 120.0        | false    | false    |
+    And Customer makes "AUTOPAY" repayment on "27 March 2025" with 20 EUR 
transaction amount
+    Then Loan Repayment schedule has 3 periods, with the following data for 
periods:
+      | Nr | Days | Date             | Paid date        | Balance of loan | 
Principal due | Interest | Fees | Penalties | Due   | Paid  | In advance | Late 
| Outstanding |
+      |    |      | 26 March 2025    |                  | 120.0           |    
           |          | 0.0  |           | 0.0   | 0.0   |            |      |  
           |
+      | 1  | 31   | 26 April 2025    |                  | 80.58           | 
39.42         | 2.96     | 0.0  | 0.0       | 42.38 | 20.0  | 20.0       | 0.0  
| 22.38       |
+      | 2  | 30   | 26 May 2025      |                  | 40.57           | 
40.01         | 2.37     | 0.0  | 0.0       | 42.38 | 0.0   | 0.0        | 0.0  
| 42.38       |
+      | 3  | 31   | 26 June 2025     |                  |   0.0           | 
40.57         | 1.19     | 0.0  | 0.0       | 41.76 | 0.0   | 0.0        | 0.0  
| 41.76       |
+    Then Loan Repayment schedule has the following data in Total row:
+      | Principal due | Interest | Fees | Penalties | Due    | Paid | In 
advance | Late | Outstanding |
+      | 120.0         | 6.52     | 0.0  | 0.0       | 126.52 | 20.0 | 20.0     
  | 0.0  | 106.52      |
+    Then Loan Transactions tab has the following data:
+      | Transaction date | Transaction Type | Amount | Principal | Interest | 
Fees | Penalties | Loan Balance | Reverted | Replayed |
+      | 26 March 2025    | Disbursement     | 120.0  | 0.0       | 0.0      | 
0.0  | 0.0       | 120.0        | false    | false    |
+      | 27 March 2025    | Repayment        | 20.0   | 19.89     | 0.11     | 
0.0  | 0.0       | 100.11       | false    | false    |
+    And Customer makes "AUTOPAY" repayment on "27 March 2025" with 20 EUR 
transaction amount
+    Then Loan Repayment schedule has 3 periods, with the following data for 
periods:
+      | Nr | Days | Date             | Paid date        | Balance of loan | 
Principal due | Interest | Fees | Penalties | Due   | Paid  | In advance | Late 
| Outstanding |
+      |    |      | 26 March 2025    |                  | 120.0           |    
           |          | 0.0  |           | 0.0   | 0.0   |            |      |  
           |
+      | 1  | 31   | 26 April 2025    |                  | 80.01           | 
39.99         | 2.39     | 0.0  | 0.0       | 42.38 | 40.0  | 40.0       | 0.0  
|  2.38       |
+      | 2  | 30   | 26 May 2025      |                  | 39.98           | 
40.03         | 2.35     | 0.0  | 0.0       | 42.38 | 0.0   | 0.0        | 0.0  
| 42.38       |
+      | 3  | 31   | 26 June 2025     |                  |   0.0           | 
39.98         | 1.18     | 0.0  | 0.0       | 41.16 | 0.0   | 0.0        | 0.0  
| 41.16       |
+    Then Loan Repayment schedule has the following data in Total row:
+      | Principal due | Interest | Fees | Penalties | Due    | Paid | In 
advance | Late | Outstanding |
+      | 120.0         | 5.92     | 0.0  | 0.0       | 125.92 | 40.0 | 40.0     
  | 0.0  |  85.92      |
+    Then Loan Transactions tab has the following data:
+      | Transaction date | Transaction Type | Amount | Principal | Interest | 
Fees | Penalties | Loan Balance | Reverted | Replayed |
+      | 26 March 2025    | Disbursement     | 120.0  | 0.0       | 0.0      | 
0.0  | 0.0       | 120.0        | false    | false    |
+      | 27 March 2025    | Repayment        | 20.0   | 19.89     | 0.11     | 
0.0  | 0.0       | 100.11       | false    | false    |
+      | 27 March 2025    | Repayment        | 20.0   | 20.0      | 0.0      | 
0.0  | 0.0       |  80.11       | false    | false    |
+    And Customer makes "MERCHANT_ISSUED_REFUND" transaction with "AUTOPAY" 
payment type on "27 March 2025" with 120 EUR transaction amount and 
self-generated Idempotency key
+    Then Loan Repayment schedule has 3 periods, with the following data for 
periods:
+      | Nr | Days | Date             | Paid date        | Balance of loan | 
Principal due | Interest | Fees | Penalties | Due   | Paid  | In advance | Late 
| Outstanding |
+      |    |      | 26 March 2025    |                  | 120.0           |    
           |          | 0.0  |           | 0.0   | 0.0   |            |      |  
           |
+      | 1  | 31   | 26 April 2025    | 27 March 2025    | 80.11           | 
39.89         | 0.11     | 0.0  | 0.0       | 40.0  | 40.0  | 40.0       | 0.0  
|  0.0        |
+      | 2  | 30   | 26 May 2025      | 27 March 2025    | 42.38           | 
37.73         | 0.0      | 0.0  | 0.0       | 37.73 | 37.73 | 37.73      | 0.0  
|  0.0        |
+      | 3  | 31   | 26 June 2025     | 27 March 2025    |   0.0           | 
42.38         | 0.0      | 0.0  | 0.0       | 42.38 | 42.38 | 42.38      | 0.0  
|  0.0        |
+    Then Loan Repayment schedule has the following data in Total row:
+      | Principal due | Interest | Fees | Penalties | Due    | Paid   | In 
advance | Late | Outstanding |
+      | 120.0         | 0.11     | 0.0  | 0.0       | 120.11 | 120.11 | 120.11 
    | 0.0  |  0.0        |
+    Then Loan Transactions tab has the following data:
+      | Transaction date | Transaction Type       | Amount | Principal | 
Interest | Fees | Penalties | Loan Balance | Reverted | Replayed |
+      | 26 March 2025    | Disbursement           | 120.0  | 0.0       | 0.0   
   | 0.0  | 0.0       | 120.0        | false    | false    |
+      | 27 March 2025    | Repayment              | 20.0   | 19.89     | 0.11  
   | 0.0  | 0.0       | 100.11       | false    | false    |
+      | 27 March 2025    | Repayment              | 20.0   | 20.0      | 0.0   
   | 0.0  | 0.0       |  80.11       | false    | false    |
+      | 27 March 2025    | Merchant Issued Refund | 120.0  | 80.11     | 0.0   
   | 0.0  | 0.0       |   0.0        | false    | false    |
+      | 27 March 2025    | Interest Refund        | 0.11   | 0.0       | 0.0   
   | 0.0  | 0.0       |   0.0        | false    | false    |
+      | 27 March 2025    | Accrual Activity       | 0.11   | 0.0       | 0.11  
   | 0.0  | 0.0       |   0.0        | false    | false    |
+      | 28 March 2025    | Accrual                | 0.11   | 0.0       | 0.11  
   | 0.0  | 0.0       |   0.0        | false    | false    |
+    Then Loan status will be "OVERPAID"
+    Then Loan has 0 outstanding amount
+    Then Loan has 40 overpaid amount
+    And Admin makes Credit Balance Refund transaction on "28 March 2025" with 
20 EUR transaction amount
+    Then Loan Repayment schedule has 3 periods, with the following data for 
periods:
+      | Nr | Days | Date             | Paid date        | Balance of loan | 
Principal due | Interest | Fees | Penalties | Due   | Paid  | In advance | Late 
| Outstanding |
+      |    |      | 26 March 2025    |                  | 120.0           |    
           |          | 0.0  |           | 0.0   | 0.0   |            |      |  
           |
+      | 1  | 31   | 26 April 2025    | 27 March 2025    | 80.11           | 
39.89         | 0.11     | 0.0  | 0.0       | 40.0  | 40.0  | 40.0       | 0.0  
|  0.0        |
+      | 2  | 30   | 26 May 2025      | 27 March 2025    | 42.38           | 
37.73         | 0.0      | 0.0  | 0.0       | 37.73 | 37.73 | 37.73      | 0.0  
|  0.0        |
+      | 3  | 31   | 26 June 2025     | 27 March 2025    |   0.0           | 
42.38         | 0.0      | 0.0  | 0.0       | 42.38 | 42.38 | 42.38      | 0.0  
|  0.0        |
+    Then Loan Repayment schedule has the following data in Total row:
+      | Principal due | Interest | Fees | Penalties | Due    | Paid   | In 
advance | Late | Outstanding |
+      | 120.0         | 0.11     | 0.0  | 0.0       | 120.11 | 120.11 | 120.11 
    | 0.0  |  0.0        |
+    Then Loan Transactions tab has the following data:
+      | Transaction date | Transaction Type       | Amount | Principal | 
Interest | Fees | Penalties | Loan Balance | Reverted | Replayed |
+      | 26 March 2025    | Disbursement           | 120.0  | 0.0       | 0.0   
   | 0.0  | 0.0       | 120.0        | false    | false    |
+      | 27 March 2025    | Repayment              | 20.0   | 19.89     | 0.11  
   | 0.0  | 0.0       | 100.11       | false    | false    |
+      | 27 March 2025    | Repayment              | 20.0   | 20.0      | 0.0   
   | 0.0  | 0.0       |  80.11       | false    | false    |
+      | 27 March 2025    | Merchant Issued Refund | 120.0  | 80.11     | 0.0   
   | 0.0  | 0.0       |   0.0        | false    | false    |
+      | 27 March 2025    | Interest Refund        | 0.11   | 0.0       | 0.0   
   | 0.0  | 0.0       |   0.0        | false    | false    |
+      | 27 March 2025    | Accrual Activity       | 0.11   | 0.0       | 0.11  
   | 0.0  | 0.0       |   0.0        | false    | false    |
+      | 28 March 2025    | Accrual                | 0.11   | 0.0       | 0.11  
   | 0.0  | 0.0       |   0.0        | false    | false    |
+      | 28 March 2025    | Credit Balance Refund  | 20.0   | 0.0       | 0.0   
   | 0.0  | 0.0       |   0.0        | false    | false    |
+    Then Loan status will be "OVERPAID"
+    Then Loan has 0 outstanding amount
+    Then Loan has 20 overpaid amount
+    When Customer undo "1"th "Repayment" transaction made on "27 March 2025"
+    Then Loan Repayment schedule has 3 periods, with the following data for 
periods:
+      | Nr | Days | Date             | Paid date        | Balance of loan | 
Principal due | Interest | Fees | Penalties | Due   | Paid  | In advance | Late 
| Outstanding |
+      |    |      | 26 March 2025    |                  | 120.0           |    
           |          | 0.0  |           | 0.0   | 0.0   |            |      |  
           |
+      | 1  | 31   | 26 April 2025    | 27 March 2025    | 84.76           | 
35.24         | 0.11     | 0.0  | 0.0       | 35.35 | 35.35 | 35.35      | 0.0  
|  0.0        |
+      | 2  | 30   | 26 May 2025      | 27 March 2025    | 42.38           | 
42.38         | 0.0      | 0.0  | 0.0       | 42.38 | 42.38 | 42.38      | 0.0  
|  0.0        |
+      | 3  | 31   | 26 June 2025     | 27 March 2025    |   0.0           | 
42.38         | 0.0      | 0.0  | 0.0       | 42.38 | 42.38 | 42.38      | 0.0  
|  0.0        |
+    Then Loan Repayment schedule has the following data in Total row:
+      | Principal due | Interest | Fees | Penalties | Due    | Paid   | In 
advance | Late | Outstanding |
+      | 120.0         | 0.11     | 0.0  | 0.0       | 120.11 | 120.11 | 120.11 
    | 0.0  |  0.0        |
+    Then Loan Transactions tab has the following data:
+      | Transaction date | Transaction Type       | Amount | Principal | 
Interest | Fees | Penalties | Loan Balance | Reverted | Replayed |
+      | 26 March 2025    | Disbursement           | 120.0  | 0.0       | 0.0   
   | 0.0  | 0.0       | 120.0        | false    | false    |
+      | 27 March 2025    | Repayment              | 20.0   | 19.89     | 0.11  
   | 0.0  | 0.0       | 100.11       | true     | false    |
+      | 27 March 2025    | Repayment              | 20.0   | 19.89     | 0.11  
   | 0.0  | 0.0       | 100.11       | false    | true     |
+      | 27 March 2025    | Merchant Issued Refund | 120.0  | 100.11    | 0.0   
   | 0.0  | 0.0       |   0.0        | false    | true     |
+      | 27 March 2025    | Interest Refund        | 0.11   | 0.0       | 0.0   
   | 0.0  | 0.0       |   0.0        | false    | true     |
+      | 27 March 2025    | Accrual Activity       | 0.11   | 0.0       | 0.11  
   | 0.0  | 0.0       |   0.0        | false    | true     |
+      | 28 March 2025    | Accrual                | 0.11   | 0.0       | 0.11  
   | 0.0  | 0.0       |   0.0        | false    | false    |
+      | 28 March 2025    | Credit Balance Refund  | 20.0   | 0.0       | 0.0   
   | 0.0  | 0.0       |   0.0        | false    | false    |
+    Then Loan status will be "CLOSED_OBLIGATIONS_MET"
+    Then Loan has 0 outstanding amount
diff --git 
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/ReprocessLoanTransactionsServiceImpl.java
 
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/ReprocessLoanTransactionsServiceImpl.java
index 9d023d7296..afc9c0ed7d 100644
--- 
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/ReprocessLoanTransactionsServiceImpl.java
+++ 
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/ReprocessLoanTransactionsServiceImpl.java
@@ -60,7 +60,6 @@ public class ReprocessLoanTransactionsServiceImpl implements 
ReprocessLoanTransa
     public void reprocessTransactionsWithPostTransactionChecks(final Loan 
loan, final LocalDate transactionDate) {
         final ChangedTransactionDetail changedTransactionDetail = 
reprocessTransactionsAndFetchChangedTransactions(loan,
                 loan.retrieveListOfTransactionsForReprocessing());
-        loan.doPostLoanTransactionChecks(transactionDate, 
loan.getLoanLifecycleStateMachine());
         handleChangedDetail(changedTransactionDetail);
     }
 
diff --git 
a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/ProgressiveLoanScheduleGenerator.java
 
b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/ProgressiveLoanScheduleGenerator.java
index b495af02f5..6673d77a80 100644
--- 
a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/ProgressiveLoanScheduleGenerator.java
+++ 
b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/ProgressiveLoanScheduleGenerator.java
@@ -275,8 +275,7 @@ public class ProgressiveLoanScheduleGenerator implements 
LoanScheduleGenerator {
                 continue;
             }
 
-            Money outstandingBalance = 
emiCalculator.getOutstandingLoanBalanceOfPeriod(interestScheduleModel, 
periodDueDate,
-                    disbursementDate);
+            Money outstandingBalance = 
emiCalculator.getOutstandingLoanBalanceOfPeriod(interestScheduleModel, 
disbursementDate);
 
             final Money disbursedAmount = 
Money.of(loanApplicationTerms.getCurrency(), disbursementData.getPrincipal(), 
mc);
             final LoanScheduleModelDisbursementPeriod disbursementPeriod = 
LoanScheduleModelDisbursementPeriod
diff --git 
a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/calc/EMICalculator.java
 
b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/calc/EMICalculator.java
index 312a116b7f..24487c499a 100644
--- 
a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/calc/EMICalculator.java
+++ 
b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/calc/EMICalculator.java
@@ -119,8 +119,7 @@ public interface EMICalculator {
     Money getPeriodInterestTillDate(@NotNull 
ProgressiveLoanInterestScheduleModel scheduleModel, @NotNull LocalDate 
periodDueDate,
             @NotNull LocalDate targetDate, boolean includeChargebackInterest);
 
-    Money 
getOutstandingLoanBalanceOfPeriod(ProgressiveLoanInterestScheduleModel 
interestScheduleModel, LocalDate repaymentPeriodDueDate,
-            LocalDate targetDate);
+    Money 
getOutstandingLoanBalanceOfPeriod(ProgressiveLoanInterestScheduleModel 
interestScheduleModel, LocalDate targetDate);
 
     OutstandingDetails 
getOutstandingAmountsTillDate(ProgressiveLoanInterestScheduleModel model, 
LocalDate targetDate);
 
diff --git 
a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/calc/ProgressiveEMICalculator.java
 
b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/calc/ProgressiveEMICalculator.java
index 03be2b04cd..35c1b5c474 100644
--- 
a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/calc/ProgressiveEMICalculator.java
+++ 
b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/calc/ProgressiveEMICalculator.java
@@ -256,7 +256,7 @@ public final class ProgressiveEMICalculator implements 
EMICalculator {
     public PeriodDueDetails getDueAmounts(@NotNull 
ProgressiveLoanInterestScheduleModel scheduleModel, @NotNull LocalDate 
periodDueDate,
             @NotNull LocalDate targetDate) {
         ProgressiveLoanInterestScheduleModel recalculatedScheduleModelTillDate 
= recalculateScheduleModelTillDate(scheduleModel,
-                periodDueDate, targetDate);
+                targetDate);
         RepaymentPeriod repaymentPeriod = 
recalculatedScheduleModelTillDate.findRepaymentPeriodByDueDate(periodDueDate).orElseThrow();
         long notFullyRepaidRepaymentPeriodCount = 
recalculatedScheduleModelTillDate.repaymentPeriods().stream()
                 .filter(rp -> !rp.isFullyPaid()).count();
@@ -265,6 +265,11 @@ public final class ProgressiveEMICalculator implements 
EMICalculator {
         if (!targetDate.isAfter(repaymentPeriod.getFromDate())) {
             if (multiplePeriodIsUnpaid) {
                 repaymentPeriod.setEmi(repaymentPeriod.getOriginalEmi());
+                Money totalOutstandingPrincipal = 
recalculatedScheduleModelTillDate.getTotalOutstandingPrincipal();
+                Money outstandingPrincipal = 
repaymentPeriod.getOutstandingPrincipal();
+                // If there are less outstanding principal than anticipated
+                Money emiAdjustment = 
MathUtil.negativeToZero(outstandingPrincipal.minus(totalOutstandingPrincipal));
+                
repaymentPeriod.setEmi(repaymentPeriod.getEmi().minus(emiAdjustment));
             } else if (repaymentPeriod.isFullyPaid() && onePeriodIsUnpaid) {
                 
repaymentPeriod.setEmi(MathUtil.min(repaymentPeriod.getOriginalEmi(), //
                         
recalculatedScheduleModelTillDate.getTotalDuePrincipal() //
@@ -285,7 +290,7 @@ public final class ProgressiveEMICalculator implements 
EMICalculator {
     public Money getPeriodInterestTillDate(@NotNull 
ProgressiveLoanInterestScheduleModel scheduleModel, @NotNull LocalDate 
periodDueDate,
             @NotNull LocalDate targetDate, boolean includeChargebackInterest) {
         ProgressiveLoanInterestScheduleModel recalculatedScheduleModelTillDate 
= recalculateScheduleModelTillDate(scheduleModel,
-                periodDueDate, targetDate);
+                targetDate);
         RepaymentPeriod repaymentPeriod = 
recalculatedScheduleModelTillDate.findRepaymentPeriodByDueDate(periodDueDate).orElseThrow();
         return includeChargebackInterest ? 
repaymentPeriod.getCalculatedDueInterest()
                 : 
repaymentPeriod.getCalculatedDueInterest().minus(repaymentPeriod.getChargebackInterest(),
@@ -293,11 +298,18 @@ public final class ProgressiveEMICalculator implements 
EMICalculator {
     }
 
     @Override
-    public Money 
getOutstandingLoanBalanceOfPeriod(ProgressiveLoanInterestScheduleModel 
scheduleModel, LocalDate periodDueDate,
-            LocalDate targetDate) {
+    public Money 
getOutstandingLoanBalanceOfPeriod(ProgressiveLoanInterestScheduleModel 
scheduleModel, LocalDate targetDate) {
         ProgressiveLoanInterestScheduleModel recalculatedScheduleModelTillDate 
= recalculateScheduleModelTillDate(scheduleModel,
-                periodDueDate, targetDate);
-        RepaymentPeriod repaymentPeriod = 
recalculatedScheduleModelTillDate.findRepaymentPeriodByDueDate(periodDueDate).orElseThrow();
+                targetDate);
+        RepaymentPeriod repaymentPeriod = 
recalculatedScheduleModelTillDate.findRepaymentPeriod(targetDate).orElseGet(() 
-> {
+            // If target date is after maturity date
+            if 
(targetDate.isAfter(recalculatedScheduleModelTillDate.getLastRepaymentPeriod().getDueDate()))
 {
+                return 
recalculatedScheduleModelTillDate.getLastRepaymentPeriod();
+            } else {
+                // if target date is before 1st disbursement date, we use 1st 
repayment period
+                return 
recalculatedScheduleModelTillDate.repaymentPeriods().get(0);
+            }
+        });
 
         return repaymentPeriod.getOutstandingLoanBalance();
     }
@@ -343,43 +355,58 @@ public final class ProgressiveEMICalculator implements 
EMICalculator {
 
     @NotNull
     private ProgressiveLoanInterestScheduleModel 
recalculateScheduleModelTillDate(
-            @NotNull ProgressiveLoanInterestScheduleModel scheduleModel, 
@NotNull LocalDate periodDueDate, @NotNull LocalDate targetDate) {
+            @NotNull ProgressiveLoanInterestScheduleModel scheduleModel, 
@NotNull LocalDate targetDate) {
         MathContext mc = scheduleModel.mc();
         ProgressiveLoanInterestScheduleModel scheduleModelCopy = 
scheduleModel.deepCopy(mc);
-        RepaymentPeriod repaymentPeriod = 
scheduleModelCopy.findRepaymentPeriodByDueDate(periodDueDate).orElseThrow();
-
-        LocalDate adjustedTargetDate = targetDate;
-        InterestPeriod interestPeriod;
-        if (!targetDate.isAfter(repaymentPeriod.getFromDate())) {
-            interestPeriod = repaymentPeriod.getFirstInterestPeriod();
-            adjustedTargetDate = repaymentPeriod.getFromDate();
-        } else if (targetDate.isAfter(repaymentPeriod.getDueDate())) {
-            interestPeriod = repaymentPeriod.getLastInterestPeriod();
-            adjustedTargetDate = repaymentPeriod.getDueDate();
+        boolean isBeforeFirstDisbursement = 
targetDate.isBefore(scheduleModelCopy.repaymentPeriods().get(0).getFromDate());
+        boolean isAfterMaturityDate = 
!targetDate.isBefore(scheduleModelCopy.getLastRepaymentPeriod().getDueDate());
+        if (isBeforeFirstDisbursement) {
+            scheduleModelCopy.repaymentPeriods().forEach(rp -> 
rp.getInterestPeriods().clear());
+            return scheduleModelCopy;
+        } else if (isAfterMaturityDate) {
+            return scheduleModelCopy;
         } else {
-            interestPeriod = 
repaymentPeriod.findInterestPeriod(targetDate).orElseThrow();
-        }
-        // TODO use findInterestPeriod
-        scheduleModelCopy.findRepaymentPeriod(targetDate)//
-                .flatMap(rp -> rp.findInterestPeriod(targetDate)).ifPresent(ip 
-> ip.setDueDate(targetDate)); //
-        interestPeriod.setDueDate(adjustedTargetDate);
-        int index = 
repaymentPeriod.getInterestPeriods().indexOf(interestPeriod);
-        int nextIdx = index + 1;
-        boolean thereIsInterestPeriodFromDateOnTargetDate = 
repaymentPeriod.getInterestPeriods().size() > nextIdx
-                && 
repaymentPeriod.getInterestPeriods().get(nextIdx).getFromDate().isEqual(targetDate);
-        if (thereIsInterestPeriodFromDateOnTargetDate) {
-            // NOTE: If there is a next interest period with fromDate on the 
target date
-            // then the related chargeback amounts comes from the next 
interest period too.
-            InterestPeriod nextInterestPeriod = 
repaymentPeriod.getInterestPeriods().get(nextIdx);
-            
interestPeriod.addChargebackPrincipalAmount(nextInterestPeriod.getChargebackPrincipal());
-            
interestPeriod.addChargebackInterestAmount(nextInterestPeriod.getChargebackInterest());
+            RepaymentPeriod repaymentPeriod = 
scheduleModelCopy.findRepaymentPeriod(targetDate).orElseThrow();
+
+            scheduleModelCopy.repaymentPeriods().forEach(rp -> {
+                if (rp.getDueDate().isAfter(targetDate)) {
+                    if (rp.equals(repaymentPeriod)) {
+                        rp.findInterestPeriod(targetDate).ifPresent(ip -> {
+                            ip.setDueDate(targetDate);
+                            int index = rp.getInterestPeriods().indexOf(ip);
+                            int nextIdx = index + 1;
+                            boolean thereIsInterestPeriodFromDateOnTargetDate 
= ip.getRepaymentPeriod().getInterestPeriods()
+                                    .size() > nextIdx;
+                            if (thereIsInterestPeriodFromDateOnTargetDate) {
+                                // NOTE: If there is a next interest period 
with fromDate on the target date
+                                // then the related chargeback amounts comes 
from the next interest period too.
+                                InterestPeriod nextInterestPeriod = 
ip.getRepaymentPeriod().getInterestPeriods().get(nextIdx);
+                                
ip.addChargebackPrincipalAmount(nextInterestPeriod.getChargebackPrincipal());
+                                
ip.addChargebackInterestAmount(nextInterestPeriod.getChargebackInterest());
+                            }
+                            ip.getRepaymentPeriod().getInterestPeriods()
+                                    .subList(nextIdx, 
ip.getRepaymentPeriod().getInterestPeriods().size()).clear();
+                        });
+                    } else if (rp.getPrevious().isPresent() && 
rp.getPrevious().get().equals(repaymentPeriod)
+                            && 
(rp.getInterestPeriods().get(0).getChargebackInterest().isGreaterThanZero()
+                                    || 
rp.getInterestPeriods().get(0).getChargebackPrincipal().isGreaterThanZero())) {
+                        // NOTE: we need to check whether there is chargeback 
on the 1st interest period of the next
+                        // period
+                        // if so, we need to retain that interest period, but 
need to update due date to match with from
+                        // date -> 0 interest
+                        
rp.getInterestPeriods().get(0).setDueDate(rp.getInterestPeriods().get(0).getFromDate());
+                        if (rp.getInterestPeriods().size() > 1) {
+                            rp.getInterestPeriods().subList(1, 
rp.getInterestPeriods().size()).clear();
+                        }
+                    } else {
+                        rp.getInterestPeriods().clear();
+                    }
+                }
+            });
+            
calculateRateFactorForPeriods(scheduleModelCopy.repaymentPeriods(), 
scheduleModelCopy);
+            calculateOutstandingBalance(scheduleModelCopy);
+            calculateLastUnpaidRepaymentPeriodEMI(scheduleModelCopy);
         }
-        repaymentPeriod.getInterestPeriods().subList(nextIdx, 
repaymentPeriod.getInterestPeriods().size()).clear();
-        scheduleModelCopy.repaymentPeriods().forEach(rp -> 
rp.getInterestPeriods().removeIf(ip -> ip.getDueDate().isAfter(targetDate)));
-        calculateRateFactorForPeriods(scheduleModelCopy.repaymentPeriods(), 
scheduleModelCopy);
-        calculateOutstandingBalance(scheduleModelCopy);
-        calculateLastUnpaidRepaymentPeriodEMI(scheduleModelCopy);
-
         return scheduleModelCopy;
     }
 
diff --git 
a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/calc/data/ProgressiveLoanInterestScheduleModel.java
 
b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/calc/data/ProgressiveLoanInterestScheduleModel.java
index e29bfc51c1..3a74d32f23 100644
--- 
a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/calc/data/ProgressiveLoanInterestScheduleModel.java
+++ 
b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/calc/data/ProgressiveLoanInterestScheduleModel.java
@@ -42,6 +42,7 @@ import java.util.stream.Collectors;
 import lombok.Data;
 import lombok.experimental.Accessors;
 import org.apache.fineract.infrastructure.core.service.DateUtils;
+import org.apache.fineract.infrastructure.core.service.MathUtil;
 import org.apache.fineract.organisation.monetary.domain.Money;
 import org.apache.fineract.portfolio.loanaccount.data.LoanTermVariationsData;
 import org.apache.fineract.portfolio.loanaccount.domain.LoanTermVariationType;
@@ -338,6 +339,14 @@ public class ProgressiveLoanInterestScheduleModel {
         return 
repaymentPeriods().stream().map(RepaymentPeriod::getChargebackPrincipal).reduce(zero,
 Money::plus);
     }
 
+    public Money getTotalOutstandingPrincipal() {
+        return 
MathUtil.negativeToZero(getTotalDuePrincipal().minus(getTotalPaidPrincipal()));
+    }
+
+    public Money getTotalOutstandingInterest() {
+        return 
MathUtil.negativeToZero(getTotalDueInterest().minus(getTotalPaidInterest()));
+    }
+
     public Optional<RepaymentPeriod> findRepaymentPeriod(@NotNull LocalDate 
transactionDate) {
         return repaymentPeriods.stream() //
                 .filter(period -> isInPeriod(transactionDate, 
period.getFromDate(), period.getDueDate(), period.isFirstRepaymentPeriod()))//
diff --git 
a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/calc/data/RepaymentPeriod.java
 
b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/calc/data/RepaymentPeriod.java
index 8aa2e595da..5390c17058 100644
--- 
a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/calc/data/RepaymentPeriod.java
+++ 
b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/calc/data/RepaymentPeriod.java
@@ -318,4 +318,8 @@ public final class RepaymentPeriod {
     public boolean isFirstRepaymentPeriod() {
         return previous == null;
     }
+
+    public Money getOutstandingPrincipal() {
+        return getDuePrincipal().minus(getPaidPrincipal());
+    }
 }
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanChargeWritePlatformServiceImpl.java
 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanChargeWritePlatformServiceImpl.java
index cfb030d17e..d9b10d53ca 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanChargeWritePlatformServiceImpl.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanChargeWritePlatformServiceImpl.java
@@ -302,7 +302,7 @@ public class LoanChargeWritePlatformServiceImpl implements 
LoanChargeWritePlatfo
             } else {
                 reprocessLoanTransactionsService.reprocessTransactions(loan);
             }
-
+            loan.doPostLoanTransactionChecks(transactionDate, 
loan.getLoanLifecycleStateMachine());
             loan = 
loanAccountService.saveAndFlushLoanWithDataIntegrityViolationChecks(loan);
         }
 
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/adjustment/LoanAdjustmentServiceImpl.java
 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/adjustment/LoanAdjustmentServiceImpl.java
index a57a3954c2..3d1215353f 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/adjustment/LoanAdjustmentServiceImpl.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/adjustment/LoanAdjustmentServiceImpl.java
@@ -184,8 +184,8 @@ public class LoanAdjustmentServiceImpl implements 
LoanAdjustmentService {
         if (!transactionIds.isEmpty()) {
             
this.accountTransfersWritePlatformService.reverseTransfersWithFromAccountTransactions(transactionIds,
                     PortfolioAccountType.LOAN);
-            loan.updateLoanSummaryAndStatus();
         }
+        loan.updateLoanSummaryAndStatus();
 
         
loanAccrualsProcessingService.processAccrualsOnInterestRecalculation(loan, 
loan.isInterestBearingAndInterestRecalculationEnabled(),
                 false);
@@ -267,8 +267,6 @@ public class LoanAdjustmentServiceImpl implements 
LoanAdjustmentService {
             writeOffTransaction.reverse();
         }
 
-        loan.updateLoanSummaryAndStatus();
-
         if (newTransactionDetail.isRepaymentLikeType() || 
newTransactionDetail.isInterestWaiver()) {
             
loanDownPaymentHandlerService.handleRepaymentOrRecoveryOrWaiverTransaction(loan,
 newTransactionDetail,
                     loanLifecycleStateMachine, transactionForAdjustment, 
scheduleGeneratorDTO);


Reply via email to