Skip to content

Commit 42ff974

Browse files
MarianaDmytrivBinariksadamsaghy
authored andcommitted
FINERACT-2311: added e2e auto test scenarios for add buy down fee final amortization with pay off and charge-off
1 parent 880580f commit 42ff974

File tree

8 files changed

+584
-58
lines changed

8 files changed

+584
-58
lines changed

fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/data/loanproduct/DefaultLoanProduct.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,7 @@ public enum DefaultLoanProduct implements LoanProduct {
131131
LP2_ADV_PYMNT_INTEREST_DAILY_EMI_360_30_IR_DAILY_TILL_REST_FREQUENCY_DATE_LAST_INSTALLMENT, //
132132
LP2_PROGRESSIVE_ADVANCED_PAYMENT_ALLOCATION_CAPITALIZED_INCOME, //
133133
LP2_PROGRESSIVE_ADVANCED_PAYMENT_ALLOCATION_BUYDOWN_FEES, //
134+
LP2_PROGRESSIVE_ADVANCED_PAYMENT_ALLOCATION_BUYDOWN_FEES_CHARGE_OFF_REASON, //
134135
LP2_ADV_PYMNT_INTEREST_DAILY_EMI_360_30_INTEREST_RECALC_DAILY_CAPITALIZED_INCOME_ADJ_CUSTOM_ALLOC, //
135136
LP2_ADV_PYMNT_INTEREST_DAILY_EMI_360_30_INTEREST_RECALC_DAILY_CAPITALIZED_INCOME, //
136137
LP2_ADV_PYMNT_INTEREST_DAILY_EMI_360_30_INTEREST_RECALC_DAILY_CAPITALIZED_INCOME_FEE, //

fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/factory/LoanProductsRequestFactory.java

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1754,4 +1754,26 @@ public PostLoanProductsRequest defaultLoanProductsRequestLP2BuyDownFees() {
17541754
.buyDownExpenseAccountId(accountTypeResolver.resolve(DefaultAccountType.BUY_DOWN_EXPENSE))//
17551755
.incomeFromBuyDownAccountId(accountTypeResolver.resolve(DefaultAccountType.INCOME_FROM_BUY_DOWN));//
17561756
}
1757+
1758+
public PostLoanProductsRequest defaultLoanProductsRequestLP2ChargeOffReasonToExpenseAccountMappingsWithBuyDownFee() {
1759+
1760+
Long chargeOffReasonId = codeHelper.retrieveCodeByName(CHARGE_OFF_REASONS).getId();
1761+
1762+
List<PostChargeOffReasonToExpenseAccountMappings> chargeOffReasonToExpenseAccountMappings = new ArrayList<>();
1763+
PostChargeOffReasonToExpenseAccountMappings chargeOffFraudReason = new PostChargeOffReasonToExpenseAccountMappings();
1764+
PostChargeOffReasonToExpenseAccountMappings chargeOffDelinquentReason = new PostChargeOffReasonToExpenseAccountMappings();
1765+
PostChargeOffReasonToExpenseAccountMappings chargeOffOtherReason = new PostChargeOffReasonToExpenseAccountMappings();
1766+
chargeOffFraudReason.chargeOffReasonCodeValueId(codeValueResolver.resolve(chargeOffReasonId, DefaultCodeValue.FRAUD));
1767+
chargeOffFraudReason.expenseAccountId(accountTypeResolver.resolve(DefaultAccountType.CREDIT_LOSS_BAD_DEBT_FRAUD));
1768+
chargeOffDelinquentReason.chargeOffReasonCodeValueId(codeValueResolver.resolve(chargeOffReasonId, DefaultCodeValue.DELINQUENT));
1769+
chargeOffDelinquentReason.expenseAccountId(accountTypeResolver.resolve(DefaultAccountType.CREDIT_LOSS_BAD_DEBT));
1770+
chargeOffOtherReason.chargeOffReasonCodeValueId(codeValueResolver.resolve(chargeOffReasonId, DefaultCodeValue.OTHER));
1771+
chargeOffOtherReason.expenseAccountId(accountTypeResolver.resolve(DefaultAccountType.CREDIT_LOSS_BAD_DEBT));
1772+
chargeOffReasonToExpenseAccountMappings.add(chargeOffFraudReason);
1773+
chargeOffReasonToExpenseAccountMappings.add(chargeOffDelinquentReason);
1774+
chargeOffReasonToExpenseAccountMappings.add(chargeOffOtherReason);
1775+
1776+
return defaultLoanProductsRequestLP2BuyDownFees()//
1777+
.chargeOffReasonToExpenseAccountMappings(chargeOffReasonToExpenseAccountMappings);//
1778+
}
17571779
}

fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/initializer/global/LoanProductGlobalInitializerStep.java

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2945,7 +2945,7 @@ public void initialize() throws Exception {
29452945
responseLoanProductsRequestLP2ProgressiveAdvPymnt36030InterestRecalcApprovedOverAppliedFlatCapitalizedIncome);
29462946

29472947
// LP2 with progressive loan schedule + horizontal + interest EMI + 360/30
2948-
// + interest recalculation=false, buy down fees enabled
2948+
// + interest recalculation, buy down fees enabled
29492949
final String name114 = DefaultLoanProduct.LP2_PROGRESSIVE_ADVANCED_PAYMENT_ALLOCATION_BUYDOWN_FEES.getName();
29502950
final PostLoanProductsRequest loanProductsRequestLP2ProgressiveAdvPaymentBuyDownFees = loanProductsRequestFactory
29512951
.defaultLoanProductsRequestLP2BuyDownFees()//
@@ -3334,6 +3334,33 @@ public void initialize() throws Exception {
33343334
.createLoanProduct(loanProductsRequestNoInterestRecalculationAllocationPenaltyFirst).execute();
33353335
TestContext.INSTANCE.set(TestContextKey.LP2_NO_INTEREST_RECALCULATION_ALLOCATION_PENALTY_FIRST_RESPONSE,
33363336
responseLoanProductsRequestNoInterestRecalculationAllocationPenaltyFirst);
3337+
3338+
// LP2 with progressive loan schedule + horizontal + interest EMI + 360/30
3339+
// charge-off reasons to GL account mapping
3340+
// + interest recalculation, buy down fees enabled
3341+
final String name128 = DefaultLoanProduct.LP2_PROGRESSIVE_ADVANCED_PAYMENT_ALLOCATION_BUYDOWN_FEES_CHARGE_OFF_REASON.getName();
3342+
final PostLoanProductsRequest loanProductsRequestLP2ProgressiveAdvPaymentBuyDownFeesWithChargeOffReason = loanProductsRequestFactory
3343+
.defaultLoanProductsRequestLP2ChargeOffReasonToExpenseAccountMappingsWithBuyDownFee()//
3344+
.name(name128)//
3345+
.transactionProcessingStrategyCode(ADVANCED_PAYMENT_ALLOCATION.getValue())//
3346+
.loanScheduleType("PROGRESSIVE") //
3347+
.isInterestRecalculationEnabled(true)//
3348+
.preClosureInterestCalculationStrategy(1)//
3349+
.rescheduleStrategyMethod(4)//
3350+
.interestRecalculationCompoundingMethod(0)//
3351+
.recalculationRestFrequencyType(2)//
3352+
.recalculationRestFrequencyInterval(1)//
3353+
.paymentAllocation(List.of(//
3354+
createPaymentAllocation("DEFAULT", "NEXT_INSTALLMENT"), //
3355+
createPaymentAllocation("GOODWILL_CREDIT", "LAST_INSTALLMENT"), //
3356+
createPaymentAllocation("MERCHANT_ISSUED_REFUND", "REAMORTIZATION"), //
3357+
createPaymentAllocation("PAYOUT_REFUND", "NEXT_INSTALLMENT")));//
3358+
final Response<PostLoanProductsResponse> responseLoanProductsRequestLP2ProgressiveAdvPaymentBuyDownFeesWithChargeOffReason = loanProductsApi
3359+
.createLoanProduct(loanProductsRequestLP2ProgressiveAdvPaymentBuyDownFeesWithChargeOffReason).execute();
3360+
TestContext.INSTANCE.set(
3361+
TestContextKey.DEFAULT_LOAN_PRODUCT_CREATE_RESPONSE_LP2_PROGRESSIVE_ADV_PYMNT_BUYDOWN_FEES_CHARGE_OFF_REASON,
3362+
responseLoanProductsRequestLP2ProgressiveAdvPaymentBuyDownFeesWithChargeOffReason);
3363+
33373364
}
33383365

