FINERACT-2311: added e2e auto test scenarios for manual journal entries with asset externalization owner

This commit is contained in:
MarianaDmytrivBinariks
2025-07-07 13:59:33 +02:00
committed by Adam Saghy
parent 0c1ca2baba
commit c8b88c3664
4 changed files with 239 additions and 0 deletions
@@ -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);
}
}
@@ -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();
}
}
}
@@ -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";
}
@@ -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