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

commit c8b88c3664c4299555f6cca1f1902754908e8176
Author: MarianaDmytrivBinariks <[email protected]>
AuthorDate: Mon Jul 7 12:26:48 2025 +0300

    FINERACT-2311: added e2e auto test scenarios for manual journal entries 
with asset externalization owner
---
 .../fineract/test/factory/LoanRequestFactory.java  |  21 ++++
 .../test/stepdef/common/JournalEntriesStepDef.java | 136 +++++++++++++++++++++
 .../fineract/test/support/TestContextKey.java      |   2 +
 .../features/AssetExternalization.feature          |  80 ++++++++++++
 4 files changed, 239 insertions(+)

diff --git 
a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/factory/LoanRequestFactory.java
 
b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/factory/LoanRequestFactory.java
index 4c7b0fc286..bd538658e5 100644
--- 
a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/factory/LoanRequestFactory.java
+++ 
b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/factory/LoanRequestFactory.java
@@ -19,12 +19,14 @@
 package org.apache.fineract.test.factory;
 
 import java.math.BigDecimal;
+import java.time.LocalDate;
 import java.time.format.DateTimeFormatter;
 import java.util.ArrayList;
 import java.util.List;
 import lombok.RequiredArgsConstructor;
 import org.apache.fineract.client.models.DisbursementDetail;
 import org.apache.fineract.client.models.InterestPauseRequestDto;
+import org.apache.fineract.client.models.JournalEntryCommand;
 import 
org.apache.fineract.client.models.PostAddAndDeleteDisbursementDetailRequest;
 import org.apache.fineract.client.models.PostCreateRescheduleLoansRequest;
 import org.apache.fineract.client.models.PostLoansLoanIdChargesChargeIdRequest;
@@ -34,11 +36,14 @@ import 
org.apache.fineract.client.models.PostLoansLoanIdTransactionsTransactionI
 import org.apache.fineract.client.models.PostLoansRequest;
 import org.apache.fineract.client.models.PostUpdateRescheduleLoansRequest;
 import org.apache.fineract.client.models.PutLoansLoanIdRequest;
+import org.apache.fineract.client.models.SingleDebitOrCreditEntryCommand;
 import org.apache.fineract.test.data.InterestCalculationPeriodTime;
 import org.apache.fineract.test.data.InterestType;
 import org.apache.fineract.test.data.LoanTermFrequencyType;
 import org.apache.fineract.test.data.RepaymentFrequencyType;
 import org.apache.fineract.test.data.TransactionProcessingStrategyCode;
+import org.apache.fineract.test.data.accounttype.AccountTypeResolver;
+import org.apache.fineract.test.data.accounttype.DefaultAccountType;
 import org.apache.fineract.test.data.loanproduct.DefaultLoanProduct;
 import org.apache.fineract.test.data.loanproduct.LoanProductResolver;
 import org.apache.fineract.test.helper.Utils;