33393366
public static AdvancedPaymentData createPaymentAllocation(String transactionType, String futureInstallmentAllocationRule,

fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/common/JournalEntriesStepDef.java

Lines changed: 67 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,6 @@ public void journalEntryDataCheck(String transactionType, String transactionDate
6969
DateTimeFormatter formatter = DateTimeFormatter.ofPattern(DATE_FORMAT);
7070
Response<PostLoansResponse> loanResponse = testContext().get(TestContextKey.LOAN_CREATE_RESPONSE);
7171
long loanId = loanResponse.body().getLoanId();
72-
String resourceId = String.valueOf(loanId);
7372

7473
Response<GetLoansLoanIdResponse> loanDetailsResponse = loansApi.retrieveLoan(loanId, false, "transactions", "", "").execute();
7574
ErrorHelper.checkSuccessfulApiCall(loanDetailsResponse);
@@ -83,6 +82,50 @@ public void journalEntryDataCheck(String transactionType, String transactionDate
8382
&& transactionTypeExpected.equals(t.getType().getCode().substring(20)))
8483
.collect(Collectors.toList());
8584

85+
List<List<JournalEntryTransactionItem>> journalLinesActualList = getJournalLinesActualList(transactionsMatch);
86+
checkJournalEntryData(journalLinesActualList, loanId, table);
87+
}
88+
89+
public void checkJournalEntryData(List<List<JournalEntryTransactionItem>> journalLinesActualList, long loanId, DataTable table) {
90+
String resourceId = String.valueOf(loanId);
91+
92+
List<List<String>> data = table.asLists();
93+
final int expectedCount = data.size() - 1;
94+
final int actualCount = journalLinesActualList.stream().mapToInt(List::size).sum();
95+
assertThat(actualCount).as("The number of journal entries for the transaction does not match the expected count! Expected: "
96+
+ expectedCount + ", Actual: " + actualCount).isEqualTo(expectedCount);
97+
for (int i = 1; i < data.size(); i++) {
98+
List<List<List<String>>> possibleActualValuesList = new ArrayList<>();
99+
List<String> expectedValues = data.get(i);
100+
boolean containsAnyExpected = false;
101+
102+
for (int j = 0; j < journalLinesActualList.size(); j++) {
103+
List<JournalEntryTransactionItem> journalLinesActual = journalLinesActualList.get(j);
104+
105+
List<List<String>> actualValuesList = journalLinesActual.stream().map(t -> {
106+
List<String> actualValues = new ArrayList<>();
107+
actualValues.add(t.getGlAccountType().getValue() == null ? null : t.getGlAccountType().getValue());
108+
actualValues.add(t.getGlAccountCode() == null ? null : t.getGlAccountCode());
109+
actualValues.add(t.getGlAccountName() == null ? null : t.getGlAccountName());
110+
actualValues.add("DEBIT".equals(t.getEntryType().getValue()) ? String.valueOf(t.getAmount()) : null);
111+
actualValues.add("CREDIT".equals(t.getEntryType().getValue()) ? String.valueOf(t.getAmount()) : null);
112+
113+
return actualValues;
114+
}).collect(Collectors.toList());
115+
possibleActualValuesList.add(actualValuesList);
116+
117+
boolean containsExpectedValues = actualValuesList.stream().anyMatch(actualValues -> actualValues.equals(expectedValues));
118+
if (containsExpectedValues) {
119+
containsAnyExpected = true;
120+
}
121+
}
122+
assertThat(containsAnyExpected)
123+
.as(ErrorMessageHelper.wrongValueInLineInJournalEntries(resourceId, i, possibleActualValuesList, expectedValues))
124+
.isTrue();
125+
}
126+
}
127+
128+
public List<List<JournalEntryTransactionItem>> getJournalLinesActualList(List<GetLoansLoanIdTransactions> transactionsMatch) {
86129
List<List<JournalEntryTransactionItem>> journalLinesActualList = transactionsMatch.stream().map(t -> {
87130
String transactionId = "L" + t.getId();
88131
Response<GetJournalEntriesTransactionIdResponse> journalEntryDataResponse = null;
@@ -116,40 +159,33 @@ public void journalEntryDataCheck(String transactionType, String transactionDate
116159
return journalEntryDataResponse.body().getPageItems();
117160
}).collect(Collectors.toList());
118161

119-
List<List<String>> data = table.asLists();
120-
final int expectedCount = data.size() - 1;
121-
final int actualCount = journalLinesActualList.stream().mapToInt(List::size).sum();
122-
assertThat(actualCount).as("The number of journal entries for the transaction does not match the expected count! Expected: "
123-
+ expectedCount + ", Actual: " + actualCount).isEqualTo(expectedCount);
124-
for (int i = 1; i < data.size(); i++) {
125-
List<List<List<String>>> possibleActualValuesList = new ArrayList<>();
126-
List<String> expectedValues = data.get(i);
127-
boolean containsAnyExpected = false;
162+
return journalLinesActualList;
163+
}
128164

129-
for (int j = 0; j < journalLinesActualList.size(); j++) {
130-
List<JournalEntryTransactionItem> journalLinesActual = journalLinesActualList.get(j);
165+
@Then("Loan Transactions tab has {int} a {string} transactions with date {string} which has the following Journal entries:")
166+
public void journalEntryDataCheck(int numberTrns, String transactionType, String transactionDate, DataTable table) throws IOException {
167+
DateTimeFormatter formatter = DateTimeFormatter.ofPattern(DATE_FORMAT);
168+
Response<PostLoansResponse> loanResponse = testContext().get(TestContextKey.LOAN_CREATE_RESPONSE);
169+
long loanId = loanResponse.body().getLoanId();
131170

132-
List<List<String>> actualValuesList = journalLinesActual.stream().map(t -> {
133-
List<String> actualValues = new ArrayList<>();
134-
actualValues.add(t.getGlAccountType().getValue() == null ? null : t.getGlAccountType().getValue());
135-
actualValues.add(t.getGlAccountCode() == null ? null : t.getGlAccountCode());
136-
actualValues.add(t.getGlAccountName() == null ? null : t.getGlAccountName());
137-
actualValues.add("DEBIT".equals(t.getEntryType().getValue()) ? String.valueOf(t.getAmount()) : null);
138-
actualValues.add("CREDIT".equals(t.getEntryType().getValue()) ? String.valueOf(t.getAmount()) : null);
171+
Response<GetLoansLoanIdResponse> loanDetailsResponse = loansApi.retrieveLoan(loanId, false, "transactions", "", "").execute();
172+
ErrorHelper.checkSuccessfulApiCall(loanDetailsResponse);
139173

140-
return actualValues;
141-
}).collect(Collectors.toList());
142-
possibleActualValuesList.add(actualValuesList);
174+
TransactionType transactionType1 = TransactionType.valueOf(transactionType);
175+
String transactionTypeExpected = transactionType1.getValue();
143176

144-
boolean containsExpectedValues = actualValuesList.stream().anyMatch(actualValues -> actualValues.equals(expectedValues));
145-
if (containsExpectedValues) {
146-
containsAnyExpected = true;
147-
}
148-
}
149-
assertThat(containsAnyExpected)
150-
.as(ErrorMessageHelper.wrongValueInLineInJournalEntries(resourceId, i, possibleActualValuesList, expectedValues))
151-
.isTrue();
152-
}
177+
List<GetLoansLoanIdTransactions> transactions = loanDetailsResponse.body().getTransactions();
178+
List<GetLoansLoanIdTransactions> transactionsMatch = transactions.stream()
179+
.filter(t -> transactionDate.equals(formatter.format(t.getDate()))
180+
&& transactionTypeExpected.equals(t.getType().getCode().substring(20)))
181+
.collect(Collectors.toList());
182+
assertThat(transactionsMatch.size())
183+
.as("The number of journal entries for the transaction does not match the expected count! Expected: " + numberTrns
184+
+ ", Actual: " + transactionsMatch.size())
185+
.isEqualTo(numberTrns);
186+
187+
List<List<JournalEntryTransactionItem>> journalLinesActualList = getJournalLinesActualList(transactionsMatch);
188+
checkJournalEntryData(journalLinesActualList, loanId, table);
153189
}
154190

155191
@Then("Reversed loan capitalized income amortization transaction has the following Journal entries:")

fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/support/TestContextKey.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,7 @@ public abstract class TestContextKey {
156156
public static final String DEFAULT_LOAN_PRODUCT_CREATE_RESPONSE_LP2_ADV_PYMNT_INTEREST_DAILY_EMI_360_30_INTEREST_RECALC_DAILY_CASH_ACCOUNTING_DISBURSEMENT_CHARGES = "loanProductCreateResponseLP2AdvancedPaymentInterestDailyEmi36030InterestRecalculationDailyCashBasedDisbursementCharge";
157157
public static final String DEFAULT_LOAN_PRODUCT_CREATE_RESPONSE_LP2_PROGRESSIVE_ADV_PYMNT_CAPITALIZED_INCOME = "loanProductCreateResponseLP2ProgressiveAdvancedPaymentCapitalizedIncome";
158158
public static final String DEFAULT_LOAN_PRODUCT_CREATE_RESPONSE_LP2_PROGRESSIVE_ADV_PYMNT_BUYDOWN_FEES = "loanProductCreateResponseLP2ProgressiveAdvancedPaymentBuyDownFees";
159+
public static final String DEFAULT_LOAN_PRODUCT_CREATE_RESPONSE_LP2_PROGRESSIVE_ADV_PYMNT_BUYDOWN_FEES_CHARGE_OFF_REASON = "loanProductCreateResponseLP2ProgressiveAdvancedPaymentBuyDownFeesWithChargeOffReason";
159160
public static final String DEFAULT_LOAN_PRODUCT_CREATE_RESPONSE_LP2_ADV_PYMNT_INTEREST_DAILY_EMI_360_30_INTEREST_RECALC_DAILY_CAPITALIZED_INCOME_ADJ_CUSTOM_ALLOC = "loanProductCreateResponseLP2AdvancedPaymentInterestDailyEmi36030InterestRecalculationDailyCapitalizedIncomeAdjCustomAlloc";
160161
public static final String DEFAULT_LOAN_PRODUCT_CREATE_RESPONSE_LP2_ADV_PYMNT_INTEREST_DAILY_RECALC_EMI_360_30_MULTIDISB_OVER_APPLIED_PERCENTAGE_CAPITALIZED_INCOME = "loanProductCreateResponseLP2AdvancedPaymentInterestDailyEmi36030InterestRecalculationDailyMultidisbursalApprovedOVerAppliedPercentageCapitalizedIncome";
161162
public static final String DEFAULT_LOAN_PRODUCT_CREATE_RESPONSE_LP2_ADV_PYMNT_INTEREST_DAILY_RECALC_EMI_360_30_MULTIDISB_OVER_APPLIED_FLAT_CAPITALIZED_INCOME = "loanProductCreateResponseLP2AdvancedPaymentInterestDailyEmi36030InterestRecalculationDailyMultidisbursalApprovedOVerAppliedFlatCapitalizedIncome";

fineract-e2e-tests-runner/src/test/resources/features/AssetExternalization.feature

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1809,15 +1809,15 @@ Feature: Asset Externalization
18091809
@TestRailId:C3800 @AssetExternalizationJournalEntry
18101810
Scenario: Verify manual journal entry with External Asset Owner empty value if asset-externalization is enabled - UC2
18111811
Given Global configuration "asset-externalization-of-non-active-loans" is enabled
1812-
When Admin sets the business date to "25 June 2025"
1813-
Then Admin creates manual Journal entry with "88" amount and "20 June 2025" date and without External Asset Owner
1812+
When Admin sets the business date to "10 June 2025"
1813+
Then Admin creates manual Journal entry with "88" amount and "10 June 2025" date and without External Asset Owner
18141814
Then Verify manual Journal entry with External Asset Owner "true" and with the following Journal entries:
18151815
| Type | Account code | Account name | Debit | Credit | Manual Entry |
18161816
| ASSET | 112601 | Loans Receivable | 88.0 | | true |
18171817
| LIABILITY | 145023 | Suspense/Clearing account | | 88.0 | true |
18181818
Given Global configuration "asset-externalization-of-non-active-loans" is enabled
18191819

1820-
@TestRailId:C38001 @AssetExternalizationJournalEntry
1820+
@TestRailId:C3801 @AssetExternalizationJournalEntry
18211821
Scenario: Verify manual journal entry with External Asset Owner empty value if asset-externalization is enabled for existing loan - UC3
18221822
Given Global configuration "asset-externalization-of-non-active-loans" is enabled
18231823

@@ -1835,18 +1835,18 @@ Feature: Asset Externalization
18351835
| settlementDate | purchasePriceRatio | status | effectiveFrom | effectiveTo | Transaction type |
18361836
| 2025-06-01 | 1 | PENDING | 2025-06-01 | 9999-12-31 | SALE |
18371837

1838-
When Admin sets the business date to "25 June 2025"
1839-
Then Admin creates manual Journal entry with "99" amount and "20 June 2025" date and without External Asset Owner
1838+
When Admin sets the business date to "27 June 2025"
1839+
Then Admin creates manual Journal entry with "99" amount and "27 June 2025" date and unique External Asset Owner
18401840
Then Verify manual Journal entry with External Asset Owner "true" and with the following Journal entries:
18411841
| Type | Account code | Account name | Debit | Credit | Manual Entry |
18421842
| ASSET | 112601 | Loans Receivable | 99.0 | | true |
18431843
| LIABILITY | 145023 | Suspense/Clearing account | | 99.0 | true |
18441844
Given Global configuration "asset-externalization-of-non-active-loans" is enabled
18451845

1846-
When Loan Pay-off is made on "25 June 2025"
1846+
When Loan Pay-off is made on "26 June 2025"
18471847
Then Loan's all installments have obligations met
18481848

1849-
@TestRailId:C3802 @AssetExternalizationJournalEntry
1849+
@TestRailId:C3821 @AssetExternalizationJournalEntry
18501850
Scenario: Verify manual journal entry with no External Asset Owner value if asset-externalization is disabled - UC4
18511851
Given Global configuration "asset-externalization-of-non-active-loans" is disabled
18521852
When Admin sets the business date to "25 June 2025"

0 commit comments

Comments
 (0)