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 <[email protected]>
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());