FINERACT-2081: Add all loan term validations to loan details

This commit is contained in:
Oleksii Novikov
2024-10-12 08:05:30 +02:00
committed by Arnold Gálovics
parent fe16b61c72
commit 25866edfc9
18 changed files with 383 additions and 161 deletions
@@ -607,6 +607,17 @@
}
]
},
{
"default": null,
"name": "loanTermVariations",
"type": [
"null",
{
"type": "array",
"items": "org.apache.fineract.avro.loan.v1.LoanTermVariationsDataV1"
}
]
},
{
"default": null,
"name": "clientActiveLoanOptions",
@@ -49,6 +49,7 @@ public final class DataTableApiConstant {
public static final String linkedAccountAssociateParamName = "linkedAccount";
public static final String multiDisburseDetailsAssociateParamName = "multiDisburseDetails";
public static final String futureScheduleAssociateParamName = "futureSchedule";
public static final String loanTermVariationsAssociateParamName = "loanTermVariations";
public static final String meetingAssociateParamName = "meeting";
public static final String emiAmountVariationsAssociateParamName = "emiAmountVariations";
public static final String collectionAssociateParamName = "collection";
@@ -24,6 +24,7 @@ import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import org.apache.commons.lang3.StringUtils;
import org.apache.fineract.client.models.BatchResponse;
import org.apache.fineract.client.models.GetJournalEntriesTransactionIdResponse;
@@ -864,4 +865,18 @@ public final class ErrorMessageHelper {
return String.format("Wrong value in LoanDeteils/fixedLength. %nActual value is: %s %nExpected Value is: %s", actualToStr,
expectedToStr);
}
public static String wrongValueInLineInLoanTermVariations(final int line, final List<List<String>> actual,
final List<String> expected) {
final String actualValues = actual.stream().map(List::toString).collect(Collectors.joining(System.lineSeparator()));
return String.format(
"%nWrong value in loan term variations tab line %d.%nActual values in line (with the same term variation applicable from) are:%n%s%nExpected values in line:%n%s",
line, actualValues, expected);
}
public static String wrongNumberOfLinesInLoanTermVariations(final int actual, final int expected) {
return String.format("Number of lines in loan term variations is not correct. Actual value is: %d - Expected value is: %d", actual,
expected);
}
}
@@ -21,6 +21,7 @@ package org.apache.fineract.test.stepdef.loan;
import static org.apache.fineract.test.data.TransactionProcessingStrategyCode.ADVANCED_PAYMENT_ALLOCATION;
import static org.assertj.core.api.Assertions.assertThat;
import static org.awaitility.Awaitility.await;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import com.google.gson.Gson;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
@@ -54,6 +55,7 @@ import org.apache.fineract.client.models.GetLoanProductsProductIdResponse;
import org.apache.fineract.client.models.GetLoansLoanIdDelinquencySummary;
import org.apache.fineract.client.models.GetLoansLoanIdLoanChargeData;
import org.apache.fineract.client.models.GetLoansLoanIdLoanChargePaidByData;
import org.apache.fineract.client.models.GetLoansLoanIdLoanTermVariations;
import org.apache.fineract.client.models.GetLoansLoanIdRepaymentPeriod;
import org.apache.fineract.client.models.GetLoansLoanIdRepaymentSchedule;
import org.apache.fineract.client.models.GetLoansLoanIdResponse;
@@ -369,74 +371,15 @@ public class LoanStepDef extends AbstractStepDef {
}
@When("Admin creates a fully customized loan with the following data:")
public void createFullyCustomizedLoan(DataTable table) throws IOException {
List<List<String>> data = table.asLists();
List<String> loanData = data.get(1);
String loanProduct = loanData.get(0);
String submitDate = loanData.get(1);
String principal = loanData.get(2);
BigDecimal interestRate = new BigDecimal(loanData.get(3));
String interestType = loanData.get(4);
String interestCalculationPeriod = loanData.get(5);
String amortizationType = loanData.get(6);
Integer loanTermFrequency = Integer.valueOf(loanData.get(7));
String loanTermFrequencyType = loanData.get(8);
Integer repaymentFrequency = Integer.valueOf(loanData.get(9));
String repaymentFrequencyType = loanData.get(10);
Integer numberOfRepayments = Integer.valueOf(loanData.get(11));
Integer graceOnPrincipalPayment = Integer.valueOf(loanData.get(12));
Integer graceOnInterestPayment = Integer.valueOf(loanData.get(13));
Integer graceOnInterestCharged = Integer.valueOf(loanData.get(14));
String transactionProcessingStrategyCode = loanData.get(15);
public void createFullyCustomizedLoan(final DataTable table) throws IOException {
final List<List<String>> data = table.asLists();
createCustomizedLoan(data.get(1), false);
}
Response<PostClientsResponse> clientResponse = testContext().get(TestContextKey.CLIENT_CREATE_RESPONSE);
Long clientId = clientResponse.body().getClientId();
DefaultLoanProduct product = DefaultLoanProduct.valueOf(loanProduct);
Long loanProductId = loanProductResolver.resolve(product);
LoanTermFrequencyType termFrequencyType = LoanTermFrequencyType.valueOf(loanTermFrequencyType);
Integer loanTermFrequencyTypeValue = termFrequencyType.getValue();
RepaymentFrequencyType repaymentFrequencyType1 = RepaymentFrequencyType.valueOf(repaymentFrequencyType);
Integer repaymentFrequencyTypeValue = repaymentFrequencyType1.getValue();
InterestType interestType1 = InterestType.valueOf(interestType);
Integer interestTypeValue = interestType1.getValue();
InterestCalculationPeriodTime interestCalculationPeriod1 = InterestCalculationPeriodTime.valueOf(interestCalculationPeriod);
Integer interestCalculationPeriodValue = interestCalculationPeriod1.getValue();
AmortizationType amortizationType1 = AmortizationType.valueOf(amortizationType);
Integer amortizationTypeValue = amortizationType1.getValue();
TransactionProcessingStrategyCode processingStrategyCode = TransactionProcessingStrategyCode
.valueOf(transactionProcessingStrategyCode);
String transactionProcessingStrategyCodeValue = processingStrategyCode.getValue();
PostLoansRequest loansRequest = loanRequestFactory.defaultLoansRequest(clientId)//
.productId(loanProductId)//
.principal(new BigDecimal(principal))//
.interestRatePerPeriod(interestRate)//
.interestType(interestTypeValue)//
.interestCalculationPeriodType(interestCalculationPeriodValue)//
.amortizationType(amortizationTypeValue)//
.loanTermFrequency(loanTermFrequency)//
.loanTermFrequencyType(loanTermFrequencyTypeValue)//
.numberOfRepayments(numberOfRepayments)//
.repaymentEvery(repaymentFrequency)//
.repaymentFrequencyType(repaymentFrequencyTypeValue)//
.submittedOnDate(submitDate)//
.expectedDisbursementDate(submitDate)//
.graceOnPrincipalPayment(graceOnPrincipalPayment)//
.graceOnInterestPayment(graceOnInterestPayment)//
.graceOnInterestPayment(graceOnInterestCharged).transactionProcessingStrategyCode(transactionProcessingStrategyCodeValue);//
Response<PostLoansResponse> response = loansApi.calculateLoanScheduleOrSubmitLoanApplication(loansRequest, "").execute();
testContext().set(TestContextKey.LOAN_CREATE_RESPONSE, response);
ErrorHelper.checkSuccessfulApiCall(response);
eventCheckHelper.createLoanEventCheck(response);
@When("Admin creates a fully customized loan with emi and the following data:")
public void createFullyCustomizedLoanWithEmi(final DataTable table) throws IOException {
final List<List<String>> data = table.asLists();
createCustomizedLoan(data.get(1), true);
}
@When("Admin creates a fully customized loan with fixed length {int} and with the following data:")
@@ -842,24 +785,36 @@ public class LoanStepDef extends AbstractStepDef {
@And("Admin successfully disburse the loan on {string} with {string} EUR transaction amount")
public void disburseLoan(String actualDisbursementDate, String transactionAmount) throws IOException {
Response<PostLoansResponse> loanResponse = testContext().get(TestContextKey.LOAN_CREATE_RESPONSE);
long loanId = loanResponse.body().getLoanId();
PostLoansLoanIdRequest disburseRequest = LoanRequestFactory.defaultLoanDisburseRequest()
final Response<PostLoansResponse> loanResponse = testContext().get(TestContextKey.LOAN_CREATE_RESPONSE);
assertNotNull(loanResponse.body());
final long loanId = loanResponse.body().getLoanId();
final PostLoansLoanIdRequest disburseRequest = LoanRequestFactory.defaultLoanDisburseRequest()
.actualDisbursementDate(actualDisbursementDate).transactionAmount(new BigDecimal(transactionAmount));
performLoanDisbursementAndVerifyStatus(loanId, disburseRequest);
}
Response<PostLoansLoanIdResponse> loanDisburseResponse = loansApi.stateTransitions(loanId, disburseRequest, "disburse").execute();
testContext().set(TestContextKey.LOAN_DISBURSE_RESPONSE, loanDisburseResponse);
ErrorHelper.checkSuccessfulApiCall(loanDisburseResponse);
Long statusActual = loanDisburseResponse.body().getChanges().getStatus().getId();
@And("Admin successfully disburse the loan on {string} with {string} EUR transaction amount and {string} fixed emi amount")
public void disburseLoanWithFixedEmiAmount(final String actualDisbursementDate, final String transactionAmount,
final String fixedEmiAmount) throws IOException {
final Response<PostLoansResponse> loanResponse = testContext().get(TestContextKey.LOAN_CREATE_RESPONSE);
assertNotNull(loanResponse.body());
final long loanId = loanResponse.body().getLoanId();
final PostLoansLoanIdRequest disburseRequest = LoanRequestFactory.defaultLoanDisburseRequest()
.actualDisbursementDate(actualDisbursementDate).transactionAmount(new BigDecimal(transactionAmount))
.fixedEmiAmount(new BigDecimal(fixedEmiAmount));
performLoanDisbursementAndVerifyStatus(loanId, disburseRequest);
}
Response<GetLoansLoanIdResponse> loanDetails = loansApi.retrieveLoan(loanId, false, "", "", "").execute();
Long statusExpected = Long.valueOf(loanDetails.body().getStatus().getId());
assertThat(statusActual)//
.as(ErrorMessageHelper.wrongLoanStatus(Math.toIntExact(statusActual), Math.toIntExact(statusExpected)))//
.isEqualTo(statusExpected);//
eventCheckHelper.disburseLoanEventCheck(loanDisburseResponse);
eventCheckHelper.loanDisbursalTransactionEventCheck(loanDisburseResponse);
@And("Admin successfully disburse the loan on {string} with {string} EUR transaction amount, {string} EUR fixed emi amount and adjust repayment date on {string}")
public void disburseLoanWithFixedEmiAmountAndAdjustRepaymentDate(final String actualDisbursementDate, final String transactionAmount,
final String fixedEmiAmount, final String adjustRepaymentDate) throws IOException {
final Response<PostLoansResponse> loanResponse = testContext().get(TestContextKey.LOAN_CREATE_RESPONSE);
assertNotNull(loanResponse.body());
final long loanId = loanResponse.body().getLoanId();
final PostLoansLoanIdRequest disburseRequest = LoanRequestFactory.defaultLoanDisburseRequest()
.actualDisbursementDate(actualDisbursementDate).transactionAmount(new BigDecimal(transactionAmount))
.fixedEmiAmount(new BigDecimal(fixedEmiAmount)).adjustRepaymentDate(adjustRepaymentDate);
performLoanDisbursementAndVerifyStatus(loanId, disburseRequest);
}
@And("Admin successfully disburse the second loan on {string} with {string} EUR transaction amount")
@@ -1939,6 +1894,155 @@ public class LoanStepDef extends AbstractStepDef {
.isEqualTo(ErrorMessageHelper.disbursePastDateFailure((int) loanId, futureApproveDateISO));
}
@Then("Loan emi amount variations has {int} variation, with the following data:")
public void loanEmiAmountVariationsCheck(final int linesExpected, final DataTable table) throws IOException {
final Response<PostLoansResponse> loanCreateResponse = testContext().get(TestContextKey.LOAN_CREATE_RESPONSE);
assertNotNull(loanCreateResponse.body());
final long loanId = loanCreateResponse.body().getLoanId();
final Response<GetLoansLoanIdResponse> loanDetailsResponse = loansApi.retrieveLoan(loanId, false, "all", "", "").execute();
ErrorHelper.checkSuccessfulApiCall(loanDetailsResponse);
final List<GetLoansLoanIdLoanTermVariations> emiAmountVariations = loanDetailsResponse.body().getEmiAmountVariations();
final List<List<String>> data = table.asLists();
assertNotNull(emiAmountVariations);
final int linesActual = emiAmountVariations.size();
data.stream().skip(1) // skip headers
.forEach(expectedValues -> {
final List<List<String>> actualValuesList = emiAmountVariations.stream()
.map(emi -> fetchValuesOfLoanTermVariations(data.get(0), emi)).collect(Collectors.toList());
final boolean containsExpectedValues = actualValuesList.stream()
.anyMatch(actualValues -> actualValues.equals(expectedValues));
assertThat(containsExpectedValues).as(ErrorMessageHelper
.wrongValueInLineInLoanTermVariations(data.indexOf(expectedValues), actualValuesList, expectedValues)).isTrue();
assertThat(linesActual).as(ErrorMessageHelper.wrongNumberOfLinesInLoanTermVariations(linesActual, linesExpected))
.isEqualTo(linesExpected);
});
}
@Then("Loan term variations has {int} variation, with the following data:")
public void loanTermVariationsCheck(final int linesExpected, final DataTable table) throws IOException {
final Response<PostLoansResponse> loanCreateResponse = testContext().get(TestContextKey.LOAN_CREATE_RESPONSE);
assertNotNull(loanCreateResponse.body());
final long loanId = loanCreateResponse.body().getLoanId();
final Response<GetLoansLoanIdResponse> loanDetailsResponse = loansApi.retrieveLoan(loanId, false, "loanTermVariations", "", "")
.execute();
ErrorHelper.checkSuccessfulApiCall(loanDetailsResponse);
final List<GetLoansLoanIdLoanTermVariations> loanTermVariations = loanDetailsResponse.body().getLoanTermVariations();
assertNotNull(loanTermVariations);
final List<List<String>> data = table.asLists();
final int linesActual = loanTermVariations.size();
data.stream().skip(1) // skip headers
.forEach(expectedValues -> {
final String expectedTermTypeId = expectedValues.get(0);
final List<List<String>> actualValuesList = loanTermVariations.stream().filter(loanTerm -> {
assertNotNull(loanTerm.getTermType());
return expectedTermTypeId.equals(String.valueOf(loanTerm.getTermType().getId()));
}).map(loanTerm -> fetchValuesOfLoanTermVariations(data.get(0), loanTerm)).collect(Collectors.toList());
final boolean containsExpectedValues = actualValuesList.stream()
.anyMatch(actualValues -> actualValues.equals(expectedValues));
assertThat(containsExpectedValues).as(ErrorMessageHelper
.wrongValueInLineInLoanTermVariations(data.indexOf(expectedValues), actualValuesList, expectedValues)).isTrue();
assertThat(linesActual).as(ErrorMessageHelper.wrongNumberOfLinesInLoanTermVariations(linesActual, linesExpected))
.isEqualTo(linesExpected);
});
}
private void createCustomizedLoan(final List<String> loanData, final boolean withEmi) throws IOException {
final String loanProduct = loanData.get(0);
final String submitDate = loanData.get(1);
final String principal = loanData.get(2);
final BigDecimal interestRate = new BigDecimal(loanData.get(3));
final String interestType = loanData.get(4);
final String interestCalculationPeriod = loanData.get(5);
final String amortizationType = loanData.get(6);
final Integer loanTermFrequency = Integer.valueOf(loanData.get(7));
final String loanTermFrequencyType = loanData.get(8);
final Integer repaymentFrequency = Integer.valueOf(loanData.get(9));
final String repaymentFrequencyType = loanData.get(10);
final Integer numberOfRepayments = Integer.valueOf(loanData.get(11));
final Integer graceOnPrincipalPayment = Integer.valueOf(loanData.get(12));
final Integer graceOnInterestPayment = Integer.valueOf(loanData.get(13));
final Integer graceOnInterestCharged = Integer.valueOf(loanData.get(14));
final String transactionProcessingStrategyCode = loanData.get(15);
final Response<PostClientsResponse> clientResponse = testContext().get(TestContextKey.CLIENT_CREATE_RESPONSE);
final Long clientId = clientResponse.body().getClientId();
final DefaultLoanProduct product = DefaultLoanProduct.valueOf(loanProduct);
final Long loanProductId = loanProductResolver.resolve(product);
final LoanTermFrequencyType termFrequencyType = LoanTermFrequencyType.valueOf(loanTermFrequencyType);
final Integer loanTermFrequencyTypeValue = termFrequencyType.getValue();
final RepaymentFrequencyType repaymentFrequencyType1 = RepaymentFrequencyType.valueOf(repaymentFrequencyType);
final Integer repaymentFrequencyTypeValue = repaymentFrequencyType1.getValue();
final InterestType interestType1 = InterestType.valueOf(interestType);
final Integer interestTypeValue = interestType1.getValue();
final InterestCalculationPeriodTime interestCalculationPeriod1 = InterestCalculationPeriodTime.valueOf(interestCalculationPeriod);
final Integer interestCalculationPeriodValue = interestCalculationPeriod1.getValue();
final AmortizationType amortizationType1 = AmortizationType.valueOf(amortizationType);
final Integer amortizationTypeValue = amortizationType1.getValue();
final TransactionProcessingStrategyCode processingStrategyCode = TransactionProcessingStrategyCode
.valueOf(transactionProcessingStrategyCode);
final String transactionProcessingStrategyCodeValue = processingStrategyCode.getValue();
final PostLoansRequest loansRequest = loanRequestFactory.defaultLoansRequest(clientId).productId(loanProductId)
.principal(new BigDecimal(principal)).interestRatePerPeriod(interestRate).interestType(interestTypeValue)
.interestCalculationPeriodType(interestCalculationPeriodValue).amortizationType(amortizationTypeValue)
.loanTermFrequency(loanTermFrequency).loanTermFrequencyType(loanTermFrequencyTypeValue)
.numberOfRepayments(numberOfRepayments).repaymentEvery(repaymentFrequency)
.repaymentFrequencyType(repaymentFrequencyTypeValue).submittedOnDate(submitDate).expectedDisbursementDate(submitDate)
.graceOnPrincipalPayment(graceOnPrincipalPayment).graceOnInterestPayment(graceOnInterestPayment)
.graceOnInterestPayment(graceOnInterestCharged).transactionProcessingStrategyCode(transactionProcessingStrategyCodeValue);
if (withEmi) {
loansRequest.fixedEmiAmount(new BigDecimal(555));
}
final Response<PostLoansResponse> response = loansApi.calculateLoanScheduleOrSubmitLoanApplication(loansRequest, "").execute();
testContext().set(TestContextKey.LOAN_CREATE_RESPONSE, response);
ErrorHelper.checkSuccessfulApiCall(response);
eventCheckHelper.createLoanEventCheck(response);
}
private void performLoanDisbursementAndVerifyStatus(final long loanId, final PostLoansLoanIdRequest disburseRequest)
throws IOException {
final Response<PostLoansLoanIdResponse> loanDisburseResponse = loansApi.stateTransitions(loanId, disburseRequest, "disburse")
.execute();
testContext().set(TestContextKey.LOAN_DISBURSE_RESPONSE, loanDisburseResponse);
ErrorHelper.checkSuccessfulApiCall(loanDisburseResponse);
assertNotNull(loanDisburseResponse.body());
assertNotNull(loanDisburseResponse.body().getChanges());
assertNotNull(loanDisburseResponse.body().getChanges().getStatus());
final Long statusActual = loanDisburseResponse.body().getChanges().getStatus().getId();
assertNotNull(statusActual);
final Response<GetLoansLoanIdResponse> loanDetails = loansApi.retrieveLoan(loanId, false, "", "", "").execute();
assertNotNull(loanDetails.body());
assertNotNull(loanDetails.body().getStatus());
final Long statusExpected = Long.valueOf(loanDetails.body().getStatus().getId());
assertThat(statusActual).as(ErrorMessageHelper.wrongLoanStatus(Math.toIntExact(statusActual), Math.toIntExact(statusExpected)))
.isEqualTo(statusExpected);
eventCheckHelper.disburseLoanEventCheck(loanDisburseResponse);
eventCheckHelper.loanDisbursalTransactionEventCheck(loanDisburseResponse);
}
private LoanStatusEnumDataV1 getExpectedStatus(String loanStatus) {
LoanStatusEnumDataV1 result = new LoanStatusEnumDataV1();
switch (loanStatus) {
@@ -2137,4 +2241,30 @@ public class LoanStepDef extends AbstractStepDef {
}
return actualValues;
}
@SuppressFBWarnings("SF_SWITCH_NO_DEFAULT")
private List<String> fetchValuesOfLoanTermVariations(final List<String> header, final GetLoansLoanIdLoanTermVariations emiVariation) {
final List<String> actualValues = new ArrayList<>();
assertNotNull(emiVariation.getTermType());
for (String headerName : header) {
switch (headerName) {
case "Term Type Id" -> actualValues
.add(emiVariation.getTermType().getId() == null ? null : String.valueOf(emiVariation.getTermType().getId()));
case "Term Type Code" ->
actualValues.add(emiVariation.getTermType().getCode() == null ? null : emiVariation.getTermType().getCode());
case "Term Type Value" ->
actualValues.add(emiVariation.getTermType().getValue() == null ? null : emiVariation.getTermType().getValue());
case "Applicable From" -> actualValues.add(emiVariation.getTermVariationApplicableFrom() == null ? null
: FORMATTER.format(emiVariation.getTermVariationApplicableFrom()));
case "Decimal Value" ->
actualValues.add(emiVariation.getDecimalValue() == null ? null : String.valueOf(emiVariation.getDecimalValue()));
case "Date Value" ->
actualValues.add(emiVariation.getDateValue() == null ? null : FORMATTER.format(emiVariation.getDateValue()));
case "Is Specific To Installment" -> actualValues.add(String.valueOf(emiVariation.getIsSpecificToInstallment()));
case "Is Processed" ->
actualValues.add(emiVariation.getIsProcessed() == null ? null : String.valueOf(emiVariation.getIsProcessed()));
}
}
return actualValues;
}
}
@@ -5728,3 +5728,32 @@ Feature: Loan
| 5 | 31 | 01 June 2024 | | 16.93 | 16.81 | 0.2 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 |
| 6 | 30 | 01 July 2024 | | 0.0 | 16.93 | 0.1 | 0.0 | 0.0 | 17.03 | 0.0 | 0.0 | 0.0 | 17.03 |
When Admin removes "LOAN_INTEREST_RECALCULATION" business step into LOAN_CLOSE_OF_BUSINESS workflow
Scenario: Loan Details Emi Amount Variations - AssociationsAll
Given Global configuration "is-interest-to-be-recovered-first-when-greater-than-emi" is enabled
Given Global configuration "enable-business-date" is enabled
When Admin sets the business date to "1 January 2023"
When Admin creates a client with random data
When Admin creates a fully customized loan with emi and 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 |
| LP1_INTEREST_DECLINING_BALANCE_SAR_RECALCULATION_SAME_AS_REPAYMENT_COMPOUNDING_NONE_MULTIDISB | 01 January 2023 | 10000 | 12 | DECLINING_BALANCE | SAME_AS_REPAYMENT_PERIOD | EQUAL_INSTALLMENTS | 6 | MONTHS | 1 | MONTHS | 6 | 0 | 0 | 0 | PENALTIES_FEES_INTEREST_PRINCIPAL_ORDER |
And Admin successfully approves the loan on "1 January 2023" with "100" amount and expected disbursement date on "1 January 2023"
When Admin successfully disburse the loan on "1 January 2023" with "100" EUR transaction amount and "50" fixed emi amount
Then Loan emi amount variations has 1 variation, with the following data:
| Term Type Id | Term Type Code | Term Type Value | Applicable From | Decimal Value | Date Value | Is Specific To Installment | Is Processed |
| 1 | loanTermType.emiAmount | emiAmount | 01 January 2023 | 50.0 | | false | |
Scenario: Loan Details Loan Term Variations
Given Global configuration "is-interest-to-be-recovered-first-when-greater-than-emi" is enabled
Given Global configuration "enable-business-date" is enabled
When Admin sets the business date to "1 January 2023"
When Admin creates a client with random data
When Admin creates a fully customized loan with emi and 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 |
| LP1_INTEREST_DECLINING_BALANCE_SAR_RECALCULATION_SAME_AS_REPAYMENT_COMPOUNDING_NONE_MULTIDISB | 01 January 2023 | 10000 | 12 | DECLINING_BALANCE | SAME_AS_REPAYMENT_PERIOD | EQUAL_INSTALLMENTS | 6 | MONTHS | 1 | MONTHS | 6 | 0 | 0 | 0 | PENALTIES_FEES_INTEREST_PRINCIPAL_ORDER |
And Admin successfully approves the loan on "1 January 2023" with "100" amount and expected disbursement date on "1 January 2023"
When Admin successfully disburse the loan on "1 January 2023" with "100" EUR transaction amount, "50" EUR fixed emi amount and adjust repayment date on "15 January 2023"
Then Loan term variations has 2 variation, with the following data:
| Term Type Id | Term Type Code | Term Type Value | Applicable From | Decimal Value | Date Value | Is Specific To Installment | Is Processed |
| 1 | loanTermType.emiAmount | emiAmount | 01 January 2023 | 50.0 | | false | |
| 4 | loanTermType.dueDate | dueDate | 01 February 2023 | 50.0 | 15 January 2023 | false | |
@@ -24,6 +24,7 @@ import lombok.Getter;
import org.apache.fineract.infrastructure.core.data.EnumOptionData;
import org.apache.fineract.infrastructure.core.service.DateUtils;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTermVariationType;
import org.apache.fineract.portfolio.loanproduct.service.LoanEnumerations;
@Getter
public class LoanTermVariationsData implements Comparable<LoanTermVariationsData> {
@@ -46,6 +47,16 @@ public class LoanTermVariationsData implements Comparable<LoanTermVariationsData
this.isSpecificToInstallment = isSpecificToInstallment;
}
public LoanTermVariationsData(final Long id, final Integer termType, final LocalDate termVariationApplicableFrom,
final BigDecimal decimalValue, final LocalDate dateValue, final boolean isSpecificToInstallment) {
this.id = id;
this.termType = LoanEnumerations.loanVariationType(LoanTermVariationType.fromInt(termType));
this.termVariationApplicableFrom = termVariationApplicableFrom;
this.decimalValue = decimalValue;
this.dateValue = dateValue;
this.isSpecificToInstallment = isSpecificToInstallment;
}
public LoanTermVariationsData(final EnumOptionData termType, final LocalDate termVariationApplicableFrom, final BigDecimal decimalValue,
LocalDate dateValue, final boolean isSpecificToInstallment) {
this.id = null;
@@ -5366,16 +5366,12 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
return isForeClosure;
}
public Set<LoanTermVariations> getActiveLoanTermVariations() {
Set<LoanTermVariations> retData = new HashSet<>();
if (this.loanTermVariations != null && !this.loanTermVariations.isEmpty()) {
for (LoanTermVariations loanTermVariations : this.loanTermVariations) {
if (loanTermVariations.isActive()) {
retData.add(loanTermVariations);
}
}
public List<LoanTermVariations> getActiveLoanTermVariations() {
if (this.loanTermVariations == null) {
return new ArrayList<>();
}
return !retData.isEmpty() ? retData : null;
return this.loanTermVariations.stream().filter(LoanTermVariations::isActive).collect(Collectors.toList());
}
public boolean isTopup() {
@@ -19,6 +19,7 @@
package org.apache.fineract.portfolio.loanaccount.rescheduleloan.domain;
import java.util.List;
import org.apache.fineract.portfolio.loanaccount.data.LoanTermVariationsData;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTermVariations;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
@@ -28,7 +29,24 @@ import org.springframework.data.repository.query.Param;
public interface LoanTermVariationsRepository
extends JpaRepository<LoanTermVariations, Long>, JpaSpecificationExecutor<LoanTermVariations> {
@Query("select lrr from LoanTermVariations lrr where lrr.loan.id = :loanId")
List<LoanTermVariations> findAllLoanTermVariationsByLoanId(@Param("loanId") Long loanId);
@Query("""
select new org.apache.fineract.portfolio.loanaccount.data.LoanTermVariationsData(
ltv.id, ltv.termType, ltv.termApplicableFrom, ltv.decimalValue, ltv.dateValue, ltv.isSpecificToInstallment
)
from LoanTermVariations ltv
where ltv.loan.id = :loanId
order by ltv.termApplicableFrom
""")
List<LoanTermVariationsData> findLoanTermVariationsByLoanId(@Param("loanId") long loanId);
@Query("""
select new org.apache.fineract.portfolio.loanaccount.data.LoanTermVariationsData(
ltv.id, ltv.termType, ltv.termApplicableFrom, ltv.decimalValue, ltv.dateValue, ltv.isSpecificToInstallment
)
from LoanTermVariations ltv
where ltv.loan.id = :loanId and ltv.termType = :termType
order by ltv.termApplicableFrom
""")
List<LoanTermVariationsData> findLoanTermVariationsByLoanIdAndTermType(@Param("loanId") long loanId, @Param("termType") int termType);
}
@@ -20,7 +20,6 @@ package org.apache.fineract.infrastructure.event.external.service.serialization.
import java.util.Collection;
import java.util.List;
import java.util.Set;
import lombok.RequiredArgsConstructor;
import org.apache.avro.generic.GenericContainer;
import org.apache.commons.collections4.CollectionUtils;
@@ -86,9 +85,10 @@ public class LoanBusinessEventSerializer implements BusinessEventSerializer {
List<LoanInstallmentDelinquencyBucketDataV1> installmentsDelinquencyData = installmentLevelDelinquencyEventProducer
.calculateInstallmentLevelDelinquencyData(event.get(), data.getCurrency());
Set<LoanTermVariations> activeLoanTermVariations = event.get().getActiveLoanTermVariations();
if (activeLoanTermVariations != null) {
data.setEmiAmountVariations(activeLoanTermVariations.stream().map(LoanTermVariations::toData).toList());
List<LoanTermVariations> activeLoanTermVariations = event.get().getActiveLoanTermVariations();
if (!activeLoanTermVariations.isEmpty()) {
data.setLoanTermVariations(activeLoanTermVariations.stream().map(LoanTermVariations::toData).toList());
}
LoanAccountDataV1 result = mapper.map(data);
@@ -123,7 +123,7 @@ public class JobExecutionRepository {
public Long getNotCompletedPartitionsCount(Long jobExecutionId, String partitionerStepName) {
return namedParameterJdbcTemplate.queryForObject("""
SELECT COUNT(bse.STEP_EXECUTION_ID)
SELECT COUNT(BSE.STEP_EXECUTION_ID)
FROM BATCH_STEP_EXECUTION BSE
WHERE
BSE.JOB_EXECUTION_ID = :jobExecutionId
@@ -146,6 +146,7 @@ import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanSchedul
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleType;
import org.apache.fineract.portfolio.loanaccount.loanschedule.service.LoanScheduleCalculationPlatformService;
import org.apache.fineract.portfolio.loanaccount.loanschedule.service.LoanScheduleHistoryReadPlatformService;
import org.apache.fineract.portfolio.loanaccount.rescheduleloan.domain.LoanTermVariationsRepository;
import org.apache.fineract.portfolio.loanaccount.service.GLIMAccountInfoReadPlatformService;
import org.apache.fineract.portfolio.loanaccount.service.LoanChargeReadPlatformService;
import org.apache.fineract.portfolio.loanaccount.service.LoanReadPlatformService;
@@ -287,6 +288,7 @@ public class LoansApiResource {
private final SqlValidator sqlValidator;
private final LoanSummaryBalancesRepository loanSummaryBalancesRepository;
private final ClientReadPlatformService clientReadPlatformService;
private final LoanTermVariationsRepository loanTermVariationsRepository;
/*
* This template API is used for loan approval, ideally this should be invoked on loan that are pending for
@@ -897,7 +899,8 @@ public class LoansApiResource {
Collection<NoteData> notes = null;
PortfolioAccountData linkedAccount = null;
Collection<DisbursementData> disbursementData = null;
Collection<LoanTermVariationsData> emiAmountVariations = null;
List<LoanTermVariationsData> emiAmountVariations = null;
List<LoanTermVariationsData> loanTermVariations = null;
Collection<LoanCollateralResponseData> loanCollateralManagements;
Collection<LoanCollateralManagementData> loanCollateralManagementData = new ArrayList<>();
CollectionData collectionData = this.delinquencyReadPlatformService.calculateLoanCollectionData(resolvedLoanId);
@@ -911,7 +914,8 @@ public class LoansApiResource {
DataTableApiConstant.transactionsAssociateParamName, DataTableApiConstant.chargesAssociateParamName,
DataTableApiConstant.guarantorsAssociateParamName, DataTableApiConstant.collateralAssociateParamName,
DataTableApiConstant.notesAssociateParamName, DataTableApiConstant.linkedAccountAssociateParamName,
DataTableApiConstant.multiDisburseDetailsAssociateParamName, DataTableApiConstant.collectionAssociateParamName));
DataTableApiConstant.multiDisburseDetailsAssociateParamName, DataTableApiConstant.collectionAssociateParamName,
DataTableApiConstant.loanTermVariationsAssociateParamName));
}
ApiParameterHelper.excludeAssociationsForResponseIfProvided(exclude, associationParameters);
@@ -938,10 +942,15 @@ public class LoansApiResource {
if (associationParameters.contains(DataTableApiConstant.emiAmountVariationsAssociateParamName)
|| associationParameters.contains(DataTableApiConstant.repaymentScheduleAssociateParamName)) {
mandatoryResponseParameters.add(DataTableApiConstant.emiAmountVariationsAssociateParamName);
emiAmountVariations = this.loanReadPlatformService.retrieveLoanTermVariations(resolvedLoanId,
emiAmountVariations = this.loanTermVariationsRepository.findLoanTermVariationsByLoanIdAndTermType(resolvedLoanId,
LoanTermVariationType.EMI_AMOUNT.getValue());
}
if (associationParameters.contains(DataTableApiConstant.loanTermVariationsAssociateParamName)) {
mandatoryResponseParameters.add(DataTableApiConstant.loanTermVariationsAssociateParamName);
loanTermVariations = this.loanTermVariationsRepository.findLoanTermVariationsByLoanId(resolvedLoanId);
}
if (associationParameters.contains(DataTableApiConstant.repaymentScheduleAssociateParamName)) {
mandatoryResponseParameters.add(DataTableApiConstant.repaymentScheduleAssociateParamName);
final RepaymentScheduleRelatedLoanData repaymentScheduleRelatedData = new RepaymentScheduleRelatedLoanData(
@@ -1118,7 +1127,8 @@ public class LoansApiResource {
interestCalculationPeriodTypeOptions, fundOptions, chargeOptions, chargeTemplate, allowedLoanOfficers, loanPurposeOptions,
loanCollateralOptions, calendarOptions, notes, accountLinkingOptions, linkedAccount, disbursementData, emiAmountVariations,
overdueCharges, paidInAdvanceTemplate, interestRatesPeriods, clientActiveLoanOptions, rates, isRatesEnabled, collectionData,
LoanScheduleType.getValuesAsEnumOptionDataList(), LoanScheduleProcessingType.getValuesAsEnumOptionDataList());
LoanScheduleType.getValuesAsEnumOptionDataList(), LoanScheduleProcessingType.getValuesAsEnumOptionDataList(),
loanTermVariations);
final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters(),
mandatoryResponseParameters);
@@ -432,11 +432,6 @@ final class LoansApiResourceSwagger {
private GetLoansLoanIdSummary() {}
static final class GetLoansLoanIdEmiVariations {
private GetLoansLoanIdEmiVariations() {}
}
static final class GetLoansLoanIdLinkedAccount {
private GetLoansLoanIdLinkedAccount() {}
@@ -606,7 +601,6 @@ final class LoansApiResourceSwagger {
public Double maxOutstandingLoanBalance;
@Schema(example = "false")
public Boolean canDisburse;
public Set<GetLoansLoanIdEmiVariations> emiAmountVariations;
@Schema(example = "true")
public Boolean inArrears;
@Schema(example = "false")
@@ -1072,6 +1066,38 @@ final class LoansApiResourceSwagger {
}
static final class GetLoansLoanIdLoanTermVariations {
private GetLoansLoanIdLoanTermVariations() {}
static final class GetLoansLoanIdLoanTermEnumData {
private GetLoansLoanIdLoanTermEnumData() {}
@Schema(example = "1")
public Long id;
@Schema(example = "loanTermType.emiAmount")
public String code;
@Schema(example = "emiAmount")
public String value;
}
@Schema(example = "1")
public Long id;
@Schema(description = "Enum option data")
public GetLoansLoanIdLoanTermEnumData termType;
@Schema(example = "[2024, 1, 1]")
public LocalDate termVariationApplicableFrom;
@Schema(example = "200.000000")
public Double decimalValue;
@Schema(example = "[2024, 1, 1]")
public LocalDate dateValue;
@Schema(example = "false")
public boolean isSpecificToInstallment;
@Schema(example = "false")
public boolean isProcessed;
}
@Schema(example = "1")
public Long id;
@Schema(example = "95174ff9-1a75-4d72-a413-6f9b1cb988b7")
@@ -1172,6 +1198,10 @@ final class LoansApiResourceSwagger {
public EnumOptionData loanScheduleType;
@Schema(example = "HORIZONTAL")
public EnumOptionData loanScheduleProcessingType;
@Schema(description = "List of GetLoansLoanIdLoanTermVariations")
public List<GetLoansLoanIdLoanTermVariations> emiAmountVariations;
@Schema(description = "List of GetLoansLoanIdLoanTermVariations")
public List<GetLoansLoanIdLoanTermVariations> loanTermVariations;
}
@Schema(description = "GetLoansResponse")
@@ -1267,6 +1297,8 @@ final class LoansApiResourceSwagger {
public BigDecimal disbursedAmountPercentageForDownPayment;
@Schema(example = "false")
public Boolean enableAutoRepaymentForDownPayment;
@Schema(example = "10.00")
public BigDecimal fixedEmiAmount;
public List<PostLoansRequestChargeData> charges;
@@ -1588,6 +1620,10 @@ final class LoansApiResourceSwagger {
public String withdrawnOnDate;
@Schema(description = "List of PostLoansLoanIdDisbursementData")
public List<PostLoansLoanIdDisbursementData> disbursementData;
@Schema(example = "500.00")
public BigDecimal fixedEmiAmount;
@Schema(example = "28 July 2022")
public String adjustRepaymentDate;
}
@Schema(description = "PostLoansLoanIdResponse")
@@ -194,6 +194,7 @@ public class LoanAccountData {
private Boolean canDisburse;
private Collection<LoanTermVariationsData> emiAmountVariations;
private Collection<LoanTermVariationsData> loanTermVariations;
private Collection<LoanAccountSummaryData> clientActiveLoanOptions;
private Boolean canUseForTopup;
// TODO: avoid prefix "is"
@@ -521,7 +522,7 @@ public class LoanAccountData {
final PaidInAdvanceData paidInAdvance, Collection<InterestRatePeriodData> interestRatesPeriods,
final Collection<LoanAccountSummaryData> clientActiveLoanOptions, final List<RateData> rates, final Boolean isRatesEnabled,
final CollectionData delinquent, final List<EnumOptionData> loanScheduleTypeOptions,
final List<EnumOptionData> loanScheduleProcessingTypeOptions) {
final List<EnumOptionData> loanScheduleProcessingTypeOptions, final List<LoanTermVariationsData> loanTermVariations) {
// TODO: why are these variables 'calendarData', 'chargeTemplate' never used (see original private constructor)
@@ -541,7 +542,7 @@ public class LoanAccountData {
.setOverdueCharges(overdueCharges).setPaidInAdvance(paidInAdvance).setInterestRatesPeriods(interestRatesPeriods)
.setClientActiveLoanOptions(clientActiveLoanOptions).setRates(rates).setIsRatesEnabled(isRatesEnabled)
.setDelinquent(delinquent).setLoanScheduleTypeOptions(loanScheduleTypeOptions)
.setLoanScheduleProcessingTypeOptions(loanScheduleProcessingTypeOptions);
.setLoanScheduleProcessingTypeOptions(loanScheduleProcessingTypeOptions).setLoanTermVariations(loanTermVariations);
}
public LoanAccountData associationsAndTemplate(final Collection<LoanProductData> productOptions,
@@ -370,9 +370,9 @@ public class LoanRescheduleRequestWritePlatformServiceImpl implements LoanResche
final LoanApplicationTerms loanApplicationTerms = loan.constructLoanApplicationTerms(scheduleGeneratorDTO);
LocalDate rescheduleFromDate = null;
Set<LoanTermVariations> activeLoanTermVariations = loan.getActiveLoanTermVariations();
List<LoanTermVariations> activeLoanTermVariations = loan.getActiveLoanTermVariations();
LoanTermVariations dueDateVariationInCurrentRequest = loanRescheduleRequest.getDueDateTermVariationIfExists();
if (dueDateVariationInCurrentRequest != null && activeLoanTermVariations != null) {
if (dueDateVariationInCurrentRequest != null && !activeLoanTermVariations.isEmpty()) {
LocalDate fromScheduleDate = dueDateVariationInCurrentRequest.fetchTermApplicaDate();
LocalDate currentScheduleDate = fromScheduleDate;
LocalDate modifiedScheduleDate = dueDateVariationInCurrentRequest.fetchDateValue();
@@ -32,7 +32,6 @@ import org.apache.fineract.portfolio.loanaccount.data.LoanAccountData;
import org.apache.fineract.portfolio.loanaccount.data.LoanApprovalData;
import org.apache.fineract.portfolio.loanaccount.data.LoanRepaymentScheduleInstallmentData;
import org.apache.fineract.portfolio.loanaccount.data.LoanScheduleAccrualData;
import org.apache.fineract.portfolio.loanaccount.data.LoanTermVariationsData;
import org.apache.fineract.portfolio.loanaccount.data.LoanTransactionData;
import org.apache.fineract.portfolio.loanaccount.data.PaidInAdvanceData;
import org.apache.fineract.portfolio.loanaccount.data.RepaymentScheduleRelatedLoanData;
@@ -99,8 +98,6 @@ public interface LoanReadPlatformService {
DisbursementData retrieveLoanDisbursementDetail(Long loanId, Long disbursementId);
Collection<LoanTermVariationsData> retrieveLoanTermVariations(Long loanId, Integer termType);
Collection<LoanScheduleAccrualData> retriveScheduleAccrualData();
LoanTransactionData retrieveRecoveryPaymentTemplate(Long loanId);
@@ -100,7 +100,6 @@ import org.apache.fineract.portfolio.loanaccount.data.LoanRepaymentScheduleInsta
import org.apache.fineract.portfolio.loanaccount.data.LoanScheduleAccrualData;
import org.apache.fineract.portfolio.loanaccount.data.LoanStatusEnumData;
import org.apache.fineract.portfolio.loanaccount.data.LoanSummaryData;
import org.apache.fineract.portfolio.loanaccount.data.LoanTermVariationsData;
import org.apache.fineract.portfolio.loanaccount.data.LoanTransactionData;
import org.apache.fineract.portfolio.loanaccount.data.LoanTransactionEnumData;
import org.apache.fineract.portfolio.loanaccount.data.LoanTransactionRelationData;
@@ -114,7 +113,6 @@ import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleTra
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepositoryWrapper;
import org.apache.fineract.portfolio.loanaccount.domain.LoanStatus;
import org.apache.fineract.portfolio.loanaccount.domain.LoanSubStatus;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTermVariationType;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRepository;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionType;
@@ -1740,33 +1738,6 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService, Loa
return this.jdbcTemplate.queryForObject(sql, rm, loanId, disbursementId); // NOSONAR
}
@Override
public Collection<LoanTermVariationsData> retrieveLoanTermVariations(Long loanId, Integer termType) {
final LoanTermVariationsMapper rm = new LoanTermVariationsMapper();
final String sql = "select " + rm.schema() + " where tv.loan_id=? and tv.term_type=?";
return this.jdbcTemplate.query(sql, rm, loanId, termType); // NOSONAR
}
private static final class LoanTermVariationsMapper implements RowMapper<LoanTermVariationsData> {
public String schema() {
return "tv.id as id,tv.applicable_date as variationApplicableFrom,tv.decimal_value as decimalValue, tv.date_value as dateValue, tv.is_specific_to_installment as isSpecificToInstallment "
+ "from m_loan_term_variations tv";
}
@Override
public LoanTermVariationsData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException {
final Long id = rs.getLong("id");
final LocalDate variationApplicableFrom = JdbcSupport.getLocalDate(rs, "variationApplicableFrom");
final BigDecimal decimalValue = rs.getBigDecimal("decimalValue");
final LocalDate dateValue = JdbcSupport.getLocalDate(rs, "dateValue");
final boolean isSpecificToInstallment = rs.getBoolean("isSpecificToInstallment");
return new LoanTermVariationsData(id, LoanEnumerations.loanVariationType(LoanTermVariationType.EMI_AMOUNT),
variationApplicableFrom, decimalValue, dateValue, isSpecificToInstallment);
}
}
@Override
public Collection<LoanScheduleAccrualData> retriveScheduleAccrualData() {
final String chargeAccrualDateCriteria = configurationDomainService.getAccrualDateConfigForCharge();
@@ -94,6 +94,7 @@ import org.apache.fineract.infrastructure.core.service.DateUtils;
import org.apache.fineract.integrationtests.common.BusinessDateHelper;
import org.apache.fineract.integrationtests.common.ClientHelper;
import org.apache.fineract.integrationtests.common.CollateralManagementHelper;
import org.apache.fineract.integrationtests.common.LoanRescheduleRequestHelper;
import org.apache.fineract.integrationtests.common.SchedulerJobHelper;
import org.apache.fineract.integrationtests.common.Utils;
import org.apache.fineract.integrationtests.common.accounting.Account;
@@ -176,6 +177,8 @@ public class ClientLoanIntegrationTest extends BaseLoanIntegrationTest {
private static final BusinessDateHelper BUSINESS_DATE_HELPER = new BusinessDateHelper();
private static final ChargesHelper CHARGES_HELPER = new ChargesHelper();
private static final ClientHelper CLIENT_HELPER = new ClientHelper(REQUEST_SPEC, RESPONSE_SPEC);
private static final LoanRescheduleRequestHelper LOAN_RESCHEDULE_REQUEST_HELPER = new LoanRescheduleRequestHelper(REQUEST_SPEC,
RESPONSE_SPEC);
private static RequestSpecification createRequestSpecification() {
RequestSpecification request = new RequestSpecBuilder().setContentType(ContentType.JSON).build();
@@ -290,30 +290,23 @@ public class ExternalBusinessEventTest extends BaseLoanIntegrationTest {
&& Objects.equals(statusId, businessEvent.getStatusId().doubleValue())
&& Objects.equals(principalDisbursed, businessEvent.getPrincipalDisbursed())
&& Objects.equals(principalOutstanding, businessEvent.getPrincipalOutstanding())
&& emiAmountVariationsMatch((List<Map<String, Object>>) externalEvent.getPayLoad().get("emiAmountVariations"),
businessEvent.emiAmountVariationType);
&& loanTermVariationsMatch((List<Map<String, Object>>) externalEvent.getPayLoad().get("loanTermVariations"),
businessEvent.loanTermVariationType);
}).count();
Assertions.assertEquals(1, count, "Expected business event not found " + businessEvent);
}
}
private boolean emiAmountVariationsMatch(List<Map<String, Object>> emiAmountVariations, List<String> expectedTypes) {
private boolean loanTermVariationsMatch(final List<Map<String, Object>> loanTermVariations, final List<String> expectedTypes) {
if (CollectionUtils.isEmpty(expectedTypes)) {
return true;
}
int numberOfMatches = 0;
if (CollectionUtils.isNotEmpty(emiAmountVariations)) {
for (String expectedType : expectedTypes) {
int i = 0;
while (i < emiAmountVariations.size() && !StringUtils
.equals((String) ((Map<String, Object>) emiAmountVariations.get(i).get("termType")).get("value"), expectedType)) {
i++;
}
if (i < emiAmountVariations.size()) {
numberOfMatches++;
}
}
}
final long numberOfMatches = expectedTypes
.stream().filter(
expectedType -> loanTermVariations.stream()
.anyMatch(variation -> StringUtils
.equals((String) ((Map<String, Object>) variation.get("termType")).get("value"), expectedType)))
.count();
return numberOfMatches == expectedTypes.size();
}
@@ -335,6 +328,6 @@ public class ExternalBusinessEventTest extends BaseLoanIntegrationTest {
private Integer statusId;
private Double principalDisbursed;
private Double principalOutstanding;
private List<String> emiAmountVariationType;
private List<String> loanTermVariationType;
}
}