FINERACT-2081: Add all loan term validations to loan details
This commit is contained in:
committed by
Arnold Gálovics
parent
fe16b61c72
commit
25866edfc9
@@ -607,6 +607,17 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"default": null,
|
||||
"name": "loanTermVariations",
|
||||
"type": [
|
||||
"null",
|
||||
{
|
||||
"type": "array",
|
||||
"items": "org.apache.fineract.avro.loan.v1.LoanTermVariationsDataV1"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"default": null,
|
||||
"name": "clientActiveLoanOptions",
|
||||
|
||||
+1
@@ -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";
|
||||
|
||||
+15
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
+212
-82
@@ -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 | |
|
||||
+11
@@ -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;
|
||||
|
||||
+5
-9
@@ -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() {
|
||||
|
||||
+20
-2
@@ -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);
|
||||
|
||||
}
|
||||
|
||||
+4
-4
@@ -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);
|
||||
|
||||
+1
-1
@@ -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
|
||||
|
||||
+14
-4
@@ -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);
|
||||
|
||||
+42
-6
@@ -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")
|
||||
|
||||
+3
-2
@@ -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,
|
||||
|
||||
+2
-2
@@ -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();
|
||||
|
||||
-3
@@ -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);
|
||||
|
||||
-29
@@ -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();
|
||||
|
||||
+3
@@ -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();
|
||||
|
||||
+10
-17
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user