This is an automated email from the ASF dual-hosted git repository. adamsaghy pushed a commit to branch develop in repository https://gitbox.apache.org/repos/asf/fineract.git
commit 41d859e68a29f81f2cbcd4b9e38dd1ff70d51cd6 Author: Oleksii Novikov <[email protected]> AuthorDate: Wed Jan 28 13:41:10 2026 +0200 FINERACT-2421: Add pastDueDate to loan summary and event --- .../src/main/avro/loan/v1/CollectionDataV1.avsc | 8 ++ .../test/stepdef/loan/LoanDelinquencyStepDef.java | 160 +++++++++++---------- .../resources/features/LoanDelinquency.feature | 45 ++++++ .../service/LoanDelinquencyDomainServiceImpl.java | 3 + .../portfolio/loanaccount/data/CollectionData.java | 4 +- .../loanaccount/api/LoansApiResourceSwagger.java | 2 + ...cyWritePlatformServiceRangeChangeEventTest.java | 41 +++--- .../LoanDelinquencyDomainServiceTest.java | 9 ++ 8 files changed, 174 insertions(+), 98 deletions(-) diff --git a/fineract-avro-schemas/src/main/avro/loan/v1/CollectionDataV1.avsc b/fineract-avro-schemas/src/main/avro/loan/v1/CollectionDataV1.avsc index af515e5930..22c3c086ab 100644 --- a/fineract-avro-schemas/src/main/avro/loan/v1/CollectionDataV1.avsc +++ b/fineract-avro-schemas/src/main/avro/loan/v1/CollectionDataV1.avsc @@ -27,6 +27,14 @@ "int" ] }, + { + "default": null, + "name": "pastDueDate", + "type": [ + "null", + "string" + ] + }, { "default": null, "name": "nextPaymentDueDate", diff --git a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/loan/LoanDelinquencyStepDef.java b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/loan/LoanDelinquencyStepDef.java index 786d754341..23e9735751 100644 --- a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/loan/LoanDelinquencyStepDef.java +++ b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/loan/LoanDelinquencyStepDef.java @@ -25,7 +25,6 @@ import static org.assertj.core.api.Assertions.assertThat; import io.cucumber.datatable.DataTable; import io.cucumber.java.en.Then; import io.cucumber.java.en.When; -import java.io.IOException; import java.math.BigDecimal; import java.math.RoundingMode; import java.time.format.DateTimeFormatter; @@ -34,6 +33,7 @@ import java.util.List; import java.util.Map; import java.util.Objects; import java.util.stream.Collectors; +import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.apache.fineract.avro.loan.v1.LoanAccountDelinquencyRangeDataV1; import org.apache.fineract.avro.loan.v1.LoanInstallmentDelinquencyBucketDataV1; @@ -59,27 +59,19 @@ import org.apache.fineract.test.messaging.event.EventCheckHelper; import org.apache.fineract.test.messaging.event.loan.delinquency.LoanDelinquencyRangeChangeEvent; import org.apache.fineract.test.stepdef.AbstractStepDef; import org.apache.fineract.test.support.TestContextKey; -import org.springframework.beans.factory.annotation.Autowired; @Slf4j +@RequiredArgsConstructor public class LoanDelinquencyStepDef extends AbstractStepDef { public static final String DATE_FORMAT = "dd MMMM yyyy"; public static final String DEFAULT_LOCALE = "en"; public static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern(DATE_FORMAT); - private static final String PWD_USER_WITH_ROLE = "1234567890Aa!"; - @Autowired - private FineractFeignClient fineractClient; - - @Autowired - private ApiProperties apiProperties; - - @Autowired - private EventAssertion eventAssertion; - - @Autowired - private EventCheckHelper eventCheckHelper; + private final FineractFeignClient fineractClient; + private final ApiProperties apiProperties; + private final EventAssertion eventAssertion; + private final EventCheckHelper eventCheckHelper; private FineractFeignClient createClientForUser(String username, String password) { String baseUrl = apiProperties.getBaseUrl(); @@ -93,9 +85,9 @@ public class LoanDelinquencyStepDef extends AbstractStepDef { } @Then("Admin checks that delinquency range is: {string} and has delinquentDate {string}") - public void checkDelinquencyRange(String range, String delinquentDateExpected) throws IOException { + public void checkDelinquencyRange(String range, String delinquentDateExpected) { PostLoansResponse loanResponse = testContext().get(TestContextKey.LOAN_CREATE_RESPONSE); - long loanId = loanResponse.getLoanId(); + Long loanId = loanResponse.getLoanId(); GetLoansLoanIdResponse loanDetails = ok(() -> fineractClient.loans().retrieveLoan(loanId, Map.of("associations", "collection"))); Integer loanStatus = loanDetails.getStatus().getId(); @@ -122,11 +114,10 @@ public class LoanDelinquencyStepDef extends AbstractStepDef { } @Then("Admin checks that {string}th delinquency range is: {string} and added on: {string} and has delinquentDate {string}") - public void checkDelinquencyRange(String nthInList, String range, String addedOnDate, String delinquentDateExpected) - throws IOException { + public void checkDelinquencyRange(String nthInList, String range, String addedOnDate, String delinquentDateExpected) { DateTimeFormatter formatter = DateTimeFormatter.ofPattern(DATE_FORMAT); PostLoansResponse loanResponse = testContext().get(TestContextKey.LOAN_CREATE_RESPONSE); - long loanId = loanResponse.getLoanId(); + Long loanId = loanResponse.getLoanId(); String delinquentDateExpectedValue = "".equals(delinquentDateExpected) ? null : delinquentDateExpected; eventAssertion.assertEvent(LoanDelinquencyRangeChangeEvent.class, loanId) @@ -155,12 +146,12 @@ public class LoanDelinquencyStepDef extends AbstractStepDef { } @Then("Loan delinquency history has the following details:") - public void delinquencyHistoryCheck(DataTable table) throws IOException { + public void delinquencyHistoryCheck(DataTable table) { DateTimeFormatter formatter = DateTimeFormatter.ofPattern(DATE_FORMAT); List<List<String>> dataExpected = table.asLists(); PostLoansResponse loanResponse = testContext().get(TestContextKey.LOAN_CREATE_RESPONSE); - long loanId = loanResponse.getLoanId(); + Long loanId = loanResponse.getLoanId(); List<GetDelinquencyTagHistoryResponse> body = ok(() -> fineractClient.loans().getDelinquencyTagHistory(loanId)); @@ -188,9 +179,9 @@ public class LoanDelinquencyStepDef extends AbstractStepDef { } @When("Admin initiate a DELINQUENCY PAUSE with startDate: {string} and endDate: {string}") - public void delinquencyPause(String startDate, String endDate) throws IOException { + public void delinquencyPause(String startDate, String endDate) { PostLoansResponse loanResponse = testContext().get(TestContextKey.LOAN_CREATE_RESPONSE); - long loanId = loanResponse.getLoanId(); + Long loanId = loanResponse.getLoanId(); PostLoansDelinquencyActionRequest request = new PostLoansDelinquencyActionRequest()// .action("pause")// @@ -205,9 +196,9 @@ public class LoanDelinquencyStepDef extends AbstractStepDef { } @When("Created user with CREATE_DELINQUENCY_ACTION permission initiate a DELINQUENCY PAUSE with startDate: {string} and endDate: {string}") - public void delinquencyPauseWithCreatedUser(String startDate, String endDate) throws IOException { + public void delinquencyPauseWithCreatedUser(String startDate, String endDate) { PostLoansResponse loanResponse = testContext().get(TestContextKey.LOAN_CREATE_RESPONSE); - long loanId = loanResponse.getLoanId(); + Long loanId = loanResponse.getLoanId(); PostLoansDelinquencyActionRequest request = new PostLoansDelinquencyActionRequest()// .action("pause")// @@ -226,9 +217,9 @@ public class LoanDelinquencyStepDef extends AbstractStepDef { } @Then("Created user with no CREATE_DELINQUENCY_ACTION permission gets an error when initiate a DELINQUENCY PAUSE with startDate: {string} and endDate: {string}") - public void delinquencyPauseWithCreatedUserNOPermissionError(String startDate, String endDate) throws IOException { + public void delinquencyPauseWithCreatedUserNOPermissionError(String startDate, String endDate) { PostLoansResponse loanResponse = testContext().get(TestContextKey.LOAN_CREATE_RESPONSE); - long loanId = loanResponse.getLoanId(); + Long loanId = loanResponse.getLoanId(); int errorCodeExpected = 403; String errorMessageExpected = "User has no authority to CREATE delinquency_actions"; @@ -257,9 +248,9 @@ public class LoanDelinquencyStepDef extends AbstractStepDef { } @When("Admin initiate a DELINQUENCY RESUME with startDate: {string}") - public void delinquencyResume(String startDate) throws IOException { + public void delinquencyResume(String startDate) { PostLoansResponse loanResponse = testContext().get(TestContextKey.LOAN_CREATE_RESPONSE); - long loanId = loanResponse.getLoanId(); + Long loanId = loanResponse.getLoanId(); PostLoansDelinquencyActionRequest request = new PostLoansDelinquencyActionRequest()// .action("resume")// @@ -273,10 +264,10 @@ public class LoanDelinquencyStepDef extends AbstractStepDef { } @When("Admin initiate a DELINQUENCY PAUSE by loanExternalId with startDate: {string} and endDate: {string}") - public void delinquencyPauseByLoanExternalId(String startDate, String endDate) throws IOException { + public void delinquencyPauseByLoanExternalId(String startDate, String endDate) { PostLoansResponse loanResponse = testContext().get(TestContextKey.LOAN_CREATE_RESPONSE); String loanExternalId = loanResponse.getResourceExternalId(); - long loanId = loanResponse.getLoanId(); + Long loanId = loanResponse.getLoanId(); PostLoansDelinquencyActionRequest request = new PostLoansDelinquencyActionRequest()// .action("pause")// @@ -292,10 +283,10 @@ public class LoanDelinquencyStepDef extends AbstractStepDef { } @When("Admin initiate a DELINQUENCY RESUME by loanExternalId with startDate: {string}") - public void delinquencyResumeByLoanExternalId(String startDate) throws IOException { + public void delinquencyResumeByLoanExternalId(String startDate) { PostLoansResponse loanResponse = testContext().get(TestContextKey.LOAN_CREATE_RESPONSE); String loanExternalId = loanResponse.getResourceExternalId(); - long loanId = loanResponse.getLoanId(); + Long loanId = loanResponse.getLoanId(); PostLoansDelinquencyActionRequest request = new PostLoansDelinquencyActionRequest()// .action("resume")// @@ -310,9 +301,9 @@ public class LoanDelinquencyStepDef extends AbstractStepDef { } @Then("Delinquency-actions have the following data:") - public void getDelinquencyActionData(DataTable table) throws IOException { + public void getDelinquencyActionData(DataTable table) { PostLoansResponse loanResponse = testContext().get(TestContextKey.LOAN_CREATE_RESPONSE); - long loanId = loanResponse.getLoanId(); + Long loanId = loanResponse.getLoanId(); List<List<String>> data = table.asLists(); int nrOfLinesExpected = data.size() - 1; @@ -341,9 +332,9 @@ public class LoanDelinquencyStepDef extends AbstractStepDef { } @Then("Initiating a delinquency-action other than PAUSE or RESUME in action field results an error - startDate: {string}, endDate: {string}") - public void actionFieldError(String startDate, String endDate) throws IOException { + public void actionFieldError(String startDate, String endDate) { PostLoansResponse loanResponse = testContext().get(TestContextKey.LOAN_CREATE_RESPONSE); - long loanId = loanResponse.getLoanId(); + Long loanId = loanResponse.getLoanId(); PostLoansDelinquencyActionRequest request = new PostLoansDelinquencyActionRequest()// .action("TEST")// @@ -358,9 +349,9 @@ public class LoanDelinquencyStepDef extends AbstractStepDef { } @Then("Initiating a DELINQUENCY PAUSE with startDate before the actual business date results an error - startDate: {string}, endDate: {string}") - public void delinquencyPauseStartDateError(String startDate, String endDate) throws IOException { + public void delinquencyPauseStartDateError(String startDate, String endDate) { PostLoansResponse loanResponse = testContext().get(TestContextKey.LOAN_CREATE_RESPONSE); - long loanId = loanResponse.getLoanId(); + Long loanId = loanResponse.getLoanId(); PostLoansDelinquencyActionRequest request = new PostLoansDelinquencyActionRequest()// .action("pause")// @@ -375,9 +366,9 @@ public class LoanDelinquencyStepDef extends AbstractStepDef { } @Then("Initiating a DELINQUENCY PAUSE on a non-active loan results an error - startDate: {string}, endDate: {string}") - public void delinquencyPauseNonActiveLoanError(String startDate, String endDate) throws IOException { + public void delinquencyPauseNonActiveLoanError(String startDate, String endDate) { PostLoansResponse loanResponse = testContext().get(TestContextKey.LOAN_CREATE_RESPONSE); - long loanId = loanResponse.getLoanId(); + Long loanId = loanResponse.getLoanId(); PostLoansDelinquencyActionRequest request = new PostLoansDelinquencyActionRequest()// .action("pause")// @@ -392,9 +383,9 @@ public class LoanDelinquencyStepDef extends AbstractStepDef { } @Then("Initiating a DELINQUENCY RESUME on a non-active loan results an error - startDate: {string}") - public void delinquencyResumeNonActiveLoanError(String startDate) throws IOException { + public void delinquencyResumeNonActiveLoanError(String startDate) { PostLoansResponse loanResponse = testContext().get(TestContextKey.LOAN_CREATE_RESPONSE); - long loanId = loanResponse.getLoanId(); + Long loanId = loanResponse.getLoanId(); PostLoansDelinquencyActionRequest request = new PostLoansDelinquencyActionRequest()// .action("resume")// @@ -408,9 +399,9 @@ public class LoanDelinquencyStepDef extends AbstractStepDef { } @Then("Overlapping PAUSE periods result an error - startDate: {string}, endDate: {string}") - public void delinquencyPauseOverlappingError(String startDate, String endDate) throws IOException { + public void delinquencyPauseOverlappingError(String startDate, String endDate) { PostLoansResponse loanResponse = testContext().get(TestContextKey.LOAN_CREATE_RESPONSE); - long loanId = loanResponse.getLoanId(); + Long loanId = loanResponse.getLoanId(); PostLoansDelinquencyActionRequest request = new PostLoansDelinquencyActionRequest()// .action("pause")// @@ -425,9 +416,9 @@ public class LoanDelinquencyStepDef extends AbstractStepDef { } @Then("Initiating a DELINQUENCY RESUME without an active PAUSE period results an error - startDate: {string}") - public void delinquencyResumeWithoutPauseError(String startDate) throws IOException { + public void delinquencyResumeWithoutPauseError(String startDate) { PostLoansResponse loanResponse = testContext().get(TestContextKey.LOAN_CREATE_RESPONSE); - long loanId = loanResponse.getLoanId(); + Long loanId = loanResponse.getLoanId(); PostLoansDelinquencyActionRequest request = new PostLoansDelinquencyActionRequest()// .action("resume")// @@ -441,9 +432,9 @@ public class LoanDelinquencyStepDef extends AbstractStepDef { } @Then("Initiating a DELINQUENCY RESUME with start date other than actual business date results an error - startDate: {string}") - public void delinquencyResumeStartDateError(String startDate) throws IOException { + public void delinquencyResumeStartDateError(String startDate) { PostLoansResponse loanResponse = testContext().get(TestContextKey.LOAN_CREATE_RESPONSE); - long loanId = loanResponse.getLoanId(); + Long loanId = loanResponse.getLoanId(); PostLoansDelinquencyActionRequest request = new PostLoansDelinquencyActionRequest()// .action("resume")// @@ -457,9 +448,9 @@ public class LoanDelinquencyStepDef extends AbstractStepDef { } @Then("Initiating a DELINQUENCY RESUME with an endDate results an error - startDate: {string}, endDate: {string}") - public void delinquencyResumeWithEndDateError(String startDate, String endDate) throws IOException { + public void delinquencyResumeWithEndDateError(String startDate, String endDate) { PostLoansResponse loanResponse = testContext().get(TestContextKey.LOAN_CREATE_RESPONSE); - long loanId = loanResponse.getLoanId(); + Long loanId = loanResponse.getLoanId(); PostLoansDelinquencyActionRequest request = new PostLoansDelinquencyActionRequest()// .action("resume")// @@ -474,17 +465,17 @@ public class LoanDelinquencyStepDef extends AbstractStepDef { } @Then("Installment level delinquency event has correct data") - public void installmentLevelDelinquencyEventCheck() throws IOException { + public void installmentLevelDelinquencyEventCheck() { PostLoansResponse loanResponse = testContext().get(TestContextKey.LOAN_CREATE_RESPONSE); - long loanId = loanResponse.getLoanId(); + Long loanId = loanResponse.getLoanId(); eventCheckHelper.installmentLevelDelinquencyRangeChangeEventCheck(loanId); } @Then("INSTALLMENT level delinquency is null") - public void installmentLevelDelinquencyNull() throws IOException { + public void installmentLevelDelinquencyNull() { PostLoansResponse loanResponse = testContext().get(TestContextKey.LOAN_CREATE_RESPONSE); - long loanId = loanResponse.getLoanId(); + Long loanId = loanResponse.getLoanId(); GetLoansLoanIdResponse loanDetails = ok(() -> fineractClient.loans().retrieveLoan(loanId, Map.of("associations", "collection"))); List<GetLoansLoanIdLoanInstallmentLevelDelinquency> installmentLevelDelinquency = loanDetails.getDelinquent() @@ -493,12 +484,12 @@ public class LoanDelinquencyStepDef extends AbstractStepDef { } @Then("Loan has the following LOAN level delinquency data:") - public void loanDelinquencyDataCheck(DataTable table) throws IOException { + public void loanDelinquencyDataCheck(DataTable table) { PostLoansResponse loanResponse = testContext().get(TestContextKey.LOAN_CREATE_RESPONSE); - long loanId = loanResponse.getLoanId(); + Long loanId = loanResponse.getLoanId(); List<String> expectedValuesList = table.asLists().get(1); - DelinquencyRange expectedDelinquencyRange = DelinquencyRange.valueOf(expectedValuesList.get(0)); + DelinquencyRange expectedDelinquencyRange = DelinquencyRange.valueOf(expectedValuesList.getFirst()); String expectedDelinquencyRangeValue = expectedDelinquencyRange.getValue(); expectedValuesList.set(0, expectedDelinquencyRangeValue); @@ -506,23 +497,37 @@ public class LoanDelinquencyStepDef extends AbstractStepDef { String actualDelinquencyRangeValue = loanDetails.getDelinquencyRange() == null ? "NO_DELINQUENCY" : loanDetails.getDelinquencyRange().getClassification(); GetLoansLoanIdDelinquencySummary delinquent = loanDetails.getDelinquent(); + assertThat(delinquent).isNotNull(); String delinquentAmount = delinquent.getDelinquentAmount() == null ? null : new Utils.DoubleFormatter(delinquent.getDelinquentAmount().doubleValue()).format(); - List<String> actualValuesList = List.of(actualDelinquencyRangeValue, delinquentAmount, - delinquent.getDelinquentDate() == null ? "null" : FORMATTER.format(delinquent.getDelinquentDate()), - delinquent.getDelinquentDays().toString(), delinquent.getPastDueDays().toString()); + + assertThat(actualDelinquencyRangeValue).isNotNull(); + assertThat(delinquentAmount).isNotNull(); + assertThat(delinquent.getDelinquentDays()).isNotNull(); + assertThat(delinquent.getPastDueDays()).isNotNull(); + List<String> actualValuesList; + if (expectedValuesList.size() == 6) { + actualValuesList = List.of(actualDelinquencyRangeValue, delinquentAmount, + delinquent.getDelinquentDate() == null ? "null" : FORMATTER.format(delinquent.getDelinquentDate()), + delinquent.getPastDueDate() == null ? "null" : FORMATTER.format(delinquent.getPastDueDate()), + delinquent.getDelinquentDays().toString(), delinquent.getPastDueDays().toString()); + } else { + actualValuesList = List.of(actualDelinquencyRangeValue, delinquentAmount, + delinquent.getDelinquentDate() == null ? "null" : FORMATTER.format(delinquent.getDelinquentDate()), + delinquent.getDelinquentDays().toString(), delinquent.getPastDueDays().toString()); + } assertThat(actualValuesList).as(ErrorMessageHelper.wrongValueInLoanLevelDelinquencyData(actualValuesList, expectedValuesList)) .isEqualTo(expectedValuesList); } @Then("Loan has the following LOAN level next payment due data:") - public void loanNextPaymentDataCheck(DataTable table) throws IOException { + public void loanNextPaymentDataCheck(DataTable table) { PostLoansResponse loanResponse = testContext().get(TestContextKey.LOAN_CREATE_RESPONSE); - long loanId = loanResponse.getLoanId(); + Long loanId = loanResponse.getLoanId(); List<String> expectedValuesList = table.asLists().get(1); - DelinquencyRange expectedDelinquencyRange = DelinquencyRange.valueOf(expectedValuesList.get(0)); + DelinquencyRange expectedDelinquencyRange = DelinquencyRange.valueOf(expectedValuesList.getFirst()); String expectedDelinquencyRangeValue = expectedDelinquencyRange.getValue(); expectedValuesList.set(0, expectedDelinquencyRangeValue); @@ -543,9 +548,9 @@ public class LoanDelinquencyStepDef extends AbstractStepDef { } @Then("Loan has the following INSTALLMENT level delinquency data:") - public void loanDelinquencyInstallmentLevelDataCheck(DataTable table) throws IOException { + public void loanDelinquencyInstallmentLevelDataCheck(DataTable table) { PostLoansResponse loanResponse = testContext().get(TestContextKey.LOAN_CREATE_RESPONSE); - long loanId = loanResponse.getLoanId(); + Long loanId = loanResponse.getLoanId(); GetLoansLoanIdResponse loanDetails = ok(() -> fineractClient.loans().retrieveLoan(loanId, Map.of("associations", "collection"))); List<GetLoansLoanIdLoanInstallmentLevelDelinquency> installmentLevelDelinquency = loanDetails.getDelinquent() @@ -572,9 +577,9 @@ public class LoanDelinquencyStepDef extends AbstractStepDef { } @Then("Loan Delinquency pause periods has the following data:") - public void loanDelinquencyPauseDataCheck(DataTable table) throws IOException { + public void loanDelinquencyPauseDataCheck(DataTable table) { PostLoansResponse loanResponse = testContext().get(TestContextKey.LOAN_CREATE_RESPONSE); - long loanId = loanResponse.getLoanId(); + Long loanId = loanResponse.getLoanId(); List<List<String>> expectedData = table.asLists(); GetLoansLoanIdResponse loanDetails = ok(() -> fineractClient.loans().retrieveLoan(loanId, Map.of("associations", "collection"))); @@ -598,9 +603,9 @@ public class LoanDelinquencyStepDef extends AbstractStepDef { } @Then("Loan details delinquent.nextPaymentDueDate will be {string}") - public void nextPaymentDueDateCheck(String expectedDate) throws IOException { + public void nextPaymentDueDateCheck(String expectedDate) { PostLoansResponse loanResponse = testContext().get(TestContextKey.LOAN_CREATE_RESPONSE); - long loanId = loanResponse.getLoanId(); + Long loanId = loanResponse.getLoanId(); GetLoansLoanIdResponse loanDetails = ok(() -> fineractClient.loans().retrieveLoan(loanId, Map.of("associations", "collection"))); String actualDate = FORMATTER.format(loanDetails.getDelinquent().getNextPaymentDueDate()); @@ -611,7 +616,7 @@ public class LoanDelinquencyStepDef extends AbstractStepDef { @Then("LoanAccountDelinquencyRangeDataV1 has delinquencyRange field with value {string}") public void checkDelinquencyRangeInEvent(String expectedRange) { PostLoansResponse loanResponse = testContext().get(TestContextKey.LOAN_CREATE_RESPONSE); - long loanId = loanResponse.getLoanId(); + Long loanId = loanResponse.getLoanId(); DelinquencyRange expectedDelinquencyRange = DelinquencyRange.valueOf(expectedRange); String expectedDelinquencyRangeValue = expectedDelinquencyRange.getValue(); @@ -627,9 +632,9 @@ public class LoanDelinquencyStepDef extends AbstractStepDef { } @Then("LoanDelinquencyRangeChangeBusinessEvent has the same Delinquency range, date and amount as in LoanDetails on both loan- and installment-level") - public void checkDelinquencyRangeInEvent() throws IOException { + public void checkDelinquencyRangeInEvent() { PostLoansResponse loanResponse = testContext().get(TestContextKey.LOAN_CREATE_RESPONSE); - long loanId = loanResponse.getLoanId(); + Long loanId = loanResponse.getLoanId(); GetLoansLoanIdResponse loanDetails = ok(() -> fineractClient.loans().retrieveLoan(loanId, Map.of("associations", "collection"))); DelinquencyRangeData delinquencyRange = loanDetails.getDelinquencyRange(); @@ -699,18 +704,17 @@ public class LoanDelinquencyStepDef extends AbstractStepDef { } @Then("In Loan details delinquent.lastRepaymentAmount is {int} EUR with lastRepaymentDate {string}") - public void delinquentLastRepaymentAmountCheck(int expectedLastRepaymentAmount, String expectedLastRepaymentDate) throws IOException { + public void delinquentLastRepaymentAmountCheck(int expectedLastRepaymentAmount, String expectedLastRepaymentDate) { PostLoansResponse loanResponse = testContext().get(TestContextKey.LOAN_CREATE_RESPONSE); - long loanId = loanResponse.getLoanId(); + Long loanId = loanResponse.getLoanId(); GetLoansLoanIdResponse loanDetails = ok(() -> fineractClient.loans().retrieveLoan(loanId, Map.of("associations", "collection"))); - Double expectedLastRepaymentAmount1 = Double.valueOf(expectedLastRepaymentAmount); Double actualLastRepaymentAmount = loanDetails.getDelinquent().getLastRepaymentAmount().doubleValue(); String actualLastRepaymentDate = FORMATTER.format(loanDetails.getDelinquent().getLastRepaymentDate()); assertThat(actualLastRepaymentAmount)// - .as(ErrorMessageHelper.wrongDataInDelinquentLastRepaymentAmount(actualLastRepaymentAmount, expectedLastRepaymentAmount1))// + .as(ErrorMessageHelper.wrongDataInDelinquentLastRepaymentAmount(actualLastRepaymentAmount, actualLastRepaymentAmount))// .isEqualTo(expectedLastRepaymentAmount);// assertThat(actualLastRepaymentDate)// .as(ErrorMessageHelper.wrongDataInDelinquentLastRepaymentDate(actualLastRepaymentDate, expectedLastRepaymentDate))// @@ -751,7 +755,7 @@ public class LoanDelinquencyStepDef extends AbstractStepDef { @Then("LoanDelinquencyRangeChangeBusinessEvent is created") public void checkLoanDelinquencyRangeChangeBusinessEventCreated() { PostLoansResponse loanResponse = testContext().get(TestContextKey.LOAN_CREATE_RESPONSE); - long loanId = loanResponse.getLoanId(); + Long loanId = loanResponse.getLoanId(); eventAssertion.assertEventRaised(LoanDelinquencyRangeChangeEvent.class, loanId); } diff --git a/fineract-e2e-tests-runner/src/test/resources/features/LoanDelinquency.feature b/fineract-e2e-tests-runner/src/test/resources/features/LoanDelinquency.feature index b3444a6d68..e0479d43fd 100644 --- a/fineract-e2e-tests-runner/src/test/resources/features/LoanDelinquency.feature +++ b/fineract-e2e-tests-runner/src/test/resources/features/LoanDelinquency.feature @@ -2176,3 +2176,48 @@ Feature: LoanDelinquency And Loan has the following LOAN level delinquency data: | classification | delinquentAmount | delinquentDate | delinquentDays | pastDueDays | | RANGE_90 | 666.68 | 06 February 2025 | 98 | 103 | + + Scenario: Verify that pastDueDate is returned correctly for overdue loan + When Admin sets the business date to "01 October 2023" + When Admin creates a client with random data + When Admin creates a fully customized loan with 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 | + | LP2_DOWNPAYMENT_ADV_PMT_ALLOC_PROGRESSIVE_LOAN_SCHEDULE_HORIZONTAL_INSTALLMENT_LEVEL_DELINQUENCY | 01 October 2023 | 1000 | 0 | FLAT | SAME_AS_REPAYMENT_PERIOD | EQUAL_INSTALLMENTS | 45 | DAYS | 15 | DAYS | 3 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | + And Admin successfully approves the loan on "01 October 2023" with "1000" amount and expected disbursement date on "01 October 2023" + When Admin successfully disburse the loan on "01 October 2023" with "1000" EUR transaction amount + When Admin sets the business date to "10 October 2023" + When Admin runs inline COB job for Loan + Then Loan has the following LOAN level delinquency data: + | classification | delinquentAmount | delinquentDate | pastDueDate | delinquentDays | pastDueDays | + | RANGE_3 | 250.0 | 04 October 2023 | 01 October 2023 | 6 | 9 | + + Scenario: Verify that pastDueDate is null when loan has no overdue + When Admin sets the business date to "01 October 2023" + When Admin creates a client with random data + When Admin creates a fully customized loan with 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 | + | LP2_DOWNPAYMENT_ADV_PMT_ALLOC_PROGRESSIVE_LOAN_SCHEDULE_HORIZONTAL_INSTALLMENT_LEVEL_DELINQUENCY | 01 October 2023 | 1000 | 0 | FLAT | SAME_AS_REPAYMENT_PERIOD | EQUAL_INSTALLMENTS | 45 | DAYS | 15 | DAYS | 3 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | + And Admin successfully approves the loan on "01 October 2023" with "1000" amount and expected disbursement date on "01 October 2023" + When Admin successfully disburse the loan on "01 October 2023" with "1000" EUR transaction amount + And Customer makes "AUTOPAY" repayment on "01 October 2023" with 250 EUR transaction amount + When Admin runs inline COB job for Loan + Then Loan has the following LOAN level delinquency data: + | classification | delinquentAmount | delinquentDate | pastDueDate | delinquentDays | pastDueDays | + | NO_DELINQUENCY | 0.0 | null | null | 0 | 0 | + + Scenario: Verify that pastDueDate equals chargeback date when chargeback creates overdue + When Admin sets the business date to "01 October 2023" + When Admin creates a client with random data + When Admin creates a fully customized loan with 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 | + | LP2_DOWNPAYMENT_ADV_PMT_ALLOC_PROGRESSIVE_LOAN_SCHEDULE_HORIZONTAL_INSTALLMENT_LEVEL_DELINQUENCY | 01 October 2023 | 1000 | 0 | FLAT | SAME_AS_REPAYMENT_PERIOD | EQUAL_INSTALLMENTS | 45 | DAYS | 15 | DAYS | 3 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | + And Admin successfully approves the loan on "01 October 2023" with "1000" amount and expected disbursement date on "01 October 2023" + When Admin successfully disburse the loan on "01 October 2023" with "1000" EUR transaction amount + And Customer makes "AUTOPAY" repayment on "01 October 2023" with 250 EUR transaction amount + When Admin sets the business date to "05 October 2023" + When Admin makes "REPAYMENT_ADJUSTMENT_CHARGEBACK" chargeback with 250 EUR transaction amount + When Admin sets the business date to "10 October 2023" + When Admin runs inline COB job for Loan + Then Loan has the following LOAN level delinquency data: + | classification | delinquentAmount | delinquentDate | pastDueDate | delinquentDays | pastDueDays | + | RANGE_1 | 250.0 | 08 October 2023 | 05 October 2023 | 2 | 5 | diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/delinquency/service/LoanDelinquencyDomainServiceImpl.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/delinquency/service/LoanDelinquencyDomainServiceImpl.java index e4fc1eb187..53d3f244d7 100644 --- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/delinquency/service/LoanDelinquencyDomainServiceImpl.java +++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/delinquency/service/LoanDelinquencyDomainServiceImpl.java @@ -126,6 +126,7 @@ public class LoanDelinquencyDomainServiceImpl implements LoanDelinquencyDomainSe overdueDays = 0L; } collectionData.setPastDueDays(overdueDays); + collectionData.setPastDueDate(overdueSinceDate); LocalDate delinquentStartDate = overdueSinceDate.plusDays(graceDays.longValue()); collectionData.setDelinquentDate(delinquentStartDate); } @@ -205,6 +206,7 @@ public class LoanDelinquencyDomainServiceImpl implements LoanDelinquencyDomainSe overdueDays = 0L; } collectionData.setPastDueDays(overdueDays); + collectionData.setPastDueDate(overdueSinceDate); LocalDate delinquentStartDate = overdueSinceDate.plusDays(graceDays.longValue()); collectionData.setDelinquentDate(delinquentStartDate); } @@ -244,6 +246,7 @@ public class LoanDelinquencyDomainServiceImpl implements LoanDelinquencyDomainSe overdueDays = 0L; } collectionData.setPastDueDays(overdueDays); + collectionData.setPastDueDate(overdueSinceDate); collectionData.setDelinquentDate(overdueSinceDate); } collectionData.setDelinquentAmount(outstandingAmount); diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/data/CollectionData.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/data/CollectionData.java index 4d22a3f275..8317db7b08 100644 --- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/data/CollectionData.java +++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/data/CollectionData.java @@ -31,6 +31,7 @@ public final class CollectionData { private BigDecimal availableDisbursementAmount; private BigDecimal availableDisbursementAmountWithOverApplied; private Long pastDueDays; + private LocalDate pastDueDate; private LocalDate nextPaymentDueDate; private BigDecimal nextPaymentAmount; private Long delinquentDays; @@ -52,7 +53,8 @@ public final class CollectionData { public static CollectionData template() { final BigDecimal zero = BigDecimal.ZERO; - return new CollectionData(zero, zero, 0L, null, zero, 0L, null, zero, null, zero, null, zero, null, null, zero, zero, zero, zero); + return new CollectionData(zero, zero, 0L, null, null, zero, 0L, null, zero, null, zero, null, zero, null, null, zero, zero, zero, + zero); } } 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 a2349178a5..45bb2367fb 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 @@ -1027,6 +1027,8 @@ final class LoansApiResourceSwagger { @Schema(example = "12") public Integer pastDueDays; @Schema(example = "[2022, 07, 01]") + public LocalDate pastDueDate; + @Schema(example = "[2022, 07, 01]") public LocalDate nextPaymentDueDate; @Schema(example = "123.23") public BigDecimal nextPaymentAmount; diff --git a/fineract-provider/src/test/java/org/apache/fineract/portfolio/deliquency/DelinquencyWritePlatformServiceRangeChangeEventTest.java b/fineract-provider/src/test/java/org/apache/fineract/portfolio/deliquency/DelinquencyWritePlatformServiceRangeChangeEventTest.java index 5e05029bcb..d692d1d967 100644 --- a/fineract-provider/src/test/java/org/apache/fineract/portfolio/deliquency/DelinquencyWritePlatformServiceRangeChangeEventTest.java +++ b/fineract-provider/src/test/java/org/apache/fineract/portfolio/deliquency/DelinquencyWritePlatformServiceRangeChangeEventTest.java @@ -186,8 +186,8 @@ public class DelinquencyWritePlatformServiceRangeChangeEventTest { LoanScheduleDelinquencyData loanScheduleDelinquencyData = new LoanScheduleDelinquencyData(1L, overDueSinceDate, 1L, loanForProcessing); final BigDecimal zero = BigDecimal.ZERO; - CollectionData collectionData = new CollectionData(zero, zero, 2L, null, zero, 2L, overDueSinceDate, zero, null, null, null, null, - null, null, zero, zero, zero, zero); + CollectionData collectionData = new CollectionData(zero, zero, 2L, overDueSinceDate, null, zero, 2L, overDueSinceDate, zero, null, + null, null, null, null, null, zero, zero, zero, zero); Map<Long, CollectionData> installmentsCollection = new HashMap<>(); @@ -240,11 +240,12 @@ public class DelinquencyWritePlatformServiceRangeChangeEventTest { LocalDate overDueSinceDate = DateUtils.getBusinessLocalDate().minusDays(2); LoanScheduleDelinquencyData loanScheduleDelinquencyData = new LoanScheduleDelinquencyData(1L, overDueSinceDate, 1L, loanForProcessing); - CollectionData collectionData = new CollectionData(zeroAmount, zeroAmount, 2L, null, zeroAmount, 2L, overDueSinceDate, zeroAmount, - null, null, null, null, null, null, zeroAmount, zeroAmount, zeroAmount, zeroAmount); + CollectionData collectionData = new CollectionData(zeroAmount, zeroAmount, 2L, overDueSinceDate, null, zeroAmount, 2L, + overDueSinceDate, zeroAmount, null, null, null, null, null, null, zeroAmount, zeroAmount, zeroAmount, zeroAmount); - CollectionData installmentCollectionData = new CollectionData(zeroAmount, zeroAmount, 2L, null, zeroAmount, 2L, overDueSinceDate, - installmentPrincipalAmount, null, null, null, null, null, null, zeroAmount, zeroAmount, zeroAmount, zeroAmount); + CollectionData installmentCollectionData = new CollectionData(zeroAmount, zeroAmount, 2L, overDueSinceDate, null, zeroAmount, 2L, + overDueSinceDate, installmentPrincipalAmount, null, null, null, null, null, null, zeroAmount, zeroAmount, zeroAmount, + zeroAmount); Map<Long, CollectionData> installmentsCollection = new HashMap<>(); installmentsCollection.put(1L, installmentCollectionData); @@ -369,11 +370,12 @@ public class DelinquencyWritePlatformServiceRangeChangeEventTest { LoanScheduleDelinquencyData loanScheduleDelinquencyData = new LoanScheduleDelinquencyData(1L, overDueSinceDate, 1L, loanForProcessing); - CollectionData collectionData = new CollectionData(zeroAmount, zeroAmount, 2L, null, zeroAmount, 2L, overDueSinceDate, zeroAmount, - null, null, null, null, null, null, zeroAmount, zeroAmount, zeroAmount, zeroAmount); + CollectionData collectionData = new CollectionData(zeroAmount, zeroAmount, 2L, overDueSinceDate, null, zeroAmount, 2L, + overDueSinceDate, zeroAmount, null, null, null, null, null, null, zeroAmount, zeroAmount, zeroAmount, zeroAmount); - CollectionData installmentCollectionData = new CollectionData(zeroAmount, zeroAmount, 2L, null, zeroAmount, 2L, overDueSinceDate, - installmentPrincipalAmount, null, null, null, null, null, null, zeroAmount, zeroAmount, zeroAmount, zeroAmount); + CollectionData installmentCollectionData = new CollectionData(zeroAmount, zeroAmount, 2L, overDueSinceDate, null, zeroAmount, 2L, + overDueSinceDate, installmentPrincipalAmount, null, null, null, null, null, null, zeroAmount, zeroAmount, zeroAmount, + zeroAmount); Map<Long, CollectionData> installmentsCollection = new HashMap<>(); installmentsCollection.put(1L, installmentCollectionData); @@ -446,11 +448,12 @@ public class DelinquencyWritePlatformServiceRangeChangeEventTest { LocalDate overDueSinceDate = DateUtils.getBusinessLocalDate().minusDays(29); LoanScheduleDelinquencyData loanScheduleDelinquencyData = new LoanScheduleDelinquencyData(1L, overDueSinceDate, 1L, loanForProcessing); - CollectionData collectionData = new CollectionData(zeroAmount, zeroAmount, 29L, null, zeroAmount, 29L, overDueSinceDate, zeroAmount, - null, null, null, null, null, null, zeroAmount, zeroAmount, zeroAmount, zeroAmount); + CollectionData collectionData = new CollectionData(zeroAmount, zeroAmount, 29L, overDueSinceDate, null, zeroAmount, 29L, + overDueSinceDate, zeroAmount, null, null, null, null, null, null, zeroAmount, zeroAmount, zeroAmount, zeroAmount); - CollectionData installmentCollectionData = new CollectionData(zeroAmount, zeroAmount, 29L, null, zeroAmount, 29L, overDueSinceDate, - installmentPrincipalAmount, null, null, null, null, null, null, zeroAmount, zeroAmount, zeroAmount, zeroAmount); + CollectionData installmentCollectionData = new CollectionData(zeroAmount, zeroAmount, 29L, overDueSinceDate, null, zeroAmount, 29L, + overDueSinceDate, installmentPrincipalAmount, null, null, null, null, null, null, zeroAmount, zeroAmount, zeroAmount, + zeroAmount); Map<Long, CollectionData> installmentsCollection = new HashMap<>(); installmentsCollection.put(1L, installmentCollectionData); @@ -534,14 +537,14 @@ public class DelinquencyWritePlatformServiceRangeChangeEventTest { LocalDate overDueSinceDate = DateUtils.getBusinessLocalDate().minusDays(29); LoanScheduleDelinquencyData loanScheduleDelinquencyData = new LoanScheduleDelinquencyData(1L, overDueSinceDate, 1L, loanForProcessing); - CollectionData collectionData = new CollectionData(zeroAmount, zeroAmount, 29L, null, zeroAmount, 29L, overDueSinceDate, zeroAmount, - null, null, null, null, null, null, zeroAmount, zeroAmount, zeroAmount, zeroAmount); + CollectionData collectionData = new CollectionData(zeroAmount, zeroAmount, 29L, overDueSinceDate, null, zeroAmount, 29L, + overDueSinceDate, zeroAmount, null, null, null, null, null, null, zeroAmount, zeroAmount, zeroAmount, zeroAmount); - CollectionData installmentCollectionData_1 = new CollectionData(zeroAmount, zeroAmount, 29L, null, zeroAmount, 29L, - overDueSinceDate, installmentPrincipalAmount, null, null, null, null, null, null, zeroAmount, zeroAmount, zeroAmount, + CollectionData installmentCollectionData_1 = new CollectionData(zeroAmount, zeroAmount, 29L, overDueSinceDate, null, zeroAmount, + 29L, overDueSinceDate, installmentPrincipalAmount, null, null, null, null, null, null, zeroAmount, zeroAmount, zeroAmount, zeroAmount); - CollectionData installmentCollectionData_2 = new CollectionData(zeroAmount, zeroAmount, 0L, null, zeroAmount, 0L, null, + CollectionData installmentCollectionData_2 = new CollectionData(zeroAmount, zeroAmount, 0L, null, null, zeroAmount, 0L, null, installmentPrincipalAmount, null, null, null, null, null, null, zeroAmount, zeroAmount, zeroAmount, zeroAmount); Map<Long, CollectionData> installmentsCollection = new HashMap<>(); diff --git a/fineract-provider/src/test/java/org/apache/fineract/portfolio/deliquency/LoanDelinquencyDomainServiceTest.java b/fineract-provider/src/test/java/org/apache/fineract/portfolio/deliquency/LoanDelinquencyDomainServiceTest.java index cd213cbf46..112d9b984f 100644 --- a/fineract-provider/src/test/java/org/apache/fineract/portfolio/deliquency/LoanDelinquencyDomainServiceTest.java +++ b/fineract-provider/src/test/java/org/apache/fineract/portfolio/deliquency/LoanDelinquencyDomainServiceTest.java @@ -139,6 +139,7 @@ public class LoanDelinquencyDomainServiceTest { // then assertEquals(0L, collectionData.getDelinquentDays()); assertEquals(null, collectionData.getDelinquentDate()); + assertEquals(null, collectionData.getPastDueDate()); assertEquals(collectionData.getDelinquentDays(), collectionData.getPastDueDays()); } @@ -170,6 +171,7 @@ public class LoanDelinquencyDomainServiceTest { // then assertEquals(daysDiff, collectionData.getDelinquentDays()); assertEquals(dueDate, collectionData.getDelinquentDate()); + assertEquals(dueDate, collectionData.getPastDueDate()); assertEquals(collectionData.getDelinquentDays(), collectionData.getPastDueDays()); } @@ -206,6 +208,7 @@ public class LoanDelinquencyDomainServiceTest { // then assertEquals(0L, collectionData.getDelinquentDays()); assertEquals(null, collectionData.getDelinquentDate()); + assertEquals(null, collectionData.getPastDueDate()); assertEquals(collectionData.getDelinquentDays(), collectionData.getPastDueDays()); } @@ -248,10 +251,12 @@ public class LoanDelinquencyDomainServiceTest { assertEquals(daysDiff, loanCollectionData.getDelinquentDays()); assertEquals(dueDate, loanCollectionData.getDelinquentDate()); + assertEquals(dueDate, loanCollectionData.getPastDueDate()); assertEquals(loanCollectionData.getDelinquentDays(), loanCollectionData.getPastDueDays()); assertEquals(daysDiff, installmentCollectionData.getDelinquentDays()); assertEquals(dueDate, installmentCollectionData.getDelinquentDate()); + assertEquals(dueDate, installmentCollectionData.getPastDueDate()); assertEquals(installmentCollectionData.getDelinquentDays(), installmentCollectionData.getPastDueDays()); } @@ -301,11 +306,13 @@ public class LoanDelinquencyDomainServiceTest { assertEquals(daysDiff, loanCollectionData.getDelinquentDays()); assertEquals(transactionDate, loanCollectionData.getDelinquentDate()); + assertEquals(transactionDate, loanCollectionData.getPastDueDate()); assertEquals(loanCollectionData.getDelinquentDays(), loanCollectionData.getPastDueDays()); // then assertEquals(daysDiff, installmentCollectionData.getDelinquentDays()); assertEquals(transactionDate, installmentCollectionData.getDelinquentDate()); + assertEquals(transactionDate, installmentCollectionData.getPastDueDate()); assertEquals(installmentCollectionData.getDelinquentDays(), installmentCollectionData.getPastDueDays()); assertEquals(0, principal.compareTo(installmentCollectionData.getDelinquentAmount())); @@ -353,6 +360,7 @@ public class LoanDelinquencyDomainServiceTest { CollectionData loanCollectionData = collectionData.getLoanCollectionData(); assertEquals(35L, loanCollectionData.getDelinquentDays()); assertEquals(LocalDate.of(2022, 1, 16), loanCollectionData.getDelinquentDate()); + assertEquals(LocalDate.of(2022, 1, 16), loanCollectionData.getPastDueDate()); Map<Long, CollectionData> installments = collectionData.getLoanInstallmentsCollectionData(); assertNotNull(installments); @@ -408,6 +416,7 @@ public class LoanDelinquencyDomainServiceTest { CollectionData loanCollectionData = delinquencyData.getLoanCollectionData(); assertEquals(16L, loanCollectionData.getDelinquentDays()); assertEquals(LocalDate.of(2022, 1, 10), loanCollectionData.getDelinquentDate()); + assertEquals(LocalDate.of(2022, 1, 10), loanCollectionData.getPastDueDate()); Map<Long, CollectionData> installmentData = delinquencyData.getLoanInstallmentsCollectionData(); assertEquals(3, installmentData.size());
