This is an automated email from the ASF dual-hosted git repository.
arnold pushed a commit to branch develop
in repository https://gitbox.apache.org/repos/asf/fineract.git
The following commit(s) were added to refs/heads/develop by this push:
new 25866edfc FINERACT-2081: Add all loan term validations to loan details
25866edfc is described below
commit 25866edfc93827b65ca3137a6ce32a5b772fe0c1
Author: Oleksii Novikov <[email protected]>
AuthorDate: Thu Oct 3 14:21:01 2024 +0300
FINERACT-2081: Add all loan term validations to loan details
---
.../src/main/avro/loan/v1/LoanAccountDataV1.avsc | 11 +
.../dataqueries/api/DataTableApiConstant.java | 1 +
.../fineract/test/helper/ErrorMessageHelper.java | 15 ++
.../fineract/test/stepdef/loan/LoanStepDef.java | 294 +++++++++++++++------
.../src/test/resources/features/Loan.feature | 29 ++
.../loanaccount/data/LoanTermVariationsData.java | 11 +
.../portfolio/loanaccount/domain/Loan.java | 14 +-
.../domain/LoanTermVariationsRepository.java | 22 +-
.../loan/LoanBusinessEventSerializer.java | 8 +-
.../jobs/domain/JobExecutionRepository.java | 2 +-
.../loanaccount/api/LoansApiResource.java | 18 +-
.../loanaccount/api/LoansApiResourceSwagger.java | 48 +++-
.../loanaccount/data/LoanAccountData.java | 5 +-
...nRescheduleRequestWritePlatformServiceImpl.java | 4 +-
.../service/LoanReadPlatformService.java | 3 -
.../service/LoanReadPlatformServiceImpl.java | 29 --
.../ClientLoanIntegrationTest.java | 3 +
.../ExternalBusinessEventTest.java | 27 +-
18 files changed, 383 insertions(+), 161 deletions(-)
diff --git a/fineract-avro-schemas/src/main/avro/loan/v1/LoanAccountDataV1.avsc
b/fineract-avro-schemas/src/main/avro/loan/v1/LoanAccountDataV1.avsc
index c56a9fc76..b97a2153c 100644
--- a/fineract-avro-schemas/src/main/avro/loan/v1/LoanAccountDataV1.avsc
+++ b/fineract-avro-schemas/src/main/avro/loan/v1/LoanAccountDataV1.avsc
@@ -607,6 +607,17 @@
}
]
},
+ {
+ "default": null,
+ "name": "loanTermVariations",
+ "type": [
+ "null",
+ {
+ "type": "array",
+ "items":
"org.apache.fineract.avro.loan.v1.LoanTermVariationsDataV1"
+ }
+ ]
+ },
{
"default": null,
"name": "clientActiveLoanOptions",
diff --git
a/fineract-core/src/main/java/org/apache/fineract/infrastructure/dataqueries/api/DataTableApiConstant.java
b/fineract-core/src/main/java/org/apache/fineract/infrastructure/dataqueries/api/DataTableApiConstant.java
index 85bb7fb8e..5cddec376 100644
---
a/fineract-core/src/main/java/org/apache/fineract/infrastructure/dataqueries/api/DataTableApiConstant.java
+++
b/fineract-core/src/main/java/org/apache/fineract/infrastructure/dataqueries/api/DataTableApiConstant.java
@@ -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";
diff --git
a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/helper/ErrorMessageHelper.java
b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/helper/ErrorMessageHelper.java
index 357db71a4..469c96d9b 100644
---
a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/helper/ErrorMessageHelper.java
+++
b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/helper/ErrorMessageHelper.java
@@ -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);
+ }
}
diff --git
a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/loan/LoanStepDef.java
b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/loan/LoanStepDef.java
index 9b36c3828..4fdf238d2 100644
---
a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/loan/LoanStepDef.java
+++
b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/loan/LoanStepDef.java
@@ -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);
-
- 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);
+ public void createFullyCustomizedLoan(final DataTable table) throws
IOException {
+ final List<List<String>> data = table.asLists();
+ createCustomizedLoan(data.get(1), false);
+ }
- 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();
-
- Response<GetLoansLoanIdResponse> loanDetails =
loansApi.retrieveLoan(loanId, false, "", "", "").execute();
- Long statusExpected =
Long.valueOf(loanDetails.body().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);
+ }
- 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;
+ }
}
diff --git a/fineract-e2e-tests-runner/src/test/resources/features/Loan.feature
b/fineract-e2e-tests-runner/src/test/resources/features/Loan.feature
index 3af5410f0..938813a1c 100644
--- a/fineract-e2e-tests-runner/src/test/resources/features/Loan.feature
+++ b/fineract-e2e-tests-runner/src/test/resources/features/Loan.feature
@@ -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 |
|
\ No newline at end of file
diff --git
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanTermVariationsData.java
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanTermVariationsData.java
index 7b9858f07..55ee574bf 100644
---
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanTermVariationsData.java
+++
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanTermVariationsData.java
@@ -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;
diff --git
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java
index 1ac524f9b..82f58a308 100644
---
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java
+++
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java
@@ -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() {
diff --git
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/domain/LoanTermVariationsRepository.java
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/domain/LoanTermVariationsRepository.java
index 70af211d1..d667825e9 100644
---
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/domain/LoanTermVariationsRepository.java
+++
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/domain/LoanTermVariationsRepository.java
@@ -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);
}
diff --git
a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/event/external/service/serialization/serializer/loan/LoanBusinessEventSerializer.java
b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/event/external/service/serialization/serializer/loan/LoanBusinessEventSerializer.java
index 561d796f5..36cfe8637 100644
---
a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/event/external/service/serialization/serializer/loan/LoanBusinessEventSerializer.java
+++
b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/event/external/service/serialization/serializer/loan/LoanBusinessEventSerializer.java
@@ -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);
diff --git
a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/domain/JobExecutionRepository.java
b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/domain/JobExecutionRepository.java
index bbfff4ca7..6acd195b6 100644
---
a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/domain/JobExecutionRepository.java
+++
b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/domain/JobExecutionRepository.java
@@ -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
diff --git
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoansApiResource.java
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoansApiResource.java
index 9ab94e1c9..c4d494910 100644
---
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoansApiResource.java
+++
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoansApiResource.java
@@ -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);
diff --git
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoansApiResourceSwagger.java
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoansApiResourceSwagger.java
index 39a4603ba..5b1b8e9bb 100644
---
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoansApiResourceSwagger.java
+++
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoansApiResourceSwagger.java
@@ -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")
diff --git
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanAccountData.java
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanAccountData.java
index 8b382aacd..1b87b9559 100644
---
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanAccountData.java
+++
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanAccountData.java
@@ -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,
diff --git
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/service/LoanRescheduleRequestWritePlatformServiceImpl.java
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/service/LoanRescheduleRequestWritePlatformServiceImpl.java
index 235155aaa..24e0befbf 100644
---
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/service/LoanRescheduleRequestWritePlatformServiceImpl.java
+++
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/service/LoanRescheduleRequestWritePlatformServiceImpl.java
@@ -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();
diff --git
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformService.java
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformService.java
index 9d626b4d5..8f39c262e 100644
---
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformService.java
+++
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformService.java
@@ -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);
diff --git
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java
index 4e38213f2..c80f79d89 100644
---
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java
+++
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java
@@ -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();
diff --git
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/ClientLoanIntegrationTest.java
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/ClientLoanIntegrationTest.java
index 5b4ebbecb..cdba5ea62 100644
---
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/ClientLoanIntegrationTest.java
+++
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/ClientLoanIntegrationTest.java
@@ -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();
diff --git
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/ExternalBusinessEventTest.java
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/ExternalBusinessEventTest.java
index 10f5d3b9a..33f4fbe8e 100644
---
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/ExternalBusinessEventTest.java
+++
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/ExternalBusinessEventTest.java
@@ -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;
}
}