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
