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

Reply via email to