FINERACT-2421: Add pastDueDate to loan summary and event

This commit is contained in:
Oleksii Novikov
2026-02-05 13:45:18 +01:00
committed by Adam Saghy
parent 8d064ddf6c
commit 41d859e68a
8 changed files with 174 additions and 98 deletions
@@ -27,6 +27,14 @@
"int"
]
},
{
"default": null,
"name": "pastDueDate",
"type": [
"null",
"string"
]
},
{
"default": null,
"name": "nextPaymentDueDate",
@@ -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);
}
@@ -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 |
@@ -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);
@@ -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);
}
}
@@ -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;
@@ -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<>();
@@ -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());