This is an automated email from the ASF dual-hosted git repository. aleks pushed a commit to branch develop in repository https://gitbox.apache.org/repos/asf/fineract.git
commit 0b5391169f5f1d2c9a3d9614b6acc244e4122390 Author: Rustam Zeinalov <[email protected]> AuthorDate: Tue Jul 22 15:00:44 2025 +0200 FINERACT-2327: added e2e testing for validatoin of feature allow to create Interest Refund transaction manually --- .../fineract/test/helper/ErrorMessageHelper.java | 4 + .../fineract/test/stepdef/loan/LoanStepDef.java | 101 +++++++++++++++++++++ .../features/LoanMerchantIssuedRefund.feature | 19 +++- .../resources/features/LoanPayoutRefund.feature | 53 ++++++++++- 4 files changed, 175 insertions(+), 2 deletions(-) diff --git a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/helper/ErrorMessageHelper.java b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/helper/ErrorMessageHelper.java index dd0a911663..91c4f6da74 100644 --- a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/helper/ErrorMessageHelper.java +++ b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/helper/ErrorMessageHelper.java @@ -72,6 +72,10 @@ public final class ErrorMessageHelper { return "Interest Refund already exists for this transaction"; } + public static String addManualInterestRefundIfReversedFailure() { + return "Target transaction must be Merchant Issued Refund or Payout Refund"; + } + public static String addDisbursementExceedMaxAppliedAmountFailure(String totalDisbAmount, String maxDisbursalAmount) { return String.format("Loan disbursal amount can't be greater than maximum applied loan amount calculation. " + "Total disbursed amount: %s Maximum disbursal amount: %s", totalDisbAmount, maxDisbursalAmount); 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 a4c667045f..e61da4f54d 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 @@ -461,6 +461,59 @@ public class LoanStepDef extends AbstractStepDef { eventCheckHelper.loanBalanceChangedEventCheck(loanId); } + @When("Admin manually adds Interest Refund for {string} transaction made on invalid date {string} with {double} EUR interest refund amount") + public void addInterestRefundTransactionManuallyWithInvalidDate(final String transactionTypeInput, final String transactionDate, + final double amount) throws IOException { + final Response<PostLoansResponse> loanResponse = testContext().get(TestContextKey.LOAN_CREATE_RESPONSE); + final long loanId = loanResponse.body().getLoanId(); + final TransactionType transactionType = TransactionType.valueOf(transactionTypeInput); + + final Response<GetLoansLoanIdResponse> loanDetailsResponse = loansApi.retrieveLoan(loanId, false, "transactions", "", "").execute(); + ErrorHelper.checkSuccessfulApiCall(loanDetailsResponse); + + assert loanDetailsResponse.body() != null; + final List<GetLoansLoanIdTransactions> transactions = loanDetailsResponse.body().getTransactions(); + assert transactions != null; + final GetLoansLoanIdTransactions refundTransaction = transactions.stream() + .filter(t -> t.getType() != null + && (transactionType.equals(TransactionType.PAYOUT_REFUND) ? "Payout Refund" : "Merchant Issued Refund") + .equals(t.getType().getValue())) + .findFirst().orElseThrow(() -> new IllegalStateException("No refund transaction found for loan " + loanId)); + + final Response<PostLoansLoanIdTransactionsResponse> adjustmentResponse = addInterestRefundTransaction(amount, + refundTransaction.getId(), transactionDate); + testContext().set(TestContextKey.LOAN_INTEREST_REFUND_RESPONSE, adjustmentResponse); + ErrorHelper.checkFailedApiCall(adjustmentResponse, 400); + } + + @When("Admin fails to add Interest Refund for {string} transaction made on {string} with {double} EUR interest refund amount") + public void addInterestRefundTransactionManuallyFailsInNonPayout(final String transactionTypeInput, final String transactionDate, + final double amount) throws IOException { + final Response<PostLoansResponse> loanResponse = testContext().get(TestContextKey.LOAN_CREATE_RESPONSE); + final long loanId = loanResponse.body().getLoanId(); + final TransactionType transactionType = TransactionType.valueOf(transactionTypeInput); + + final Response<GetLoansLoanIdResponse> loanDetailsResponse = loansApi.retrieveLoan(loanId, false, "transactions", "", "").execute(); + ErrorHelper.checkSuccessfulApiCall(loanDetailsResponse); + + assert loanDetailsResponse.body() != null; + final List<GetLoansLoanIdTransactions> transactions = loanDetailsResponse.body().getTransactions(); + assert transactions != null; + + final GetLoansLoanIdTransactions moneyTransaction = transactions.stream() + .filter(t -> t.getType() != null && transactionType.equals(TransactionType.REPAYMENT) && t.getDate() != null + && transactionDate.equals(FORMATTER.format(t.getDate()))) + .findFirst().orElseThrow(() -> new IllegalStateException("No repayment transaction found")); + + final Response<PostLoansLoanIdTransactionsResponse> adjustmentResponse = addInterestRefundTransaction(amount, + moneyTransaction.getId()); + testContext().set(TestContextKey.LOAN_INTEREST_REFUND_RESPONSE, adjustmentResponse); + final ErrorResponse errorDetails = ErrorResponse.from(adjustmentResponse); + assertThat(errorDetails.getHttpStatusCode()).as(ErrorMessageHelper.addManualInterestRefundIfReversedFailure()).isEqualTo(403); + assertThat(errorDetails.getSingleError().getDeveloperMessage()) + .isEqualTo(ErrorMessageHelper.addManualInterestRefundIfReversedFailure()); + } + @Then("Admin fails to add duplicate Interest Refund for {string} transaction made on {string} with {double} EUR interest refund amount") public void failToAddManualInterestRefundIfAlreadyExists(final String transactionTypeInput, final String transactionDate, final double amount) throws IOException { @@ -490,6 +543,35 @@ public class LoanStepDef extends AbstractStepDef { .isEqualTo(ErrorMessageHelper.addManualInterestRefundIfAlreadyExistsFailure()); } + @Then("Admin fails to add Interest Refund {string} transaction after reverse made on {string} with {double} EUR interest refund amount") + public void failToAddManualInterestRefundIfReversed(final String transactionTypeInput, final String transactionDate, + final double amount) throws IOException { + final Response<PostLoansResponse> loanResponse = testContext().get(TestContextKey.LOAN_CREATE_RESPONSE); + final long loanId = loanResponse.body().getLoanId(); + final TransactionType transactionType = TransactionType.valueOf(transactionTypeInput); + + final Response<GetLoansLoanIdResponse> loanDetailsResponse = loansApi.retrieveLoan(loanId, false, "transactions", "", "").execute(); + ErrorHelper.checkSuccessfulApiCall(loanDetailsResponse); + + assert loanDetailsResponse.body() != null; + final List<GetLoansLoanIdTransactions> transactions = loanDetailsResponse.body().getTransactions(); + assert transactions != null; + final GetLoansLoanIdTransactions refundTransaction = transactions.stream() + .filter(t -> t.getType() != null + && (transactionType.equals(TransactionType.PAYOUT_REFUND) ? "Payout Refund" : "Merchant Issued Refund") + .equals(t.getType().getValue()) + && t.getDate() != null && transactionDate.equals(FORMATTER.format(t.getDate()))) + .findFirst().orElseThrow(() -> new IllegalStateException("No refund transaction found for loan " + loanId)); + + final Response<PostLoansLoanIdTransactionsResponse> adjustmentResponse = addInterestRefundTransaction(amount, + refundTransaction.getId()); + testContext().set(TestContextKey.LOAN_INTEREST_REFUND_RESPONSE, adjustmentResponse); + final ErrorResponse errorDetails = ErrorResponse.from(adjustmentResponse); + assertThat(errorDetails.getHttpStatusCode()).as(ErrorMessageHelper.addManualInterestRefundIfAlreadyExistsFailure()).isEqualTo(403); + assertThat(errorDetails.getSingleError().getDeveloperMessage()) + .isEqualTo(ErrorMessageHelper.addManualInterestRefundIfReversedFailure()); + } + private void createTransactionWithAutoIdempotencyKeyAndWithExternalOwner(String transactionTypeInput, String transactionPaymentType, String transactionDate, double transactionAmount, String externalOwnerId) throws IOException { eventStore.reset(); @@ -5058,4 +5140,23 @@ public class LoanStepDef extends AbstractStepDef { return loanTransactionsApi.adjustLoanTransaction(loanId, transactionId, interestRefundRequest, "interest-refund").execute(); } + private Response<PostLoansLoanIdTransactionsResponse> addInterestRefundTransaction(final double amount, final Long transactionId, + final String transactionDate) throws IOException { + final Response<PostLoansResponse> loanResponse = testContext().get(TestContextKey.LOAN_CREATE_RESPONSE); + assert loanResponse.body() != null; + final long loanId = loanResponse.body().getLoanId(); + + final DefaultPaymentType paymentType = DefaultPaymentType.AUTOPAY; + final Long paymentTypeValue = paymentTypeResolver.resolve(paymentType); + + final PostLoansLoanIdTransactionsTransactionIdRequest interestRefundRequest = new PostLoansLoanIdTransactionsTransactionIdRequest() + .dateFormat("dd MMMM yyyy").locale("en").transactionAmount(amount).paymentTypeId(paymentTypeValue) + .externalId("EXT-INT-REF-" + UUID.randomUUID()).note(""); + + if (transactionDate != null) { + interestRefundRequest.transactionDate(transactionDate); + } + + return loanTransactionsApi.adjustLoanTransaction(loanId, transactionId, interestRefundRequest, "interest-refund").execute(); + } } diff --git a/fineract-e2e-tests-runner/src/test/resources/features/LoanMerchantIssuedRefund.feature b/fineract-e2e-tests-runner/src/test/resources/features/LoanMerchantIssuedRefund.feature index 1a9aacd2b8..ebb3adc892 100644 --- a/fineract-e2e-tests-runner/src/test/resources/features/LoanMerchantIssuedRefund.feature +++ b/fineract-e2e-tests-runner/src/test/resources/features/LoanMerchantIssuedRefund.feature @@ -531,4 +531,21 @@ Feature: MerchantIssuedRefund | 10 July 2024 | Repayment | 338.9 | 336.48 | 2.42 | 0.0 | 0.0 | 663.52 | false | false | | 15 July 2024 | Merchant Issued Refund | 50.0 | 50.0 | 0.0 | 0.0 | 0.0 | 613.52 | false | false | | 15 July 2024 | Interest Refund | 0.19 | 0.0 | 0.19 | 0.0 | 0.0 | 613.52 | false | false | - When Admin fails to add duplicate Interest Refund for "MERCHANT_ISSUED_REFUND" transaction made on "15 July 2024" with 0.19 EUR interest refund amount \ No newline at end of file + When Admin fails to add duplicate Interest Refund for "MERCHANT_ISSUED_REFUND" transaction made on "15 July 2024" with 0.19 EUR interest refund amount + + @TestRailId:C3880 + Scenario: Prevent manual Interest Refund creation with mismatched transaction date for Merchant Issued Refund + When Admin sets the business date to "01 July 2024" + 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_CUSTOM_PMT_ALLOC_PROGRESSIVE_LOAN_SCHEDULE_HORIZONTAL | 01 July 2024 | 1000 | 10 | DECLINING_BALANCE | DAILY | EQUAL_INSTALLMENTS | 3 | MONTHS | 1 | MONTHS | 3 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | + And Admin successfully approves the loan on "01 July 2024" with "1000" amount and expected disbursement date on "01 July 2024" + And Admin successfully disburse the loan on "01 July 2024" with "1000" EUR transaction amount + When Admin sets the business date to "10 July 2024" + And Customer makes "REPAYMENT" transaction with "AUTOPAY" payment type on "10 July 2024" with 338.9 EUR transaction amount and system-generated Idempotency key + When Admin sets the business date to "15 July 2024" + And Customer makes "MERCHANT_ISSUED_REFUND" transaction with "AUTOPAY" payment type on "15 July 2024" with 50 EUR transaction amount and system-generated Idempotency key and interestRefundCalculation false + When Admin sets the business date to "16 July 2024" + #mismatch date for Interest Refund + When Admin manually adds Interest Refund for "MERCHANT_ISSUED_REFUND" transaction made on invalid date "16 July 2024" with 2.42 EUR interest refund amount diff --git a/fineract-e2e-tests-runner/src/test/resources/features/LoanPayoutRefund.feature b/fineract-e2e-tests-runner/src/test/resources/features/LoanPayoutRefund.feature index 4de834faee..a20b25acc9 100644 --- a/fineract-e2e-tests-runner/src/test/resources/features/LoanPayoutRefund.feature +++ b/fineract-e2e-tests-runner/src/test/resources/features/LoanPayoutRefund.feature @@ -390,4 +390,55 @@ Feature: PayoutRefund | 10 July 2024 | Repayment | 338.9 | 336.48 | 2.42 | 0.0 | 0.0 | 663.52 | false | false | | 15 July 2024 | Payout Refund | 50.0 | 50.0 | 0.0 | 0.0 | 0.0 | 613.52 | false | false | | 15 July 2024 | Interest Refund | 0.19 | 0.0 | 0.19 | 0.0 | 0.0 | 613.52 | false | false | - When Admin fails to add duplicate Interest Refund for "PAYOUT_REFUND" transaction made on "15 July 2024" with 0.19 EUR interest refund amount \ No newline at end of file + When Admin fails to add duplicate Interest Refund for "PAYOUT_REFUND" transaction made on "15 July 2024" with 0.19 EUR interest refund amount + + @TestRailId:C3878 + Scenario: Prevent manual Interest Refund creation on reversed refund transaction + When Admin sets the business date to "01 July 2024" + 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_CUSTOM_PMT_ALLOC_PROGRESSIVE_LOAN_SCHEDULE_HORIZONTAL | 01 July 2024 | 1000 | 10 | DECLINING_BALANCE | DAILY | EQUAL_INSTALLMENTS | 3 | MONTHS | 1 | MONTHS | 3 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | + And Admin successfully approves the loan on "01 July 2024" with "1000" amount and expected disbursement date on "01 July 2024" + And Admin successfully disburse the loan on "01 July 2024" with "1000" EUR transaction amount + When Admin sets the business date to "10 July 2024" + And Customer makes "REPAYMENT" transaction with "AUTOPAY" payment type on "10 July 2024" with 338.9 EUR transaction amount and system-generated Idempotency key + When Admin sets the business date to "15 July 2024" + And Customer makes "PAYOUT_REFUND" transaction with "AUTOPAY" payment type on "15 July 2024" with 50 EUR transaction amount and system-generated Idempotency key and interestRefundCalculation false + When Customer undo "1"th transaction made on "15 July 2024" + Then Admin fails to add Interest Refund "PAYOUT_REFUND" transaction after reverse made on "15 July 2024" with 2.42 EUR interest refund amount + + @TestRailId:C3879 + Scenario: Prevent manual Interest Refund creation on non-refund transaction type + When Admin sets the business date to "01 July 2024" + 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_CUSTOM_PMT_ALLOC_PROGRESSIVE_LOAN_SCHEDULE_HORIZONTAL | 01 July 2024 | 1000 | 10 | DECLINING_BALANCE | DAILY | EQUAL_INSTALLMENTS | 3 | MONTHS | 1 | MONTHS | 3 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | + And Admin successfully approves the loan on "01 July 2024" with "1000" amount and expected disbursement date on "01 July 2024" + And Admin successfully disburse the loan on "01 July 2024" with "1000" 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 | + | | | 01 July 2024 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 31 | 01 August 2024 | | 669.43 | 330.57 | 8.33 | 0.0 | 0.0 | 338.9 | 0.0 | 0.0 | 0.0 | 338.9 | + | 2 | 31 | 01 September 2024 | | 336.11 | 333.32 | 5.58 | 0.0 | 0.0 | 338.9 | 0.0 | 0.0 | 0.0 | 338.9 | + | 3 | 30 | 01 October 2024 | | 0.0 | 336.11 | 2.8 | 0.0 | 0.0 | 338.91 | 0.0 | 0.0 | 0.0 | 338.91 | + And Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 1000.0 | 16.71 | 0.0 | 0.0 | 1016.71 | 0.0 | 0.0 | 0.0 | 1016.71 | + When Admin sets the business date to "10 July 2024" + And Customer makes "REPAYMENT" transaction with "AUTOPAY" payment type on "10 July 2024" with 338.9 EUR transaction amount and system-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 | + | | | 01 July 2024 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 31 | 01 August 2024 | 10 July 2024 | 663.52 | 336.48 | 2.42 | 0.0 | 0.0 | 338.9 | 338.9 | 338.9 | 0.0 | 0.0 | + | 2 | 31 | 01 September 2024 | | 334.07 | 329.45 | 9.45 | 0.0 | 0.0 | 338.9 | 0.0 | 0.0 | 0.0 | 338.9 | + | 3 | 30 | 01 October 2024 | | 0.0 | 334.07 | 2.78 | 0.0 | 0.0 | 336.85 | 0.0 | 0.0 | 0.0 | 336.85 | + And Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 1000.0 | 14.65 | 0.0 | 0.0 | 1014.65 | 338.9 | 338.9 | 0.0 | 675.75 | + And Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | + | 01 July 2024 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | false | false | + | 10 July 2024 | Repayment | 338.9 | 336.48 | 2.42 | 0.0 | 0.0 | 663.52 | false | false | + When Admin fails to add Interest Refund for "REPAYMENT" transaction made on "10 July 2024" with 2.42 EUR interest refund amount
