From 716385980bb179acb1f63b7297f4be6039244669 Mon Sep 17 00:00:00 2001 From: Ruchi Dhamankar Date: Tue, 12 Dec 2023 21:21:10 +0530 Subject: [PATCH] FINERACT-1992: Backdated delinquency pause --- .../DelinquencyWritePlatformServiceImpl.java | 22 ++- .../DelinquencyActionParseAndValidator.java | 14 +- ...elinquencyActionParseAndValidatorTest.java | 31 +++- .../DelinquencyActionIntegrationTests.java | 140 +++++++++++++++++- 4 files changed, 195 insertions(+), 12 deletions(-) diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/delinquency/service/DelinquencyWritePlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/delinquency/service/DelinquencyWritePlatformServiceImpl.java index c92693d348e..23a0f0cda83 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/delinquency/service/DelinquencyWritePlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/delinquency/service/DelinquencyWritePlatformServiceImpl.java @@ -39,6 +39,7 @@ import org.apache.fineract.portfolio.delinquency.api.DelinquencyApiConstants; import org.apache.fineract.portfolio.delinquency.data.DelinquencyBucketData; import org.apache.fineract.portfolio.delinquency.data.DelinquencyRangeData; +import org.apache.fineract.portfolio.delinquency.domain.DelinquencyAction; import org.apache.fineract.portfolio.delinquency.domain.DelinquencyBucket; import org.apache.fineract.portfolio.delinquency.domain.DelinquencyBucketMappings; import org.apache.fineract.portfolio.delinquency.domain.DelinquencyBucketMappingsRepository; @@ -230,8 +231,12 @@ public CommandProcessingResult createDelinquencyAction(Long loanId, JsonCommand savedDelinquencyList, businessDate); parsedDelinquencyAction.setLoan(loan); - LoanDelinquencyAction saved = loanDelinquencyActionRepository.saveAndFlush(parsedDelinquencyAction); + // if backdated pause recalculate delinquency data + if (DateUtils.isBefore(parsedDelinquencyAction.getStartDate(), businessDate) + && DelinquencyAction.PAUSE.equals(parsedDelinquencyAction.getAction())) { + recalculateLoanDelinquencyData(loan); + } businessEventNotifierService.notifyPostBusinessEvent(new LoanAccountDelinquencyPauseChangedBusinessEvent(loan)); return new CommandProcessingResultBuilder().withCommandId(command.commandId()) // .withEntityId(saved.getId()) // @@ -242,6 +247,21 @@ public CommandProcessingResult createDelinquencyAction(Long loanId, JsonCommand .build(); } + private void recalculateLoanDelinquencyData(Loan loan) { + List savedDelinquencyList = delinquencyReadPlatformService.retrieveLoanDelinquencyActions(loan.getId()); + List effectiveDelinquencyList = delinquencyEffectivePauseHelper + .calculateEffectiveDelinquencyList(savedDelinquencyList); + + CollectionData loanDelinquencyData = loanDelinquencyDomainService.getOverdueCollectionData(loan, effectiveDelinquencyList); + LoanScheduleDelinquencyData loanScheduleDelinquencyData = new LoanScheduleDelinquencyData(loan.getId(), + loanDelinquencyData.getDelinquentDate(), loanDelinquencyData.getDelinquentDays(), loan); + if (loanScheduleDelinquencyData.getOverdueDays() > 0) { + applyDelinquencyTagToLoan(loanScheduleDelinquencyData, effectiveDelinquencyList); + } else { + removeDelinquencyTagToLoan(loan); + } + } + @Override public void removeDelinquencyTagToLoan(final Loan loan) { if (loan.isEnableInstallmentLevelDelinquency()) { diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/delinquency/validator/DelinquencyActionParseAndValidator.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/delinquency/validator/DelinquencyActionParseAndValidator.java index 380785c905c..a9779d9b15b 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/delinquency/validator/DelinquencyActionParseAndValidator.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/delinquency/validator/DelinquencyActionParseAndValidator.java @@ -56,7 +56,8 @@ public LoanDelinquencyAction validateAndParseUpdate(@NotNull final JsonCommand c validateLoanIsActive(loan); if (DelinquencyAction.PAUSE.equals(parsedDelinquencyAction.getAction())) { validateBothStartAndEndDatesAreProvided(parsedDelinquencyAction); - validatePauseStartAndEndDate(parsedDelinquencyAction, businessDate); + validatePauseStartAndEndDate(parsedDelinquencyAction); + validatePauseStartDateNotBeforeDisbursementDate(parsedDelinquencyAction, loan.getDisbursementDate()); validatePauseShallNotOverlap(parsedDelinquencyAction, effectiveDelinquencyList); } else if (DelinquencyAction.RESUME.equals(parsedDelinquencyAction.getAction())) { validateResumeStartDate(parsedDelinquencyAction, businessDate); @@ -117,15 +118,18 @@ private void validateResumeStartDate(LoanDelinquencyAction parsedDelinquencyActi } } - private void validatePauseStartAndEndDate(LoanDelinquencyAction parsedDelinquencyAction, LocalDate businessDate) { + private void validatePauseStartAndEndDate(LoanDelinquencyAction parsedDelinquencyAction) { if (parsedDelinquencyAction.getStartDate().equals(parsedDelinquencyAction.getEndDate())) { raiseValidationError("loan-delinquency-action-invalid-start-date-and-end-date", "Delinquency pause period must be at least one day"); } + } - if (businessDate.isAfter(parsedDelinquencyAction.getStartDate())) { - raiseValidationError("loan-delinquency-action-invalid-start-date", "Start date of pause period must be in the future", - START_DATE); + private void validatePauseStartDateNotBeforeDisbursementDate(LoanDelinquencyAction parsedDelinquencyAction, + LocalDate firstDisbursalDate) { + if (firstDisbursalDate.isAfter(parsedDelinquencyAction.getStartDate())) { + raiseValidationError("loan-delinquency-action-invalid-start-date", + "Start date of pause period must be after first disbursal date", START_DATE); } } diff --git a/fineract-provider/src/test/java/org/apache/fineract/portfolio/delinquency/validator/DelinquencyActionParseAndValidatorTest.java b/fineract-provider/src/test/java/org/apache/fineract/portfolio/delinquency/validator/DelinquencyActionParseAndValidatorTest.java index 2c1d3638fb4..4b4b0f5f97f 100644 --- a/fineract-provider/src/test/java/org/apache/fineract/portfolio/delinquency/validator/DelinquencyActionParseAndValidatorTest.java +++ b/fineract-provider/src/test/java/org/apache/fineract/portfolio/delinquency/validator/DelinquencyActionParseAndValidatorTest.java @@ -65,6 +65,7 @@ class DelinquencyActionParseAndValidatorTest { public void testParseAndValidationIsOKForPause() throws JsonProcessingException { Loan loan = Mockito.mock(Loan.class); Mockito.when(loan.getStatus()).thenReturn(LoanStatus.ACTIVE); + Mockito.when(loan.getDisbursementDate()).thenReturn(localDate("07 September 2022")); JsonCommand command = delinquencyAction("pause", "09 September 2022", "19 September 2022"); @@ -96,6 +97,7 @@ public void testParseAndValidationIsOKForResume() throws JsonProcessingException public void testPauseBothStartAndEndDateIsOverlappingWithAnActivePause() throws JsonProcessingException { Loan loan = Mockito.mock(Loan.class); Mockito.when(loan.getStatus()).thenReturn(LoanStatus.ACTIVE); + Mockito.when(loan.getDisbursementDate()).thenReturn(localDate("07 September 2022")); List existing = List.of(loanDelinquencyAction(PAUSE, "14 September 2022", "22 September 2022")); JsonCommand command = delinquencyAction("pause", "09 September 2022", "15 September 2022"); @@ -111,6 +113,7 @@ public void testPauseBothStartAndEndDateIsOverlappingWithAnActivePause() throws public void testPauseStartIsOverlappingWithAnActivePause() throws JsonProcessingException { Loan loan = Mockito.mock(Loan.class); Mockito.when(loan.getStatus()).thenReturn(LoanStatus.ACTIVE); + Mockito.when(loan.getDisbursementDate()).thenReturn(localDate("11 September 2022")); List existing = List.of(loanDelinquencyAction(PAUSE, "14 September 2022", "22 September 2022")); JsonCommand command = delinquencyAction("pause", "15 September 2022", "23 September 2022"); @@ -126,7 +129,7 @@ public void testPauseStartIsOverlappingWithAnActivePause() throws JsonProcessing public void testNewPauseEndIsOverlappingWithExistingPause() throws JsonProcessingException { Loan loan = Mockito.mock(Loan.class); Mockito.when(loan.getStatus()).thenReturn(LoanStatus.ACTIVE); - + Mockito.when(loan.getDisbursementDate()).thenReturn(localDate("11 September 2022")); List existing = List.of(loanDelinquencyAction(PAUSE, "15 September 2022", "22 September 2022")); JsonCommand command = delinquencyAction("pause", "13 September 2022", "20 September 2022"); List effectiveList = List.of(loanDelinquencyActionData(existing.get(0))); @@ -141,6 +144,7 @@ public void testNewPauseEndIsOverlappingWithExistingPause() throws JsonProcessin public void testNewPauseIsOverlappingWithExistingPauseBecauseSameDates() throws JsonProcessingException { Loan loan = Mockito.mock(Loan.class); Mockito.when(loan.getStatus()).thenReturn(LoanStatus.ACTIVE); + Mockito.when(loan.getDisbursementDate()).thenReturn(localDate("11 September 2022")); List existing = List.of(loanDelinquencyAction(PAUSE, "15 September 2022", "22 September 2022")); JsonCommand command = delinquencyAction("pause", "15 September 2022", "22 September 2022"); @@ -156,6 +160,7 @@ public void testNewPauseIsOverlappingWithExistingPauseBecauseSameDates() throws public void testNewPauseIsNotOverlappingBecauseThereWasAResume() throws JsonProcessingException { Loan loan = Mockito.mock(Loan.class); Mockito.when(loan.getStatus()).thenReturn(LoanStatus.ACTIVE); + Mockito.when(loan.getDisbursementDate()).thenReturn(localDate("11 September 2022")); JsonCommand command = delinquencyAction("pause", "18 September 2022", "20 September 2022"); @@ -269,13 +274,15 @@ public void testValidationErrorPausePeriodShouldBeAtLeastOneDay() throws JsonPro } @Test - public void testValidationErrorPausePeriodMustBeInFuture() throws JsonProcessingException { + public void testValidationErrorPausePeriodMustNotBeBeforeDisbursement() throws JsonProcessingException { Loan loan = Mockito.mock(Loan.class); Mockito.when(loan.getStatus()).thenReturn(LoanStatus.ACTIVE); + Mockito.when(loan.getDisbursementDate()).thenReturn(localDate("11 September 2022")); JsonCommand command = delinquencyAction("pause", "08 September 2022", "09 September 2022"); - assertPlatformValidationException("Start date of pause period must be in the future", "loan-delinquency-action-invalid-start-date", + assertPlatformValidationException("Start date of pause period must be after first disbursal date", + "loan-delinquency-action-invalid-start-date", () -> underTest.validateAndParseUpdate(command, loan, List.of(), localDate("09 September 2022"))); } @@ -308,6 +315,7 @@ public void testStartDateIsMissingForResume() { public void testNewPausePeriodStartingOnExistingEndDate() throws JsonProcessingException { Loan loan = Mockito.mock(Loan.class); Mockito.when(loan.getStatus()).thenReturn(LoanStatus.ACTIVE); + Mockito.when(loan.getDisbursementDate()).thenReturn(localDate("11 September 2022")); JsonCommand command = delinquencyAction("pause", "18 September 2022", "20 September 2022"); @@ -324,6 +332,7 @@ public void testNewPausePeriodStartingOnExistingEndDate() throws JsonProcessingE public void testNewPauseEndingOnExistingStartDate() throws JsonProcessingException { Loan loan = Mockito.mock(Loan.class); Mockito.when(loan.getStatus()).thenReturn(LoanStatus.ACTIVE); + Mockito.when(loan.getDisbursementDate()).thenReturn(localDate("11 September 2022")); JsonCommand command = delinquencyAction("pause", "18 September 2022", "20 September 2022"); @@ -340,6 +349,7 @@ public void testNewPauseEndingOnExistingStartDate() throws JsonProcessingExcepti public void testNewPausePeriodStartingOnExistingEffectiveEndDate() throws JsonProcessingException { Loan loan = Mockito.mock(Loan.class); Mockito.when(loan.getStatus()).thenReturn(LoanStatus.ACTIVE); + Mockito.when(loan.getDisbursementDate()).thenReturn(localDate("11 September 2022")); JsonCommand command = delinquencyAction("pause", "18 September 2022", "20 September 2022"); @@ -355,6 +365,21 @@ public void testNewPausePeriodStartingOnExistingEffectiveEndDate() throws JsonPr Assertions.assertEquals(localDate("20 September 2022"), parsedDelinquencyAction.getEndDate()); } + @Test + public void testParseAndValidationIsOKForBackdatedPause() throws JsonProcessingException { + Loan loan = Mockito.mock(Loan.class); + Mockito.when(loan.getStatus()).thenReturn(LoanStatus.ACTIVE); + Mockito.when(loan.getDisbursementDate()).thenReturn(localDate("07 September 2022")); + + JsonCommand command = delinquencyAction("pause", "08 September 2022", "19 September 2022"); + + LoanDelinquencyAction parsedDelinquencyAction = underTest.validateAndParseUpdate(command, loan, List.of(), + localDate("09 September 2022")); + Assertions.assertEquals(PAUSE, parsedDelinquencyAction.getAction()); + Assertions.assertEquals(localDate("08 September 2022"), parsedDelinquencyAction.getStartDate()); + Assertions.assertEquals(localDate("19 September 2022"), parsedDelinquencyAction.getEndDate()); + } + @NotNull private JsonCommand delinquencyAction(@Nullable String action, @Nullable String startDate, @Nullable String endDate) throws JsonProcessingException { diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/DelinquencyActionIntegrationTests.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/DelinquencyActionIntegrationTests.java index ac409adae49..f7c8284fb08 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/DelinquencyActionIntegrationTests.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/DelinquencyActionIntegrationTests.java @@ -22,6 +22,7 @@ import static org.apache.fineract.infrastructure.businessdate.domain.BusinessDateType.BUSINESS_DATE; import static org.apache.fineract.portfolio.delinquency.domain.DelinquencyAction.PAUSE; import static org.apache.fineract.portfolio.delinquency.domain.DelinquencyAction.RESUME; +import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertThrows; @@ -32,11 +33,14 @@ import java.util.Arrays; import java.util.List; import java.util.UUID; +import lombok.AllArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.tuple.Pair; import org.apache.fineract.client.models.BusinessDateRequest; import org.apache.fineract.client.models.GetDelinquencyActionsResponse; import org.apache.fineract.client.models.GetLoanProductsProductIdResponse; import org.apache.fineract.client.models.GetLoansLoanIdDelinquencyPausePeriod; +import org.apache.fineract.client.models.GetLoansLoanIdLoanInstallmentLevelDelinquency; import org.apache.fineract.client.models.GetLoansLoanIdResponse; import org.apache.fineract.client.models.PostLoanProductsRequest; import org.apache.fineract.client.models.PostLoanProductsResponse; @@ -44,6 +48,7 @@ import org.apache.fineract.client.util.CallFailedRuntimeException; import org.apache.fineract.integrationtests.common.ClientHelper; import org.apache.fineract.integrationtests.common.loans.LoanTestLifecycleExtension; +import org.apache.fineract.integrationtests.common.products.DelinquencyBucketsHelper; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -201,7 +206,7 @@ public void testCreatePauseAndResumeDelinquencyActionWithStatusFlag() { } @Test - public void testValidationErrorIsThrownWhenCreatingActionInThePast() { + public void testValidationErrorIsThrownWhenCreatingPauseActionWithBackdatedStartDateBeforeDisbursement() { runAt("01 January 2023", () -> { // Create Client Long clientId = clientHelper.createClient(ClientHelper.defaultClientCreationRequest()).getClientId(); @@ -215,10 +220,80 @@ public void testValidationErrorIsThrownWhenCreatingActionInThePast() { // Disburse Loan disburseLoan(loanId, BigDecimal.valueOf(1000.00), "01 January 2023"); - // Create Delinquency Pause for the Loan in the past + // Create Delinquency Pause for the Loan before disbursement date CallFailedRuntimeException exception = assertThrows(CallFailedRuntimeException.class, () -> loanTransactionHelper.createLoanDelinquencyAction(loanId, PAUSE, "05 December 2022", "15 January 2023")); - assertTrue(exception.getMessage().contains("Start date of pause period must be in the future")); + assertTrue(exception.getMessage().contains("Start date of pause period must be after first disbursal date")); + }); + } + + @Test + public void testCreateAndVerifyBackdatedPauseDelinquencyAction() { + runAt("30 January 2023", () -> { + // Create Client + Long clientId = clientHelper.createClient(ClientHelper.defaultClientCreationRequest()).getClientId(); + + // Create Loan Product + Long loanProductId = createLoanProductWith25PctDownPayment(true, true); + + // Apply and Approve Loan + Long loanId = applyAndApproveLoan(clientId, loanProductId, "25 December 2022", 1500.0, 3, + req -> req.submittedOnDate("25 December 2022")); + + // Disburse Loan + disburseLoan(loanId, BigDecimal.valueOf(1000.00), "25 December 2022"); + + // Create Delinquency Pause for the Loan in the past + PostLoansDelinquencyActionResponse response = loanTransactionHelper.createLoanDelinquencyAction(loanId, PAUSE, + "28 January 2023", "15 February 2023"); + + List loanDelinquencyActions = loanTransactionHelper.getLoanDelinquencyActions(loanId); + Assertions.assertNotNull(loanDelinquencyActions); + Assertions.assertEquals(1, loanDelinquencyActions.size()); + Assertions.assertEquals("PAUSE", loanDelinquencyActions.get(0).getAction()); + Assertions.assertEquals(LocalDate.parse("28 January 2023", dateTimeFormatter), loanDelinquencyActions.get(0).getStartDate()); + Assertions.assertEquals(LocalDate.parse("15 February 2023", dateTimeFormatter), loanDelinquencyActions.get(0).getEndDate()); + + // Validate Active Delinquency Pause Period on Loan + validateLoanDelinquencyPausePeriods(loanId, pausePeriods("28 January 2023", "15 February 2023", true)); + }); + } + + @Test + public void testVerifyLoanDelinquencyRecalculationForBackdatedPauseDelinquencyAction() { + runAt("30 January 2023", () -> { + // Create Client + Long clientId = clientHelper.createClient(ClientHelper.defaultClientCreationRequest()).getClientId(); + + // Create Loan Product + Long loanProductId = createLoanProductWith25PctDownPaymentAndDelinquencyBucket(true, true, true); + + // Apply and Approve Loan + Long loanId = applyAndApproveLoan(clientId, loanProductId, "25 December 2022", 1500.0, 3, + req -> req.submittedOnDate("25 December 2022")); + + // Disburse Loan + disburseLoan(loanId, BigDecimal.valueOf(1000.00), "25 December 2022"); + + // Loan delinquency data before backdated pause + verifyLoanDelinquencyData(loanId, 6, new InstallmentDelinquencyData(4, 10, BigDecimal.valueOf(250.0))); + + // Create Delinquency Pause for the Loan in the past + PostLoansDelinquencyActionResponse response = loanTransactionHelper.createLoanDelinquencyAction(loanId, PAUSE, + "27 January 2023", "15 February 2023"); + + List loanDelinquencyActions = loanTransactionHelper.getLoanDelinquencyActions(loanId); + Assertions.assertNotNull(loanDelinquencyActions); + Assertions.assertEquals(1, loanDelinquencyActions.size()); + Assertions.assertEquals("PAUSE", loanDelinquencyActions.get(0).getAction()); + Assertions.assertEquals(LocalDate.parse("27 January 2023", dateTimeFormatter), loanDelinquencyActions.get(0).getStartDate()); + Assertions.assertEquals(LocalDate.parse("15 February 2023", dateTimeFormatter), loanDelinquencyActions.get(0).getEndDate()); + + // Loan delinquency data calculation after backdated pause + verifyLoanDelinquencyData(loanId, 3, new InstallmentDelinquencyData(1, 3, BigDecimal.valueOf(250.0))); + + // Validate Active Delinquency Pause Period on Loan + validateLoanDelinquencyPausePeriods(loanId, pausePeriods("27 January 2023", "15 February 2023", true)); }); } @@ -265,6 +340,24 @@ private GetLoansLoanIdDelinquencyPausePeriod pausePeriods(String startDate, Stri return pausePeriod; } + private void verifyLoanDelinquencyData(Long loanId, Integer loanLevelDelinquentDays, + InstallmentDelinquencyData... expectedInstallmentLevelInstallmentDelinquencyData) { + GetLoansLoanIdResponse loan = loanTransactionHelper.getLoan(requestSpec, responseSpec, loanId.intValue()); + Assertions.assertNotNull(loan.getDelinquent()); + List installmentLevelDelinquency = loan.getDelinquent() + .getInstallmentLevelDelinquency(); + + assertThat(loan.getDelinquent().getDelinquentDays()).isEqualTo(loanLevelDelinquentDays); + + assertThat(installmentLevelDelinquency.get(0).getMaximumAgeDays()) + .isEqualTo(expectedInstallmentLevelInstallmentDelinquencyData[0].maxAgeDays); + assertThat(installmentLevelDelinquency.get(0).getMinimumAgeDays()) + .isEqualTo(expectedInstallmentLevelInstallmentDelinquencyData[0].minAgeDays); + assertThat(installmentLevelDelinquency.get(0).getDelinquentAmount()) + .isEqualByComparingTo(expectedInstallmentLevelInstallmentDelinquencyData[0].delinquentAmount); + + } + private Long createLoanProductWith25PctDownPayment(boolean autoDownPaymentEnabled, boolean multiDisburseEnabled) { PostLoanProductsRequest product = createOnePeriod30DaysLongNoInterestPeriodicAccrualProduct(); product.setMultiDisburseLoan(multiDisburseEnabled); @@ -294,4 +387,45 @@ private Long createLoanProductWith25PctDownPayment(boolean autoDownPaymentEnable return loanProductId; } + private Long createLoanProductWith25PctDownPaymentAndDelinquencyBucket(boolean autoDownPaymentEnabled, boolean multiDisburseEnabled, + boolean installmentLevelDelinquencyEnabled) { + // Create DelinquencyBuckets + Integer delinquencyBucketId = DelinquencyBucketsHelper.createDelinquencyBucket(requestSpec, responseSpec, List.of(// + Pair.of(1, 3), // + Pair.of(4, 10), // + Pair.of(11, 60), // + Pair.of(61, null)// + )); + PostLoanProductsRequest product = createOnePeriod30DaysLongNoInterestPeriodicAccrualProduct(); + product.setDelinquencyBucketId(delinquencyBucketId.longValue()); + product.setMultiDisburseLoan(multiDisburseEnabled); + product.setEnableDownPayment(true); + + product.setDisbursedAmountPercentageForDownPayment(DOWN_PAYMENT_PERCENTAGE); + product.setEnableAutoRepaymentForDownPayment(autoDownPaymentEnabled); + product.setEnableInstallmentLevelDelinquency(installmentLevelDelinquencyEnabled); + + PostLoanProductsResponse loanProductResponse = loanProductHelper.createLoanProduct(product); + GetLoanProductsProductIdResponse getLoanProductsProductIdResponse = loanProductHelper + .retrieveLoanProductById(loanProductResponse.getResourceId()); + + Long loanProductId = loanProductResponse.getResourceId(); + + assertEquals(TRUE, getLoanProductsProductIdResponse.getEnableDownPayment()); + assertNotNull(getLoanProductsProductIdResponse.getDisbursedAmountPercentageForDownPayment()); + assertEquals(0, getLoanProductsProductIdResponse.getDisbursedAmountPercentageForDownPayment().compareTo(DOWN_PAYMENT_PERCENTAGE)); + assertEquals(autoDownPaymentEnabled, getLoanProductsProductIdResponse.getEnableAutoRepaymentForDownPayment()); + assertEquals(multiDisburseEnabled, getLoanProductsProductIdResponse.getMultiDisburseLoan()); + return loanProductId; + + } + + @AllArgsConstructor + public static class InstallmentDelinquencyData { + + Integer minAgeDays; + Integer maxAgeDays; + BigDecimal delinquentAmount; + } + }