From b1af8a3f833503b65ebf42717b0d69de9c0e2891 Mon Sep 17 00:00:00 2001 From: Jose Alberto Hernandez Date: Thu, 1 Jun 2023 01:50:50 -0600 Subject: [PATCH] FINERACT-1724: Arrears configuration at loan creation is not considered correctly --- .../LoanDelinquencyDomainServiceImpl.java | 7 +- .../api/LoansApiResourceSwagger.java | 2 + .../api/LoanProductsApiResourceSwagger.java | 2 + .../LoanDelinquencyDomainServiceTest.java | 9 +- .../DelinquencyBucketsIntegrationTest.java | 115 ++++++++++++++++-- 5 files changed, 116 insertions(+), 19 deletions(-) diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/delinquency/service/LoanDelinquencyDomainServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/delinquency/service/LoanDelinquencyDomainServiceImpl.java index 90e036f82..13507cf59 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/delinquency/service/LoanDelinquencyDomainServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/delinquency/service/LoanDelinquencyDomainServiceImpl.java @@ -120,11 +120,8 @@ public class LoanDelinquencyDomainServiceImpl implements LoanDelinquencyDomainSe } Integer graceDays = 0; - if (loan.getLoanProduct().getLoanProductRelatedDetail().getGraceOnArrearsAgeing() != null) { - graceDays = loan.getLoanProduct().getLoanProductRelatedDetail().getGraceOnArrearsAgeing(); - if (graceDays == null) { - graceDays = 0; - } + if (loan.getLoanProductRelatedDetail().getGraceOnArrearsAgeing() != null) { + graceDays = loan.getLoanProductRelatedDetail().getGraceOnArrearsAgeing(); } log.debug("Loan id {} with overdue since date {} and outstanding amount {}", loan.getId(), overdueSinceDate, outstandingAmount); 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 96bf96f27..2c636e45f 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 @@ -1072,6 +1072,8 @@ final class LoansApiResourceSwagger { public LocalDate overpaidOnDate; @Schema(example = "false") public Boolean chargedOff; + @Schema(example = "3") + public Integer inArrearsTolerance; } @Schema(description = "GetLoansResponse") diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/api/LoanProductsApiResourceSwagger.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/api/LoanProductsApiResourceSwagger.java index 1f2afab08..2626ac977 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/api/LoanProductsApiResourceSwagger.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/api/LoanProductsApiResourceSwagger.java @@ -1199,6 +1199,8 @@ final class LoanProductsApiResourceSwagger { public Integer dueDaysForRepaymentEvent; @Schema(example = "3") public Integer overDueDaysForRepaymentEvent; + @Schema(example = "3") + public Integer inArrearsTolerance; } @Schema(description = "PutLoanProductsProductIdRequest") 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 054021e56..c75580724 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 @@ -112,8 +112,7 @@ public class LoanDelinquencyDomainServiceTest { // when when(loanProductRelatedDetail.getGraceOnArrearsAgeing()).thenReturn(0); - when(loanProduct.getLoanProductRelatedDetail()).thenReturn(loanProductRelatedDetail); - when(loan.getLoanProduct()).thenReturn(loanProduct); + when(loan.getLoanProductRelatedDetail()).thenReturn(loanProductRelatedDetail); when(loan.getRepaymentScheduleInstallments()).thenReturn(repaymentScheduleInstallments); when(loan.getCurrency()).thenReturn(currency); @@ -137,8 +136,7 @@ public class LoanDelinquencyDomainServiceTest { // when when(loanProductRelatedDetail.getGraceOnArrearsAgeing()).thenReturn(0); - when(loanProduct.getLoanProductRelatedDetail()).thenReturn(loanProductRelatedDetail); - when(loan.getLoanProduct()).thenReturn(loanProduct); + when(loan.getLoanProductRelatedDetail()).thenReturn(loanProductRelatedDetail); when(loan.getRepaymentScheduleInstallments()).thenReturn(repaymentScheduleInstallments); when(loan.getLoanTransactions(Mockito.any(Predicate.class))).thenReturn(Collections.emptyList()); when(loan.getLastLoanRepaymentScheduleInstallment()).thenReturn(repaymentScheduleInstallments.get(0)); @@ -174,8 +172,7 @@ public class LoanDelinquencyDomainServiceTest { // when when(loanProductRelatedDetail.getGraceOnArrearsAgeing()).thenReturn(0); - when(loanProduct.getLoanProductRelatedDetail()).thenReturn(loanProductRelatedDetail); - when(loan.getLoanProduct()).thenReturn(loanProduct); + when(loan.getLoanProductRelatedDetail()).thenReturn(loanProductRelatedDetail); when(loan.getRepaymentScheduleInstallments()).thenReturn(repaymentScheduleInstallments); when(loan.getCurrency()).thenReturn(currency); diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/DelinquencyBucketsIntegrationTest.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/DelinquencyBucketsIntegrationTest.java index 252c7ec64..dcac50af7 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/DelinquencyBucketsIntegrationTest.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/DelinquencyBucketsIntegrationTest.java @@ -50,6 +50,8 @@ import org.apache.fineract.client.models.PostDelinquencyRangeResponse; import org.apache.fineract.client.models.PostLoansLoanIdTransactionsResponse; import org.apache.fineract.client.models.PutDelinquencyBucketResponse; import org.apache.fineract.client.models.PutDelinquencyRangeResponse; +import org.apache.fineract.client.models.PutLoanProductsProductIdRequest; +import org.apache.fineract.client.models.PutLoanProductsProductIdResponse; import org.apache.fineract.cob.data.JobBusinessStepConfigData; import org.apache.fineract.infrastructure.businessdate.domain.BusinessDateType; import org.apache.fineract.integrationtests.common.BusinessDateHelper; @@ -294,7 +296,7 @@ public class DelinquencyBucketsIntegrationTest { // Create Loan Account final Integer loanId = createLoanAccount(loanTransactionHelper, clientId.toString(), - getLoanProductsProductResponse.getId().toString(), operationDate); + getLoanProductsProductResponse.getId().toString(), operationDate, null); GetLoansLoanIdResponse getLoansLoanIdResponse = loanTransactionHelper.getLoan(requestSpec, responseSpec, loanId); assertNotNull(getLoansLoanIdResponse); @@ -390,7 +392,7 @@ public class DelinquencyBucketsIntegrationTest { // Create Loan Account final Integer loanId = createLoanAccount(loanTransactionHelper, clientId.toString(), - getLoanProductsProductResponse.getId().toString(), operationDate); + getLoanProductsProductResponse.getId().toString(), operationDate, null); GetLoansLoanIdResponse getLoansLoanIdResponse = loanTransactionHelper.getLoan(requestSpec, responseSpec, loanId); log.info("Loan Delinquency Range after Disbursement {}", getLoansLoanIdResponse.getDelinquencyRange().getClassification()); @@ -488,7 +490,7 @@ public class DelinquencyBucketsIntegrationTest { // Create Loan Account final Integer loanId = createLoanAccount(loanTransactionHelper, clientId.toString(), - getLoanProductsProductResponse.getId().toString(), operationDate); + getLoanProductsProductResponse.getId().toString(), operationDate, null); GetLoansLoanIdResponse getLoansLoanIdResponse = loanTransactionHelper.getLoan(requestSpec, responseSpec, loanId); assertNotNull(getLoansLoanIdResponse); @@ -583,7 +585,7 @@ public class DelinquencyBucketsIntegrationTest { // Create Loan Account final Integer loanId = createLoanAccount(loanTransactionHelper, clientId.toString(), - getLoanProductsProductResponse.getId().toString(), operationDate); + getLoanProductsProductResponse.getId().toString(), operationDate, null); GetLoansLoanIdResponse getLoansLoanIdResponse = loanTransactionHelper.getLoan(requestSpec, responseSpec, loanId); loanTransactionHelper.printRepaymentSchedule(getLoansLoanIdResponse); @@ -689,7 +691,7 @@ public class DelinquencyBucketsIntegrationTest { // Create Loan Account final Integer loanId = createLoanAccount(loanTransactionHelper, clientId.toString(), - getLoanProductsProductResponse.getId().toString(), operationDate); + getLoanProductsProductResponse.getId().toString(), operationDate, null); // Run first time the Job final String jobName = "Loan Delinquency Classification"; @@ -777,7 +779,7 @@ public class DelinquencyBucketsIntegrationTest { // Create Loan Account final Integer loanId = createLoanAccount(loanTransactionHelper, clientId.toString(), - getLoanProductsProductResponse.getId().toString(), operationDate); + getLoanProductsProductResponse.getId().toString(), operationDate, null); // COB Step Validation final JobBusinessStepConfigData jobBusinessStepConfigData = BusinessStepConfigurationHelper @@ -879,7 +881,7 @@ public class DelinquencyBucketsIntegrationTest { // Create Loan Account final Integer loanId = createLoanAccount(loanTransactionHelper, clientId.toString(), - getLoanProductsProductResponse.getId().toString(), operationDate); + getLoanProductsProductResponse.getId().toString(), operationDate, null); // Get loan details expecting to have a delinquency classification GetLoansLoanIdResponse getLoansLoanIdResponse = loanTransactionHelper.getLoan(requestSpec, responseSpec, loanId); @@ -911,6 +913,95 @@ public class DelinquencyBucketsIntegrationTest { } } + @Test + public void testLoanClassificationUsingAgeingArrears() { + try { + GlobalConfigurationHelper.updateIsBusinessDateEnabled(requestSpec, responseSpec, Boolean.TRUE); + + LocalDate bussinesLocalDate = Utils.getDateAsLocalDate("01 January 2012"); + log.info("Current date {}", bussinesLocalDate); + BusinessDateHelper.updateBusinessDate(requestSpec, responseSpec, BusinessDateType.BUSINESS_DATE, bussinesLocalDate); + + // Given + final LoanTransactionHelper loanTransactionHelper = new LoanTransactionHelper(this.requestSpec, this.responseSpec); + final SchedulerJobHelper schedulerJobHelper = new SchedulerJobHelper(requestSpec); + + ArrayList rangeIds = new ArrayList<>(); + String jsonRange = DelinquencyRangesHelper.getAsJSON(1, 3); + PostDelinquencyRangeResponse delinquencyRangeResponse = DelinquencyRangesHelper.createDelinquencyRange(requestSpec, + responseSpec, jsonRange); + rangeIds.add(delinquencyRangeResponse.getResourceId()); + final GetDelinquencyRangesResponse range = DelinquencyRangesHelper.getDelinquencyRange(requestSpec, responseSpec, + delinquencyRangeResponse.getResourceId()); + final String classificationExpected = range.getClassification(); + log.info("Expected Delinquency Range classification {}", classificationExpected); + + jsonRange = DelinquencyRangesHelper.getAsJSON(4, 60); + delinquencyRangeResponse = DelinquencyRangesHelper.createDelinquencyRange(requestSpec, responseSpec, jsonRange); + rangeIds.add(delinquencyRangeResponse.getResourceId()); + + String jsonBucket = DelinquencyBucketsHelper.getAsJSON(rangeIds); + PostDelinquencyBucketResponse delinquencyBucketResponse = DelinquencyBucketsHelper.createDelinquencyBucket(requestSpec, + responseSpec, jsonBucket); + final GetDelinquencyBucketsResponse delinquencyBucket = DelinquencyBucketsHelper.getDelinquencyBucket(requestSpec, responseSpec, + delinquencyBucketResponse.getResourceId()); + + // Client and Loan account creation + final Integer clientId = ClientHelper.createClient(this.requestSpec, this.responseSpec, "01 January 2012"); + final GetLoanProductsProductIdResponse getLoanProductsProductResponse = createLoanProduct(loanTransactionHelper, + delinquencyBucket.getId(), "3"); + assertNotNull(getLoanProductsProductResponse); + log.info("Loan Product Arrears: {}", getLoanProductsProductResponse.getInArrearsTolerance()); + assertEquals(3, getLoanProductsProductResponse.getInArrearsTolerance()); + + // Older date to have more than one overdue installment + final LocalDate transactionDate = bussinesLocalDate; + String operationDate = Utils.dateFormatter.format(transactionDate); + + // Create Loan Account + final Integer loanId = createLoanAccount(loanTransactionHelper, clientId.toString(), + getLoanProductsProductResponse.getId().toString(), operationDate, "3"); + + // Get loan details expecting to have a delinquency classification + GetLoansLoanIdResponse getLoansLoanIdResponse = loanTransactionHelper.getLoan(requestSpec, responseSpec, loanId); + final GetDelinquencyRangesResponse firstTestCase = getLoansLoanIdResponse.getDelinquencyRange(); + log.info("Loan Delinquency Range is null {}", (firstTestCase == null)); + loanTransactionHelper.printRepaymentSchedule(getLoansLoanIdResponse); + log.info("Loan Account Arrears {}", getLoansLoanIdResponse.getInArrearsTolerance()); + assertEquals(3, getLoansLoanIdResponse.getInArrearsTolerance()); + + // Update the Loan Product + updateLoanProduct(loanTransactionHelper, getLoanProductsProductResponse.getId(), 0); + GetLoanProductsProductIdResponse loanProductsProductIdResponseUpd = loanTransactionHelper + .getLoanProduct(getLoanProductsProductResponse.getId().intValue()); + assertNotNull(loanProductsProductIdResponseUpd); + log.info("Loan Product Arrears: {}", loanProductsProductIdResponseUpd.getInArrearsTolerance()); + assertEquals(0, loanProductsProductIdResponseUpd.getInArrearsTolerance()); + + final String jobName = "Loan COB"; + + bussinesLocalDate = Utils.getDateAsLocalDate("31 January 2012"); + LocalDate lastLoanCOBBusinessDate = bussinesLocalDate.minusDays(1); + schedulerJobHelper.fastForwardTime(lastLoanCOBBusinessDate, bussinesLocalDate, jobName, responseSpec); + log.info("Current date {}", bussinesLocalDate); + BusinessDateHelper.updateBusinessDate(requestSpec, responseSpec, BusinessDateType.BUSINESS_DATE, bussinesLocalDate); + // Run Second time the Job + schedulerJobHelper.executeAndAwaitJob(jobName); + + // Get loan details expecting to have a delinquency classification + getLoansLoanIdResponse = loanTransactionHelper.getLoan(requestSpec, responseSpec, loanId); + loanTransactionHelper.printDelinquencyData(getLoansLoanIdResponse); + + GetLoansLoanIdCollectionData getLoansLoanIdCollectionData = getLoansLoanIdResponse.getDelinquent(); + assertNotNull(getLoansLoanIdCollectionData); + assertEquals(0, getLoansLoanIdCollectionData.getDelinquentDays()); + assertEquals(0, getLoansLoanIdCollectionData.getPastDueDays()); + + } finally { + GlobalConfigurationHelper.updateIsBusinessDateEnabled(requestSpec, responseSpec, Boolean.FALSE); + } + } + private GetLoanProductsProductIdResponse createLoanProduct(final LoanTransactionHelper loanTransactionHelper, final Integer delinquencyBucketId, final String inArrearsTolerance) { final HashMap loanProductMap = new LoanProductTestBuilder().withInArrearsTolerance(inArrearsTolerance).build(null, @@ -919,8 +1010,15 @@ public class DelinquencyBucketsIntegrationTest { return loanTransactionHelper.getLoanProduct(loanProductId); } + private PutLoanProductsProductIdResponse updateLoanProduct(LoanTransactionHelper loanTransactionHelper, Long id, + final Integer inArrearsTolerance) { + final PutLoanProductsProductIdRequest requestModifyLoan = new PutLoanProductsProductIdRequest() + .inArrearsTolerance(inArrearsTolerance); + return loanTransactionHelper.updateLoanProduct(id, requestModifyLoan); + } + private Integer createLoanAccount(final LoanTransactionHelper loanTransactionHelper, final String clientId, final String loanProductId, - final String operationDate) { + final String operationDate, final String inArrearsTolerance) { final String loanApplicationJSON = new LoanApplicationTestBuilder().withPrincipal(principalAmount).withLoanTermFrequency("12") .withLoanTermFrequencyAsMonths().withNumberOfRepayments("12").withRepaymentEveryAfter("1") .withRepaymentFrequencyTypeAsMonths() // @@ -928,6 +1026,7 @@ public class DelinquencyBucketsIntegrationTest { .withExpectedDisbursementDate(operationDate) // .withInterestTypeAsDecliningBalance() // .withSubmittedOnDate(operationDate) // + .withInArrearsTolerance(inArrearsTolerance) // .build(clientId, loanProductId, null); final Integer loanId = loanTransactionHelper.getLoanId(loanApplicationJSON); loanTransactionHelper.approveLoan(operationDate, principalAmount, loanId, null);