You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@fineract.apache.org by ad...@apache.org on 2025/11/13 17:15:24 UTC

(fineract) branch develop updated: FINERACT-2326: Fix duplicate reverse replay of ‘Accrual Activity’ during reprocessing

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 da1f310d43 FINERACT-2326: Fix duplicate reverse replay of ‘Accrual Activity’ during reprocessing
da1f310d43 is described below

commit da1f310d43264e622e1aab669b058fe3ed531c61
Author: Adam Saghy <ad...@gmail.com>
AuthorDate: Wed Nov 12 18:23:03 2025 +0100

    FINERACT-2326: Fix duplicate reverse replay of ‘Accrual Activity’ during reprocessing
---
 .../fineract/test/stepdef/loan/LoanStepDef.java    | 23 ++++++++++++
 .../test/resources/features/LoanRepayment.feature  |  2 +-
 .../test/resources/features/LoanReschedule.feature | 42 ++++++++++++++++++++++
 ...dvancedPaymentScheduleTransactionProcessor.java |  2 +-
 4 files changed, 67 insertions(+), 2 deletions(-)

diff --git a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/loan/LoanStepDef.java b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/loan/LoanStepDef.java
index fd97188e11..34061c7176 100644
--- a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/loan/LoanStepDef.java
+++ b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/loan/LoanStepDef.java
@@ -27,6 +27,7 @@ import static org.apache.fineract.test.factory.LoanProductsRequestFactory.CHARGE
 import static org.apache.fineract.test.factory.LoanProductsRequestFactory.LOCALE_EN;
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.awaitility.Awaitility.await;
+import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertNotNull;
 import static org.junit.jupiter.api.Assertions.assertNull;
 import static org.junit.jupiter.api.Assertions.assertTrue;
@@ -6095,4 +6096,26 @@ public class LoanStepDef extends AbstractStepDef {
         }
         return actualValues;
     }
+
+    @Then("In Loan Transactions the {string}th Transaction of {string} on {string} has {string} relationship with type={string}")
+    public void inLoanTransactionsTheThTransactionOfOnHasRelationshipWithTypeREPLAYED(String nthTransactionFromStr, String transactionType,
+            String transactionDate, String numberOfRelations, String relationshipType) throws IOException {
+        final Response<PostLoansResponse> loanCreateResponse = testContext().get(TestContextKey.LOAN_CREATE_RESPONSE);
+        final long loanId = loanCreateResponse.body().getLoanId();
+
+        final Response<GetLoansLoanIdResponse> loanDetailsResponse = loansApi.retrieveLoan(loanId, false, "transactions", "", "").execute();
+        ErrorHelper.checkSuccessfulApiCall(loanDetailsResponse);
+
+        final List<GetLoansLoanIdTransactions> transactions = loanDetailsResponse.body().getTransactions();
+        final int nthTransactionFrom = nthTransactionFromStr == null ? transactions.size() - 1
+                : Integer.parseInt(nthTransactionFromStr) - 1;
+        final GetLoansLoanIdTransactions transactionFrom = transactions.stream()
+                .filter(t -> transactionType.equals(t.getType().getValue()) && transactionDate.equals(FORMATTER.format(t.getDate())))
+                .toList().get(nthTransactionFrom);
+
+        final List<GetLoansLoanIdLoanTransactionRelation> relationshipOptional = transactionFrom.getTransactionRelations().stream()
+                .filter(r -> r.getRelationType().equals(relationshipType)).toList();
+
+        assertEquals(Integer.valueOf(numberOfRelations), relationshipOptional.size(), "Missed relationship for transaction");
+    }
 }
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 5388359541..ab790cd0b9 100644
--- a/fineract-e2e-tests-runner/src/test/resources/features/LoanRepayment.feature
+++ b/fineract-e2e-tests-runner/src/test/resources/features/LoanRepayment.feature
@@ -4562,7 +4562,7 @@ Feature: LoanRepayment
       | 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     |
+      | 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 "CLOSED_OBLIGATIONS_MET"
diff --git a/fineract-e2e-tests-runner/src/test/resources/features/LoanReschedule.feature b/fineract-e2e-tests-runner/src/test/resources/features/LoanReschedule.feature
index 6e56f71f0f..424461e376 100644
--- a/fineract-e2e-tests-runner/src/test/resources/features/LoanReschedule.feature
+++ b/fineract-e2e-tests-runner/src/test/resources/features/LoanReschedule.feature
@@ -1132,3 +1132,45 @@ Feature: LoanReschedule
       | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed |
       | 24 July 2025     | Disbursement     | 500.0  | 0.0       | 0.0      | 0.0  | 0.0       | 500.0        | false    | false    |
     Then LoanRescheduledDueAdjustScheduleBusinessEvent is raised on "24 July 2025"
