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 fd97188e1..34061c717 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 loanCreateResponse = testContext().get(TestContextKey.LOAN_CREATE_RESPONSE); + final long loanId = loanCreateResponse.body().getLoanId(); + + final Response loanDetailsResponse = loansApi.retrieveLoan(loanId, false, "transactions", "", "").execute(); + ErrorHelper.checkSuccessfulApiCall(loanDetailsResponse); + + final List 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 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 538835954..ab790cd0b 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 6e56f71f0..424461e37 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 bf06feaa5..7898ba170 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());