@@ -87,6 +92,8 @@ public class LoanRequestFactory {
     public static final String DATE_WITHDRAWN_STRING = 
FORMATTER.format(Utils.now().minusMonths(1L));
     public static final String DEFAULT_TRANSACTION_DATE = 
FORMATTER.format(Utils.now().minusMonths(1L));
 
+    private final AccountTypeResolver accountTypeResolver;
+
     public PostLoansRequest defaultLoansRequest(Long clientId) {
         return new PostLoansRequest()//
                 .clientId(clientId)//
@@ -312,4 +319,18 @@ public class LoanRequestFactory {
     public static PostLoansLoanIdRequest 
defaultLoanContractTerminationRequest() {
         return new 
PostLoansLoanIdRequest().dateFormat(DATE_FORMAT).locale(DEFAULT_LOCALE).note("Contract
 Termination");
     }
+
+    public JournalEntryCommand defaultManualJournalEntryRequest(BigDecimal 
amount) {
+        final Long glAccountDebit = 
accountTypeResolver.resolve(DefaultAccountType.LOANS_RECEIVABLE);
+        final Long glAccountCredit = 
accountTypeResolver.resolve(DefaultAccountType.SUSPENSE_CLEARING_ACCOUNT);
+
+        return new 
JournalEntryCommand().amount(BigDecimal.TEN).officeId(1L).currencyCode("USD").locale(DEFAULT_LOCALE)
+                .dateFormat("uuuu-MM-dd").transactionDate(LocalDate.of(2024, 
1, 1))
+                .addCreditsItem(new 
SingleDebitOrCreditEntryCommand().glAccountId(glAccountCredit).amount(amount))
+                .addDebitsItem(new 
SingleDebitOrCreditEntryCommand().glAccountId(glAccountDebit).amount(amount));
+    }
+
+    public JournalEntryCommand defaultManualJournalEntryRequest(BigDecimal 
amount, String externalAssetOwner) {
+        return 
defaultManualJournalEntryRequest(amount).externalAssetOwner(externalAssetOwner);
+    }
 }
diff --git 
a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/common/JournalEntriesStepDef.java
 
b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/common/JournalEntriesStepDef.java
index b1b2e09419..3aeca48bb8 100644
--- 
a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/common/JournalEntriesStepDef.java
+++ 
b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/common/JournalEntriesStepDef.java
@@ -18,11 +18,14 @@
  */
 package org.apache.fineract.test.stepdef.common;
 
+import static 
org.apache.fineract.test.stepdef.loan.LoanRescheduleStepDef.FORMATTER_EN;
 import static org.assertj.core.api.Assertions.assertThat;
 
 import io.cucumber.datatable.DataTable;
 import io.cucumber.java.en.Then;
 import java.io.IOException;
+import java.math.BigDecimal;
+import java.time.LocalDate;
 import java.time.format.DateTimeFormatter;
 import java.util.ArrayList;
 import java.util.List;
@@ -32,11 +35,14 @@ import lombok.extern.slf4j.Slf4j;
 import 
org.apache.fineract.client.models.GetJournalEntriesTransactionIdResponse;
 import org.apache.fineract.client.models.GetLoansLoanIdResponse;
 import org.apache.fineract.client.models.GetLoansLoanIdTransactions;
+import org.apache.fineract.client.models.JournalEntryCommand;
 import org.apache.fineract.client.models.JournalEntryTransactionItem;
+import org.apache.fineract.client.models.PostJournalEntriesResponse;
 import org.apache.fineract.client.models.PostLoansResponse;
 import org.apache.fineract.client.services.JournalEntriesApi;
 import org.apache.fineract.client.services.LoansApi;
 import org.apache.fineract.test.data.TransactionType;
+import org.apache.fineract.test.factory.LoanRequestFactory;
 import org.apache.fineract.test.helper.ErrorHelper;
 import org.apache.fineract.test.helper.ErrorMessageHelper;
 import org.apache.fineract.test.stepdef.AbstractStepDef;
@@ -55,6 +61,9 @@ public class JournalEntriesStepDef extends AbstractStepDef {
     @Autowired
     private JournalEntriesApi journalEntriesApi;
 
+    @Autowired
+    private LoanRequestFactory loanRequestFactory;
+
     @Then("Loan Transactions tab has a {string} transaction with date {string} 
which has the following Journal entries:")
     public void journalEntryDataCheck(String transactionType, String 
transactionDate, DataTable table) throws IOException {
         DateTimeFormatter formatter = DateTimeFormatter.ofPattern(DATE_FORMAT);
@@ -360,4 +369,131 @@ public class JournalEntriesStepDef extends 
AbstractStepDef {
 
         
assertThat(journalLinesActualList.stream().findFirst().get().size()).isZero();
     }
+
+    public Response<PostJournalEntriesResponse> 
addManualJournalEntryWithoutExternalAssetOwner(String amount, String date)
+            throws IOException {
+        LocalDate transactionDate = LocalDate.parse(date, FORMATTER_EN);
+        JournalEntryCommand journalEntriesRequest = 
loanRequestFactory.defaultManualJournalEntryRequest(new BigDecimal(amount))
+                .transactionDate(transactionDate);
+        Response<PostJournalEntriesResponse> journalEntriesResponse = 
journalEntriesApi.createGLJournalEntry("", journalEntriesRequest)
+                .execute();
+        testContext().set(TestContextKey.MANUAL_JOURNAL_ENTRIES_REQUEST, 
journalEntriesRequest);
+        return journalEntriesResponse;
+    }
+
+    public Response<PostJournalEntriesResponse> 
addManualJournalEntryWithExternalAssetOwner(String amount, String date,
+            String externalAssetOwner) throws IOException {
+        LocalDate transactionDate = LocalDate.parse(date, FORMATTER_EN);
+        JournalEntryCommand journalEntriesRequest = loanRequestFactory
+                .defaultManualJournalEntryRequest(new BigDecimal(amount), 
externalAssetOwner).transactionDate(transactionDate);
+        Response<PostJournalEntriesResponse> journalEntriesResponse = 
journalEntriesApi.createGLJournalEntry("", journalEntriesRequest)
+                .execute();
+        testContext().set(TestContextKey.MANUAL_JOURNAL_ENTRIES_REQUEST, 
journalEntriesRequest);
+        return journalEntriesResponse;
+    }
+
+    @Then("Admin creates manual Journal entry with {string} amount and 
{string} date and unique External Asset Owner")
+    public void createManualJournalEntryWithExternalAssetOwner(String amount, 
String date) throws IOException {
+        String ownerExternalIdStored = 
testContext().get(TestContextKey.ASSET_EXTERNALIZATION_OWNER_EXTERNAL_ID);
+        Response<PostJournalEntriesResponse> journalEntriesResponse = 
addManualJournalEntryWithExternalAssetOwner(amount, date,
+                ownerExternalIdStored);
+
+        testContext().set(TestContextKey.MANUAL_JOURNAL_ENTRIES_RESPONSE, 
journalEntriesResponse);
+        ErrorHelper.checkSuccessfulApiCall(journalEntriesResponse);
+    }
+
+    @Then("Admin creates manual Journal entry with {string} amount and 
{string} date and empty External Asset Owner")
+    public void createManualJournalEntryWithEmptyExternalAssetOwner(String 
amount, String date) throws IOException {
+        Response<PostJournalEntriesResponse> journalEntriesResponse = 
addManualJournalEntryWithExternalAssetOwner(amount, date, "");
+
+        testContext().set(TestContextKey.MANUAL_JOURNAL_ENTRIES_RESPONSE, 
journalEntriesResponse);
+        ErrorHelper.checkSuccessfulApiCall(journalEntriesResponse);
+    }
+
+    @Then("Admin creates manual Journal entry with {string} amount and 
{string} date and without External Asset Owner")
+    public void createManualJournalEntryWithoutExternalAssetOwner(String 
amount, String date) throws IOException {
+        Response<PostJournalEntriesResponse> journalEntriesResponse = 
addManualJournalEntryWithoutExternalAssetOwner(amount, date);
+
+        testContext().set(TestContextKey.MANUAL_JOURNAL_ENTRIES_RESPONSE, 
journalEntriesResponse);
+        ErrorHelper.checkSuccessfulApiCall(journalEntriesResponse);
+    }
+
+    @Then("Verify manual Journal entry with External Asset Owner {string} and 
with the following Journal entries:")
+    public void checkManualJournalEntry(String externalAssetOwnerEnabled, 
DataTable table) {
+        Response<PostJournalEntriesResponse> journalEnriesResponse = 
testContext().get(TestContextKey.MANUAL_JOURNAL_ENTRIES_RESPONSE);
+        PostJournalEntriesResponse journalEntriesResponseBody = 
journalEnriesResponse.body();
+        String transactionId = journalEntriesResponseBody.getTransactionId();
+
+        JournalEntryCommand journalEntriesRequest = 
testContext().get(TestContextKey.MANUAL_JOURNAL_ENTRIES_REQUEST);
+
+        Response<GetJournalEntriesTransactionIdResponse> 
journalEntryDataResponse = null;
+        try {
+            journalEntryDataResponse = journalEntriesApi.retrieveAll1(//
+                    null, //
+                    null, //
+                    null, //
+                    null, //
+                    null, //
+                    null, //
+                    null, //
+                    transactionId, //
+                    null, //
+                    null, //
+                    null, //
+                    null, //
+                    null, //
+                    null, //
+                    null, //
+                    null, //
+                    null, //
+                    null, //
+                    true//
+            ).execute();
+            ErrorHelper.checkSuccessfulApiCall(journalEntryDataResponse);
+        } catch (IOException e) {
+            log.error("Exception", e);
+        }
+
+        List<List<String>> data = table.asLists();
+        for (int i = 1; i < data.size(); i++) {
+            List<List<List<String>>> possibleActualValuesList = new 
ArrayList<>();
+            List<String> expectedValues = data.get(i);
+            if (Boolean.parseBoolean(externalAssetOwnerEnabled)) {
+                expectedValues
+                        .add(journalEntriesRequest.getExternalAssetOwner() == 
null ? null : journalEntriesRequest.getExternalAssetOwner());
+            }
+            boolean containsAnyExpected = false;
+
+            GetJournalEntriesTransactionIdResponse journalEntryData = 
journalEntryDataResponse.body();
+
+            List<JournalEntryTransactionItem> journalLinesActual = 
journalEntryData.getPageItems();
+
+            List<List<String>> actualValuesList = 
journalLinesActual.stream().map(t -> {
+                List<String> actualValues = new ArrayList<>();
+                actualValues.add(t.getGlAccountType().getValue() == null ? 
null : t.getGlAccountType().getValue());
+                actualValues.add(t.getGlAccountCode() == null ? null : 
t.getGlAccountCode());
+                actualValues.add(t.getGlAccountName() == null ? null : 
t.getGlAccountName());
+                actualValues.add("DEBIT".equals(t.getEntryType().getValue()) ? 
String.valueOf(t.getAmount()) : null);
+                actualValues.add("CREDIT".equals(t.getEntryType().getValue()) 
? String.valueOf(t.getAmount()) : null);
+                
actualValues.add(String.valueOf(t.getManualEntry()).toLowerCase());
+                if (Boolean.parseBoolean(externalAssetOwnerEnabled)) {
+                    actualValues.add(t.getExternalAssetOwner() == null ? null 
: t.getExternalAssetOwner());
+                }
+
+                return actualValues;
+            }).collect(Collectors.toList());
+
+            possibleActualValuesList.add(actualValuesList);
+
+            boolean containsExpectedValues = 
actualValuesList.stream().anyMatch(actualValues -> 
actualValues.equals(expectedValues));
+            if (containsExpectedValues) {
+                containsAnyExpected = true;
+            }
+
+            assertThat(containsAnyExpected)
+                    
.as(ErrorMessageHelper.wrongValueInLineInJournalEntries(transactionId, i, 
possibleActualValuesList, expectedValues))
+                    .isTrue();
+        }
+    }
+
 }
diff --git 
a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/support/TestContextKey.java
 
b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/support/TestContextKey.java
index 3ccde040b9..b61a28e5c3 100644
--- 
a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/support/TestContextKey.java
+++ 
b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/support/TestContextKey.java
@@ -232,4 +232,6 @@ public abstract class TestContextKey {
     public static final String 
DEFAULT_LOAN_PRODUCT_CREATE_RESPONSE_LP2_ADV_PYMNT_INTEREST_DAILY_INTEREST_RECALCULATION_CONTRACT_TERMINATION
 = 
"loanProductCreateResponseLP2AdvancedPaymentInterestDailyInterestRecalculationContractTermination";
     public static final String LOAN_CONTRACT_TERMINATION_RESPONSE = 
"loanContractTerminationResponse";
     public static final String LOAN_UNDO_CONTRACT_TERMINATION_RESPONSE = 
"loanUndoContractTerminationResponse";
+    public static final String MANUAL_JOURNAL_ENTRIES_REQUEST = 
"manualJournalEntriesRequest";
+    public static final String MANUAL_JOURNAL_ENTRIES_RESPONSE = 
"manualJournalEntriesResponse";
 }
diff --git 
a/fineract-e2e-tests-runner/src/test/resources/features/AssetExternalization.feature
 
b/fineract-e2e-tests-runner/src/test/resources/features/AssetExternalization.feature
index c205844b61..468b4fd59c 100644
--- 
a/fineract-e2e-tests-runner/src/test/resources/features/AssetExternalization.feature
+++ 
b/fineract-e2e-tests-runner/src/test/resources/features/AssetExternalization.feature
@@ -1776,3 +1776,83 @@ Feature: Asset Externalization
       | ASSET         | 112603        | Interest/Fee Receivable | DEBIT     | 
0.33    |
       | INCOME        | 404000        | Interest Income         | CREDIT    | 
0.33    |
     When Global config 
"outstanding-interest-calculation-strategy-for-external-asset-transfer" value 
set to "TOTAL_OUTSTANDING_INTEREST"
+
+  @TestRailId:C3799 @AssetExternalizationJournalEntry
+  Scenario: Verify manual journal entry with External Asset Owner value if 
asset-externalization is enabled for existing loan - UC1
+    Given Global configuration "asset-externalization-of-non-active-loans" is 
enabled
+
+    When Admin sets the business date to "1 June 2025"
+    When Admin creates a client with random data
+    When Admin creates a new default Loan with date: "1 June 2025"
+    And Admin successfully approves the loan on "1 June 2025" with "1000" 
amount and expected disbursement date on "1 June 2025"
+    When Admin successfully disburse the loan on "1 June 2025" with "1000" EUR 
transaction amount
+    Then Loan status will be "ACTIVE"
+    When Admin makes asset externalization request by Loan ID with unique 
ownerExternalId, system-generated transferExternalId and the following data:
+      | Transaction type | settlementDate | purchasePriceRatio |
+      | sale             | 2025-06-01     | 1                  |
+    Then Asset externalization response has the correct Loan ID, 
transferExternalId
+    Then Fetching Asset externalization details by loan id gives 
numberOfElements: 1 with correct ownerExternalId and the following data:
+      | settlementDate | purchasePriceRatio | status  | effectiveFrom | 
effectiveTo | Transaction type |
+      | 2025-06-01     | 1                  | PENDING | 2025-06-01    | 
9999-12-31  | SALE             |
+
+    When Admin sets the business date to "26 June 2025"
+    Then Admin creates manual Journal entry with "131" amount and "26 June 
2025" date and unique External Asset Owner
+    Then Verify manual Journal entry with External Asset Owner "true" and with 
the following Journal entries:
+      | Type      | Account code | Account name              | Debit | Credit 
| Manual Entry |
+      | ASSET     | 112601       | Loans Receivable          | 131.0 |        
| true         |
+      | LIABILITY | 145023       | Suspense/Clearing account |       | 131.0  
| true         |
+    Given Global configuration "asset-externalization-of-non-active-loans" is 
enabled
+
+    When Loan Pay-off is made on "26 June 2025"
+    Then Loan's all installments have obligations met
+
+  @TestRailId:C3800 @AssetExternalizationJournalEntry
+  Scenario: Verify manual journal entry with External Asset Owner empty value 
if asset-externalization is enabled - UC2
+    Given Global configuration "asset-externalization-of-non-active-loans" is 
enabled
+    When Admin sets the business date to "25 June 2025"
+    Then Admin creates manual Journal entry with "88" amount and "20 June 
2025" date and without External Asset Owner
+    Then Verify manual Journal entry with External Asset Owner "true" and with 
the following Journal entries:
+      | Type      | Account code | Account name              | Debit | Credit 
| Manual Entry |
+      | ASSET     | 112601       | Loans Receivable          | 88.0  |        
| true         |
+      | LIABILITY | 145023       | Suspense/Clearing account |       | 88.0   
| true         |
+    Given Global configuration "asset-externalization-of-non-active-loans" is 
enabled
+
+  @TestRailId:C38001 @AssetExternalizationJournalEntry
+  Scenario: Verify manual journal entry with External Asset Owner empty value 
if asset-externalization is enabled for existing loan - UC3
+    Given Global configuration "asset-externalization-of-non-active-loans" is 
enabled
+
+    When Admin sets the business date to "1 June 2025"
+    When Admin creates a client with random data
+    When Admin creates a new default Loan with date: "1 June 2025"
+    And Admin successfully approves the loan on "1 June 2025" with "1000" 
amount and expected disbursement date on "1 June 2025"
+    When Admin successfully disburse the loan on "1 June 2025" with "1000" EUR 
transaction amount
+    Then Loan status will be "ACTIVE"
+    When Admin makes asset externalization request by Loan ID with unique 
ownerExternalId, system-generated transferExternalId and the following data:
+      | Transaction type | settlementDate | purchasePriceRatio |
+      | sale             | 2025-06-01     | 1                  |
+    Then Asset externalization response has the correct Loan ID, 
transferExternalId
+    Then Fetching Asset externalization details by loan id gives 
numberOfElements: 1 with correct ownerExternalId and the following data:
+      | settlementDate | purchasePriceRatio | status  | effectiveFrom | 
effectiveTo | Transaction type |
+      | 2025-06-01     | 1                  | PENDING | 2025-06-01    | 
9999-12-31  | SALE             |
+
+    When Admin sets the business date to "25 June 2025"
+    Then Admin creates manual Journal entry with "99" amount and "20 June 
2025" date and without External Asset Owner
+    Then Verify manual Journal entry with External Asset Owner "true" and with 
the following Journal entries:
+      | Type      | Account code | Account name              | Debit | Credit 
| Manual Entry |
+      | ASSET     | 112601       | Loans Receivable          | 99.0  |        
| true         |
+      | LIABILITY | 145023       | Suspense/Clearing account |       | 99.0   
| true         |
+    Given Global configuration "asset-externalization-of-non-active-loans" is 
enabled
+
+    When Loan Pay-off is made on "25 June 2025"
+    Then Loan's all installments have obligations met
+
+  @TestRailId:C3802 @AssetExternalizationJournalEntry
+  Scenario: Verify manual journal entry with no External Asset Owner value if 
asset-externalization is disabled - UC4
+    Given Global configuration "asset-externalization-of-non-active-loans" is 
disabled
+    When Admin sets the business date to "25 June 2025"
+    Then Admin creates manual Journal entry with "250.05" amount and "15 June 
2025" date and without External Asset Owner
+    Then Verify manual Journal entry with External Asset Owner "false" and 
with the following Journal entries:
+      | Type      | Account code | Account name              | Debit  | Credit 
| Manual Entry |
+      | ASSET     | 112601       | Loans Receivable          | 250.05 |        
| true         |
+      | LIABILITY | 145023       | Suspense/Clearing account |        | 250.05 
| true         |
+    Given Global configuration "asset-externalization-of-non-active-loans" is 
enabled

Reply via email to