From 140d368a8df05270045626541c37f226fe2fce63 Mon Sep 17 00:00:00 2001 From: mariiaKraievska Date: Mon, 16 Feb 2026 13:10:18 +0200 Subject: [PATCH] FINERACT-2389: Fix undo repayment with linked chargeback error --- .../stepdef/loan/LoanRepaymentStepDef.java | 30 ++++++++++++ .../test/stepdef/loan/LoanStepDef.java | 20 -------- .../resources/features/LoanRepayment.feature | 47 +++++++++++++++++++ .../resources/features/LoanWriteOff.feature | 8 ++-- ...WritePlatformServiceJpaRepositoryImpl.java | 6 +-- .../LoanTransactionChargebackTest.java | 2 +- ...eOffWithAdvancedPaymentAllocationTest.java | 4 +- 7 files changed, 87 insertions(+), 30 deletions(-) diff --git a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/loan/LoanRepaymentStepDef.java b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/loan/LoanRepaymentStepDef.java index 25ec08a9b3e..113f29ca8e3 100644 --- a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/loan/LoanRepaymentStepDef.java +++ b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/loan/LoanRepaymentStepDef.java @@ -679,6 +679,36 @@ private void adjustNthRepaymentWithExternalOwnerCheck(String nthItemStr, String loanTransactionAdjustmentDataV1 -> loanTransactionAdjustmentDataV1.getNewTransactionDetail().getExternalOwnerId()) .isEqualTo(externalOwnerId); } + } + + @Then("Customer undo {string}th transaction made on {string} results a {int} error and {string} error message") + public void undoTransactionResultsError(final String nthItemStr, final String transactionDate, final int errorCodeExpected, + final String errorMessageCode) { + eventStore.reset(); + final DateTimeFormatter formatter = DateTimeFormatter.ofPattern(DATE_FORMAT); + final PostLoansResponse loanResponse = testContext().get(TestContextKey.LOAN_CREATE_RESPONSE); + assert loanResponse != null; + final long loanId = loanResponse.getLoanId(); + final List transactions = ok( + () -> fineractClient.loans().retrieveLoan(loanId, Map.of("associations", "transactions"))) + .getTransactions(); + final int nthItem = Integer.parseInt(nthItemStr) - 1; + assert transactions != null; + final GetLoansLoanIdTransactions targetTransaction = transactions.stream().filter(t -> { + assert t.getDate() != null; + return transactionDate.equals(formatter.format(t.getDate())); + }).toList().get(nthItem); + + final PostLoansLoanIdTransactionsTransactionIdRequest transactionUndoRequest = LoanRequestFactory.defaultTransactionUndoRequest() + .transactionDate(transactionDate); + + final CallFailedRuntimeException exception = fail(() -> fineractClient.loanTransactions().adjustLoanTransaction(loanId, + targetTransaction.getId(), transactionUndoRequest, Map.of())); + + assertThat(exception.getStatus()).as(ErrorMessageHelper.wrongErrorCode(exception.getStatus(), errorCodeExpected)) + .isEqualTo(errorCodeExpected); + assertThat(exception.getDeveloperMessage()) + .as(ErrorMessageHelper.wrongErrorMessage(exception.getDeveloperMessage(), errorMessageCode)).contains(errorMessageCode); } } 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 23dcb365d3a..8b5a5470d64 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 @@ -3319,26 +3319,6 @@ public void writeOffLoan(String transactionDate, String writeOffReason) { testContext().set(TestContextKey.LOAN_WRITE_OFF_RESPONSE, writeOffResponse); } - @Then("Admin fails to undo {string}th transaction made on {string}") - public void undoTransaction(String nthTransaction, String transactionDate) { - DateTimeFormatter formatter = DateTimeFormatter.ofPattern(DATE_FORMAT); - PostLoansResponse loanResponse = testContext().get(TestContextKey.LOAN_CREATE_RESPONSE); - long loanId = loanResponse.getLoanId(); - List transactions = ok(() -> fineractClient.loans().retrieveLoan(loanId, - Map.of("staffInSelectedOfficeOnly", "false", "associations", "transactions"))).getTransactions(); - - int nthItem = Integer.parseInt(nthTransaction) - 1; - GetLoansLoanIdTransactions targetTransaction = transactions.stream() - .filter(t -> transactionDate.equals(formatter.format(t.getDate()))).toList().get(nthItem); - - PostLoansLoanIdTransactionsTransactionIdRequest transactionUndoRequest = LoanRequestFactory.defaultTransactionUndoRequest() - .transactionDate(transactionDate); - - CallFailedRuntimeException exception = fail(() -> fineractClient.loanTransactions().adjustLoanTransaction(loanId, - targetTransaction.getId(), transactionUndoRequest, Map.of())); - assertThat(exception.getStatus()).as(ErrorMessageHelper.dateFailureErrorCodeMsg()).isEqualTo(503); - } - @Then("Loan {string} repayment transaction on {string} with {double} EUR transaction amount results in error") public void loanTransactionWithErrorCheck(String repaymentType, String transactionDate, double transactionAmount) { PostLoansResponse loanResponse = testContext().get(TestContextKey.LOAN_CREATE_RESPONSE); 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 2026b67ac1f..53e9ffee7e1 100644 --- a/fineract-e2e-tests-runner/src/test/resources/features/LoanRepayment.feature +++ b/fineract-e2e-tests-runner/src/test/resources/features/LoanRepayment.feature @@ -6704,3 +6704,50 @@ Feature: LoanRepayment | 27 October 2025 | Repayment | 0.4 | 0.4 | 0.0 | 0.0 | 0.0 | 0.0 | false | false | Then Loan is closed with zero outstanding balance and it's all installments have obligations met + @TestRailId:C4648 + Scenario: Verify repayment undo with linked chargeback fails with proper error + When Admin sets the business date to "01 January 2024" + 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_PYMNT_INTEREST_DAILY_INTEREST_RECALC_EMI_360_30_CHARGEBACK_INTEREST_PENALTY_FEE_PRINCIPAL | 01 January 2024 | 100 | 7 | DECLINING_BALANCE | DAILY | EQUAL_INSTALLMENTS | 6 | MONTHS | 1 | MONTHS | 6 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | + And Admin successfully approves the loan on "01 January 2024" with "100" amount and expected disbursement date on "01 January 2024" + When Admin successfully disburse the loan on "01 January 2024" with "100" EUR transaction amount + Then Loan Repayment schedule has 6 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 | + | | | 01 January 2024 | | 100.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 31 | 01 February 2024 | | 83.57 | 16.43 | 0.58 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 2 | 29 | 01 March 2024 | | 67.05 | 16.52 | 0.49 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 3 | 31 | 01 April 2024 | | 50.43 | 16.62 | 0.39 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 4 | 30 | 01 May 2024 | | 33.71 | 16.72 | 0.29 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 5 | 31 | 01 June 2024 | | 16.9 | 16.81 | 0.2 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 6 | 30 | 01 July 2024 | | 0.0 | 16.9 | 0.1 | 0.0 | 0.0 | 17.0 | 0.0 | 0.0 | 0.0 | 17.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 100.0 | 2.05 | 0.0 | 0.0 | 102.05 | 0.0 | 0.0 | 0.0 | 102.05 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | + | 01 January 2024 | Disbursement | 100.0 | 0.0 | 0.0 | 0.0 | 0.0 | 100.0 | false | + When Admin sets the business date to "01 February 2024" + And Customer makes "AUTOPAY" repayment on "01 February 2024" with 17.01 EUR transaction amount + Then Loan Repayment schedule has 6 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 | + | | | 01 January 2024 | | 100.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 31 | 01 February 2024 | 01 February 2024 | 83.57 | 16.43 | 0.58 | 0.0 | 0.0 | 17.01 | 17.01 | 0.0 | 0.0 | 0.0 | + | 2 | 29 | 01 March 2024 | | 67.05 | 16.52 | 0.49 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 3 | 31 | 01 April 2024 | | 50.43 | 16.62 | 0.39 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 4 | 30 | 01 May 2024 | | 33.71 | 16.72 | 0.29 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 5 | 31 | 01 June 2024 | | 16.9 | 16.81 | 0.2 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 6 | 30 | 01 July 2024 | | 0.0 | 16.9 | 0.1 | 0.0 | 0.0 | 17.0 | 0.0 | 0.0 | 0.0 | 17.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 100.0 | 2.05 | 0.0 | 0.0 | 102.05 | 17.01 | 0.0 | 0.0 | 85.04 | + Then Loan Transactions tab has the following data: + | 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 | 17.01 | 16.43 | 0.58 | 0.0 | 0.0 | 83.57 | false | false | + When Admin sets the business date to "15 March 2024" + When Admin makes "REPAYMENT_ADJUSTMENT_CHARGEBACK" chargeback with 17.01 EUR transaction amount + Then Customer undo "1"th transaction made on "01 February 2024" results a 403 error and "update not allowed as loan transaction is linked to other transactions" error message + When Loan Pay-off is made on "15 March 2024" + Then Loan is closed with zero outstanding balance and it's all installments have obligations met diff --git a/fineract-e2e-tests-runner/src/test/resources/features/LoanWriteOff.feature b/fineract-e2e-tests-runner/src/test/resources/features/LoanWriteOff.feature index 02d7c09a803..a9d245a792a 100644 --- a/fineract-e2e-tests-runner/src/test/resources/features/LoanWriteOff.feature +++ b/fineract-e2e-tests-runner/src/test/resources/features/LoanWriteOff.feature @@ -16,9 +16,9 @@ And Admin does write-off the loan on "29 January 2023" Then Loan status will be "CLOSED_WRITTEN_OFF" Then Loan Transactions tab has a transaction with date: "29 January 2023", and with the following data: - | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | + | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | | Close (as written-off) | 650.0 | 650.0 | 0.0 | 0.0 | 0.0 | 0.0 | - Then Admin fails to undo "1"th transaction made on "22 January 2023" + Then Customer undo "1"th transaction made on "22 January 2023" results a 403 error and "update not allowed as loan status is written off" error message @TestRailId:C2935 @@ -56,9 +56,9 @@ And Admin does write-off the loan on "29 January 2023" Then Loan status will be "CLOSED_WRITTEN_OFF" Then Loan Transactions tab has a transaction with date: "29 January 2023", and with the following data: - | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | + | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | | Close (as written-off) | 650.0 | 650.0 | 0.0 | 0.0 | 0.0 | 0.0 | - Then Admin fails to undo "1"th transaction made on "29 January 2023" + Then Customer undo "1"th transaction made on "29 January 2023" results a 403 error and "update not allowed as loan status is written off" error message @TestRailId:C4006 Scenario: Verify accounting journal entries are not duplicated during write-off in case the cumulative loan was already charged-off diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java index 49bc7e2a0fc..dba8dcc2423 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java @@ -1195,16 +1195,16 @@ public CommandProcessingResult adjustLoanTransaction(final Long loanId, final Lo businessEventNotifierService.notifyPreBusinessEvent( new LoanAdjustTransactionBusinessEvent(new LoanAdjustTransactionBusinessEvent.Data(transactionToAdjust))); if (this.accountTransfersReadPlatformService.isAccountTransfer(transactionId, PortfolioAccountType.LOAN)) { - throw new PlatformServiceUnavailableException("error.msg.loan.transfer.transaction.update.not.allowed", + throw new GeneralPlatformDomainRuleException("error.msg.loan.transfer.transaction.update.not.allowed", "Loan transaction: " + transactionId + " update not allowed as it involves in account transfer", transactionId); } if (loan.isClosedWrittenOff()) { - throw new PlatformServiceUnavailableException("error.msg.loan.written.off.update.not.allowed", + throw new GeneralPlatformDomainRuleException("error.msg.loan.written.off.update.not.allowed", "Loan transaction: " + transactionId + " update not allowed as loan status is written off", transactionId); } if (transactionToAdjust.hasChargebackLoanTransactionRelations()) { - throw new PlatformServiceUnavailableException("error.msg.loan.transaction.update.not.allowed", + throw new GeneralPlatformDomainRuleException("error.msg.loan.transaction.update.not.allowed", "Loan transaction: " + transactionId + " update not allowed as loan transaction is linked to other transactions", transactionId); } diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanTransactionChargebackTest.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanTransactionChargebackTest.java index bbcfaf550a8..9c9d89ff19f 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanTransactionChargebackTest.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanTransactionChargebackTest.java @@ -157,7 +157,7 @@ public void applyLoanTransactionChargeback(LoanProductTestBuilder loanProductTes chargebackTransactionId, operationDate, responseSpecErr403); // Try to reverse a Loan Transaction repayment with linked transactions - reverseTransactionResponse = loanTransactionHelper.reverseLoanTransaction(loanId, transactionId, operationDate, responseSpecErr503); + reverseTransactionResponse = loanTransactionHelper.reverseLoanTransaction(loanId, transactionId, operationDate, responseSpecErr403); } @ParameterizedTest diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanWriteOffWithAdvancedPaymentAllocationTest.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanWriteOffWithAdvancedPaymentAllocationTest.java index f4c4e06f9a3..42a109a9f44 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanWriteOffWithAdvancedPaymentAllocationTest.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanWriteOffWithAdvancedPaymentAllocationTest.java @@ -148,7 +148,7 @@ public void loanUndoRepaymentAfterWriteOffShouldGiveErrorTest() { new PostLoansLoanIdTransactionsTransactionIdRequest().transactionDate("9 September 2022").locale("en") .dateFormat("dd MMMM yyyy").transactionAmount(0.0))); - assertEquals(503, exception.getResponse().code()); + assertEquals(403, exception.getResponse().code()); assertTrue(exception.getMessage().contains("error.msg.loan.written.off.update.not.allowed")); } @@ -221,7 +221,7 @@ public void loanUndoWriteOffShouldGiveErrorTest() { new PostLoansLoanIdTransactionsTransactionIdRequest().transactionDate("8 September 2022").locale("en") .dateFormat("dd MMMM yyyy").transactionAmount(0.0))); - assertEquals(503, exception.getResponse().code()); + assertEquals(403, exception.getResponse().code()); assertTrue(exception.getMessage().contains("error.msg.loan.written.off.update.not.allowed")); }