From 33d781f5fb8ff5a0db5df91cac44f2998a341b84 Mon Sep 17 00:00:00 2001 From: Jose Alberto Hernandez Date: Mon, 12 Sep 2022 00:23:43 -0500 Subject: [PATCH] Loan Delinquency Tags with Loan charges --- .../LoanRescheduleRequestDataValidator.java | 8 +- ...WritePlatformServiceJpaRepositoryImpl.java | 9 +- .../DelinquencyBucketsIntegrationTest.java | 101 ++++++++++++++++++ 3 files changed, 106 insertions(+), 12 deletions(-) diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/data/LoanRescheduleRequestDataValidator.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/data/LoanRescheduleRequestDataValidator.java index 67d7b8ae6..cb20dde73 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/data/LoanRescheduleRequestDataValidator.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/data/LoanRescheduleRequestDataValidator.java @@ -30,6 +30,7 @@ import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; +import lombok.AllArgsConstructor; import org.apache.commons.lang3.StringUtils; import org.apache.fineract.infrastructure.core.api.JsonCommand; import org.apache.fineract.infrastructure.core.data.ApiParameterError; @@ -42,10 +43,10 @@ import org.apache.fineract.portfolio.loanaccount.domain.LoanCharge; import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment; import org.apache.fineract.portfolio.loanaccount.rescheduleloan.RescheduleLoansApiConstants; import org.apache.fineract.portfolio.loanaccount.rescheduleloan.domain.LoanRescheduleRequest; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; @Component +@AllArgsConstructor public class LoanRescheduleRequestDataValidator { private final FromJsonHelper fromJsonHelper; @@ -68,11 +69,6 @@ public class LoanRescheduleRequestDataValidator { Arrays.asList(RescheduleLoansApiConstants.localeParamName, RescheduleLoansApiConstants.dateFormatParamName, RescheduleLoansApiConstants.approvedOnDateParam)); - @Autowired - public LoanRescheduleRequestDataValidator(FromJsonHelper fromJsonHelper) { - this.fromJsonHelper = fromJsonHelper; - } - /** * Validates the request to create a new loan reschedule entry * diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java index 3ecadc238..4ed29ed93 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java @@ -1253,7 +1253,6 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf changes.put("transactionDate", command.stringValueOfParameterNamed("transactionDate")); changes.put("locale", command.locale()); changes.put("dateFormat", command.dateFormat()); - final LocalDate transactionDate = command.localDateValueOfParameterNamed("transactionDate"); final Loan loan = this.loanAssembler.assembleFrom(loanId); if (command.hasParameter("writeoffReasonId")) { Long writeoffReasonId = command.longValueOfParameterNamed("writeoffReasonId"); @@ -1322,7 +1321,6 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf changes.put("transactionDate", command.stringValueOfParameterNamed("transactionDate")); changes.put("locale", command.locale()); changes.put("dateFormat", command.dateFormat()); - final LocalDate transactionDate = command.localDateValueOfParameterNamed("transactionDate"); final List existingTransactionIds = new ArrayList<>(); final List existingReversedTransactionIds = new ArrayList<>(); @@ -1557,6 +1555,8 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf && loan.isFeeCompoundingEnabledForInterestRecalculation()) { this.loanAccountDomainService.recalculateAccruals(loan); } + this.loanAccountDomainService.setLoanDelinquencyTag(loan, DateUtils.getBusinessLocalDate()); + businessEventNotifierService.notifyPostBusinessEvent(new LoanAddChargeBusinessEvent(loanCharge)); return new CommandProcessingResultBuilder().withCommandId(command.commandId()).withEntityId(loanCharge.getId()) .withOfficeId(loan.getOfficeId()).withClientId(loan.getClientId()).withGroupId(loan.getGroupId()).withLoanId(loanId) @@ -1588,7 +1588,6 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf throw new PlatformApiDataValidationException(dataValidationErrors); } } - } public void runScheduleRecalculation(final Loan loan, final LocalDate recalculateFrom) { @@ -1606,7 +1605,6 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf this.accountTransfersWritePlatformService.updateLoanTransaction(mapEntry.getKey(), mapEntry.getValue()); } } - } } @@ -1616,7 +1614,6 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf ScheduleGeneratorDTO scheduleGeneratorDTO = this.loanUtilService.buildScheduleGeneratorDTO(loan, recalculateFrom); createLoanScheduleArchive(loan, scheduleGeneratorDTO); } - } private boolean addCharge(final Loan loan, final Charge chargeDefinition, final LoanCharge loanCharge) { @@ -1733,7 +1730,6 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf businessEventNotifierService.notifyPostBusinessEvent(new LoanBalanceChangedBusinessEvent(newChargeRefundTxn.getLoan())); businessEventNotifierService.notifyPostBusinessEvent(new LoanChargeRefundBusinessEvent(newChargeRefundTxn)); return result; - } private JsonCommand adaptLoanChargeRefundCommandForFurtherRepaymentProcessing(JsonCommand command, BigDecimal fullRefundAbleAmount) { @@ -2076,6 +2072,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf saveLoanWithDataIntegrityViolationChecks(loan); postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds); + this.loanAccountDomainService.setLoanDelinquencyTag(loan, DateUtils.getBusinessLocalDate()); businessEventNotifierService.notifyPostBusinessEvent(new LoanWaiveChargeBusinessEvent(loanCharge)); 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 1efd1d9af..ab5c8e10d 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 @@ -24,6 +24,7 @@ import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertTrue; +import com.google.gson.Gson; import io.restassured.builder.RequestSpecBuilder; import io.restassured.builder.ResponseSpecBuilder; import io.restassured.http.ContentType; @@ -52,9 +53,11 @@ import org.apache.fineract.infrastructure.businessdate.domain.BusinessDateType; import org.apache.fineract.integrationtests.common.BusinessDateHelper; import org.apache.fineract.integrationtests.common.BusinessStepConfigurationHelper; import org.apache.fineract.integrationtests.common.ClientHelper; +import org.apache.fineract.integrationtests.common.CommonConstants; import org.apache.fineract.integrationtests.common.GlobalConfigurationHelper; import org.apache.fineract.integrationtests.common.SchedulerJobHelper; import org.apache.fineract.integrationtests.common.Utils; +import org.apache.fineract.integrationtests.common.charges.ChargesHelper; import org.apache.fineract.integrationtests.common.loans.LoanApplicationTestBuilder; import org.apache.fineract.integrationtests.common.loans.LoanProductTestBuilder; import org.apache.fineract.integrationtests.common.loans.LoanTransactionHelper; @@ -507,6 +510,92 @@ public class DelinquencyBucketsIntegrationTest { log.info("Delinquency Tag Item with Lifted On {}", getDelinquencyTagsHistory.get(0).getLiftedOnDate()); } + @Test + public void testLoanClassificationRealtimeWithCharges() { + // Given + final LoanTransactionHelper loanTransactionHelper = new LoanTransactionHelper(this.requestSpec, this.responseSpec); + + ArrayList rangeIds = new ArrayList<>(); + // First Range + String jsonRange = DelinquencyRangesHelper.getAsJSON(1, 3); + PostDelinquencyRangeResponse delinquencyRangeResponse = DelinquencyRangesHelper.createDelinquencyRange(requestSpec, responseSpec, + jsonRange); + rangeIds.add(delinquencyRangeResponse.getResourceId()); + jsonRange = DelinquencyRangesHelper.getAsJSON(4, 60); + + GetDelinquencyRangesResponse range = DelinquencyRangesHelper.getDelinquencyRange(requestSpec, responseSpec, + delinquencyRangeResponse.getResourceId()); + + // Second Range + delinquencyRangeResponse = DelinquencyRangesHelper.createDelinquencyRange(requestSpec, responseSpec, jsonRange); + rangeIds.add(delinquencyRangeResponse.getResourceId()); + + range = DelinquencyRangesHelper.getDelinquencyRange(requestSpec, responseSpec, delinquencyRangeResponse.getResourceId()); + final String classificationExpected = range.getClassification(); + log.info("Expected Delinquency Range classification after Disbursement {}", classificationExpected); + + String jsonBucket = DelinquencyBucketsHelper.getAsJSON(rangeIds); + PostDelinquencyBucketResponse delinquencyBucketResponse = DelinquencyBucketsHelper.createDelinquencyBucket(requestSpec, + responseSpec, jsonBucket); + assertNotNull(delinquencyBucketResponse); + 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()); + assertNotNull(getLoanProductsProductResponse); + log.info("Loan Product Bucket Name: {}", getLoanProductsProductResponse.getDelinquencyBucket().getName()); + assertEquals(getLoanProductsProductResponse.getDelinquencyBucket().getName(), delinquencyBucket.getName()); + + final LocalDate todaysDate = Utils.getLocalDateOfTenant(); + // Older date to have more than one overdue installment + LocalDate transactionDate = todaysDate.minusDays(45); + String operationDate = Utils.dateFormatter.format(transactionDate); + + // Create Loan Account + final Integer loanId = createLoanAccount(loanTransactionHelper, clientId.toString(), + getLoanProductsProductResponse.getId().toString(), operationDate); + + GetLoansLoanIdResponse getLoansLoanIdResponse = loanTransactionHelper.getLoan(requestSpec, responseSpec, loanId); + log.info("Loan Delinquency Range after Disbursement {}", getLoansLoanIdResponse.getDelinquencyRange().getClassification()); + assertNotNull(getLoansLoanIdResponse); + // First Loan Delinquency Classification after Disbursement command + assertEquals(getLoansLoanIdResponse.getDelinquencyRange().getClassification(), classificationExpected); + printRepaymentSchedule(getLoansLoanIdResponse); + + // Apply a repayment to get a full paid installment + operationDate = Utils.dateFormatter.format(todaysDate); + loanTransactionHelper.makeLoanRepayment(operationDate, 1033.33f, loanId); + + getLoansLoanIdResponse = loanTransactionHelper.getLoan(requestSpec, responseSpec, loanId); + log.info("Loan Delinquency Range after Repayment {}", getLoansLoanIdResponse.getDelinquencyRange()); + assertNotNull(getLoansLoanIdResponse); + // The Loan Delinquency Classification after Repayment command must be null + assertNull(getLoansLoanIdResponse.getDelinquencyRange()); + printRepaymentSchedule(getLoansLoanIdResponse); + + transactionDate = todaysDate.minusDays(18); + operationDate = Utils.dateFormatter.format(transactionDate); + + // Create and apply Charge for Specific Due Date + final Integer chargeId = ChargesHelper.createCharges(requestSpec, responseSpec, + ChargesHelper.getLoanSpecifiedDueDateJSON(1, "30", false)); + assertNotNull(chargeId); + final Integer loanChargeId = loanTransactionHelper.addChargesForLoan(loanId, getChargeApplyJSON(chargeId, operationDate), + responseSpec); + assertNotNull(loanChargeId); + + getLoansLoanIdResponse = loanTransactionHelper.getLoan(requestSpec, responseSpec, loanId); + printRepaymentSchedule(getLoansLoanIdResponse); + + log.info("Loan Delinquency Range after add Loan Charge {}", getLoansLoanIdResponse.getDelinquencyRange()); + assertNotNull(getLoansLoanIdResponse.getDelinquencyRange()); + // Evaluate a Delinquency Tag set after add charge to the Loan + assertEquals(getLoansLoanIdResponse.getDelinquencyRange().getClassification(), classificationExpected); + } + @Test public void testLoanClassificationRealtimeOlderLoan() { // Given @@ -741,4 +830,16 @@ public class DelinquencyBucketsIntegrationTest { } } + private String getChargeApplyJSON(final Integer chargeId, final String dueDate) { + final HashMap map = new HashMap<>(); + map.put("chargeId", chargeId); + map.put("amount", 12.0f); + map.put("dueDate", dueDate); + map.put("dateFormat", Utils.DATE_FORMAT); + map.put("locale", CommonConstants.LOCALE); + final String chargeApplyJSON = new Gson().toJson(map); + log.info("{}", chargeApplyJSON); + return chargeApplyJSON; + } + }