+
+  @TestRailId:C4225
+  Scenario: Verify after reschedule of loan by changing due date, the last Accrual Activity transaction is reversed-replayed only once
+    When Admin sets the business date to "05 September 2025"
+    And Admin creates a client with random data
+    And 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_PYMNT_INTEREST_DAILY_EMI_ACTUAL_ACTUAL_ACCRUAL_ACTIVITY | 31 December 2024   | 1111            | 24.99                      | DECLINING_BALANCE | DAILY                       | EQUAL_INSTALLMENTS | 24                 | MONTHS                | 1              | MONTHS                 | 24                  | 0                       | 0                      | 0                    | ADVANCED_PAYMENT_ALLOCATION |
+    And Admin successfully approves the loan on "31 December 2024" with "1111" amount and expected disbursement date on "31 December 2024"
+    And Admin successfully disburse the loan on "31 December 2024" with "1111" EUR transaction amount
+    And Customer makes "AUTOPAY" repayment on "31 January 2025" with 59.26 EUR transaction amount
+    And Customer makes "AUTOPAY" repayment on "28 February 2025" with 59.26 EUR transaction amount
+    And Customer makes "AUTOPAY" repayment on "31 March 2025" with 59.26 EUR transaction amount
+    And Customer makes "AUTOPAY" repayment on "30 April 2025" with 59.26 EUR transaction amount
+    And Customer makes "AUTOPAY" repayment on "30 May 2025" with 59.26 EUR transaction amount
+    And Customer makes "AUTOPAY" repayment on "29 June 2025" with 59.26 EUR transaction amount
+    And Customer makes "AUTOPAY" repayment on "31 July 2025" with 59.26 EUR transaction amount
+    And Customer makes "AUTOPAY" repayment on "31 August 2025" with 59.26 EUR transaction amount
+    When Admin sets the business date to "06 September 2025"
+    When Admin runs inline COB job for Loan
+    When Admin sets the business date to "11 September 2025"
+    When Customer undo "1"th "Repayment" transaction made on "31 August 2025"
+    When Admin sets the business date to "30 September 2025"
+    And Customer makes "AUTOPAY" repayment on "30 September 2025" with 59.26 EUR transaction amount
+    And Customer makes "AUTOPAY" repayment on "30 September 2025" with 59.26 EUR transaction amount
+    When Admin runs inline COB job for Loan
+    When Admin sets the business date to "10 October 2025"
+    When Customer undo "1"th "Repayment" transaction made on "30 September 2025"
+    When Customer undo "2"th "Repayment" transaction made on "30 September 2025"
+    When Admin sets the business date to "16 October 2025"
+    And Customer makes "AUTOPAY" repayment on "16 October 2025" with 60 EUR transaction amount
+    When Admin sets the business date to "31 October 2025"
+    And Customer makes "AUTOPAY" repayment on "31 October 2025" with 59.26 EUR transaction amount
+    And Customer makes "AUTOPAY" repayment on "31 October 2025" with 58.52 EUR transaction amount
+    When Admin runs inline COB job for Loan
+    When Admin sets the business date to "10 November 2025"
+    When Admin runs inline COB job for Loan
+    When Admin creates and approves Loan reschedule with the following data:
+      | rescheduleFromDate | submittedOnDate  | adjustedDueDate | graceOnPrincipal | graceOnInterest | extraTerms | newInterestRate |
+      | 30 November 2025   | 10 November 2025 | 31 January 2026 |                  |                 |            |                 |
+    Then In Loan Transactions the "1"th Transaction of "Accrual Activity" on "31 October 2025" has "1" relationship with type="REPLAYED"
+
diff --git a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/AdvancedPaymentScheduleTransactionProcessor.java b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/AdvancedPaymentScheduleTransactionProcessor.java
index bf06feaa5c..7898ba1703 100644
--- a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/AdvancedPaymentScheduleTransactionProcessor.java
+++ b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/AdvancedPaymentScheduleTransactionProcessor.java
@@ -1191,7 +1191,7 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
         boolean alreadyProcessed = changedTransactionDetail.getTransactionChanges().stream().map(TransactionChangeData::getNewTransaction)
                 .anyMatch(lt -> !lt.equals(newTransaction) && lt.getTransactionDate().equals(oldTransaction.getTransactionDate()));
         boolean amountMatch = LoanTransaction.transactionAmountsMatch(currency, oldTransaction, newTransaction);
-        if (!alreadyProcessed && amountMatch) {
+        if ((!alreadyProcessed && amountMatch) || newTransaction.isAccrualActivity()) {
             if (!oldTransaction.getTypeOf().isWaiveCharges()) { // WAIVE_CHARGES is not reprocessed
                 oldTransaction
                         .updateLoanTransactionToRepaymentScheduleMappings(newTransaction.getLoanTransactionToRepaymentScheduleMappings());