From ba1d0ce66cd309b76c82025a288e202cc92b9cfe Mon Sep 17 00:00:00 2001 From: logoutdhaval Date: Fri, 16 Sep 2022 20:39:03 +0530 Subject: [PATCH] FINERACT-1748: Loan prepayment issue after advance payment --- .../domain/AbstractLoanScheduleGenerator.java | 4 +- .../ClientLoanIntegrationTest.java | 207 ++++++++++++++++++ .../common/loans/LoanProductTestBuilder.java | 10 +- 3 files changed, 219 insertions(+), 2 deletions(-) diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/AbstractLoanScheduleGenerator.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/AbstractLoanScheduleGenerator.java index 1bb5793e7..b523a645d 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/AbstractLoanScheduleGenerator.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/AbstractLoanScheduleGenerator.java @@ -2225,7 +2225,9 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener .adjustRepaymentDate(actualRepaymentDate, loanApplicationTerms, holidayDetailDTO).getChangedScheduleDate(); LocalDate modifiedLastInstDate = null; LoanTermVariationsData variation1 = null; + boolean hasDueDateVariation = false; while (loanApplicationTerms.getLoanTermVariations().hasDueDateVariation(lastInstallmentDate)) { + hasDueDateVariation = true; LoanTermVariationsData variation = loanApplicationTerms.getLoanTermVariations().nextDueDateVariation(); if (!variation.isSpecificToInstallment()) { modifiedLastInstDate = variation.getDateValue(); @@ -2233,7 +2235,7 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener } } - if (!lastInstallmentDate.isEqual(installment.getDueDate()) + if (hasDueDateVariation && !lastInstallmentDate.isEqual(installment.getDueDate()) && !installment.getDueDate().equals(modifiedLastInstDate)) { lastInstallmentDate = prevLastInstDate; actualRepaymentDate = lastInstallmentDate; diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/ClientLoanIntegrationTest.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/ClientLoanIntegrationTest.java index 0303cf825..69515a928 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/ClientLoanIntegrationTest.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/ClientLoanIntegrationTest.java @@ -45,6 +45,7 @@ import java.util.Locale; import java.util.Map; import org.apache.fineract.integrationtests.common.ClientHelper; import org.apache.fineract.integrationtests.common.CollateralManagementHelper; +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.accounting.Account; @@ -4712,6 +4713,82 @@ public class ClientLoanIntegrationTest { return this.loanTransactionHelper.getLoanProductId(loanProductJSON); } + private Integer createLoanProductWithInterestRecalculationAndCompoundingDetails(final String repaymentStrategy, + final String interestRecalculationCompoundingMethod, final String rescheduleStrategyMethod, + final String recalculationRestFrequencyType, final String preCloseInterestCalculationStrategy, final Account[] accounts, + final String installmentMultipleOf) { + final String recalculationCompoundingFrequencyType = null; + final String recalculationCompoundingFrequencyInterval = null; + final Integer recalculationCompoundingFrequencyOnDayType = null; + final Integer recalculationCompoundingFrequencyDayOfWeekType = null; + return createLoanProductWithInterestRecalculation(repaymentStrategy, interestRecalculationCompoundingMethod, + rescheduleStrategyMethod, recalculationCompoundingFrequencyType, recalculationCompoundingFrequencyInterval, + preCloseInterestCalculationStrategy, accounts, null, false, recalculationCompoundingFrequencyOnDayType, + recalculationCompoundingFrequencyDayOfWeekType, installmentMultipleOf); + } + + private Integer createLoanProductWithInterestRecalculation(final String repaymentStrategy, + final String interestRecalculationCompoundingMethod, final String rescheduleStrategyMethod, + final String recalculationCompoundingFrequencyType, final String recalculationCompoundingFrequencyInterval, + final String preCloseInterestCalculationStrategy, final Account[] accounts, final String chargeId, + boolean isArrearsBasedOnOriginalSchedule, final Integer recalculationCompoundingFrequencyOnDayType, + final Integer recalculationCompoundingFrequencyDayOfWeekType, final String installmentsMultiplesOf) { + LOG.info("------------------------------CREATING NEW LOAN PRODUCT ---------------------------------------"); + LoanProductTestBuilder builder = new LoanProductTestBuilder().withPrincipal("10000.00").withNumberOfRepayments("12") + .withRepaymentAfterEvery("1").withRepaymentTypeAsMonth().withinterestRatePerPeriod("19.9") + .withInterestRateFrequencyTypeAsMonths().withRepaymentStrategy(repaymentStrategy).withAmortizationTypeAsEqualInstallments() + .withInterestTypeAsDecliningBalance().withInterestCalculationPeriodTypeAsDays() + .withInterestRecalculationDetails(interestRecalculationCompoundingMethod, rescheduleStrategyMethod, + preCloseInterestCalculationStrategy) + .withInterestRecalculationDetails(interestRecalculationCompoundingMethod, rescheduleStrategyMethod, + preCloseInterestCalculationStrategy) + .withInterestRecalculationCompoundingFrequencyDetails(recalculationCompoundingFrequencyType, + recalculationCompoundingFrequencyInterval, recalculationCompoundingFrequencyOnDayType, + recalculationCompoundingFrequencyDayOfWeekType) + .withDefineInstallmentAmount(true).withInstallmentAmountInMultiplesOf(installmentsMultiplesOf); + if (accounts != null) { + builder = builder.withAccountingRulePeriodicAccrual(accounts); + } + + if (isArrearsBasedOnOriginalSchedule) { + builder = builder.withArrearsConfiguration(); + } + + final String loanProductJSON = builder.build(chargeId); + return this.loanTransactionHelper.getLoanProductId(loanProductJSON); + } + + private Integer applyForLoanApplicationForInterestRecalculation(final Integer clientID, final Integer loanProductID, + final String disbursementDate, final String repaymentStrategy, final String firstRepaymentDate) { + LOG.info("--------------------------------APPLYING FOR LOAN APPLICATION--------------------------------"); + final Integer collateralId = CollateralManagementHelper.createCollateralProduct(this.requestSpec, this.responseSpec); + Assertions.assertNotNull(collateralId); + List collaterals = new ArrayList<>(); + + final Integer clientCollateralId = CollateralManagementHelper.createClientCollateral(this.requestSpec, this.responseSpec, + String.valueOf(clientID), collateralId); + Assertions.assertNotNull(clientCollateralId); + addCollaterals(collaterals, clientCollateralId, BigDecimal.valueOf(1)); + + final String loanApplicationJSON = new LoanApplicationTestBuilder() // + .withPrincipal("10000.00") // + .withLoanTermFrequency("12") // + .withLoanTermFrequencyAsMonths() // + .withNumberOfRepayments("12") // + .withRepaymentEveryAfter("1") // + .withLoanTermFrequencyAsMonths() // + .withInterestRatePerPeriod("19.9") // + .withAmortizationTypeAsEqualInstallments() // + .withInterestTypeAsDecliningBalance() // + .withInterestCalculationPeriodTypeAsDays() // + .withExpectedDisbursementDate(disbursementDate) // + .withSubmittedOnDate(disbursementDate) // + .withwithRepaymentStrategy(repaymentStrategy).withRepaymentFrequencyTypeAsMonths()// + .withFirstRepaymentDate(firstRepaymentDate).withCollaterals(collaterals) + .build(clientID.toString(), loanProductID.toString(), null); + return this.loanTransactionHelper.getLoanId(loanApplicationJSON); + } + private Integer applyForLoanApplicationForInterestRecalculation(final Integer clientID, final Integer loanProductID, final String disbursementDate, final String repaymentStrategy, final List charges) { return applyForLoanApplicationForInterestRecalculation(clientID, loanProductID, disbursementDate, repaymentStrategy, charges, null, @@ -5721,6 +5798,136 @@ public class ClientLoanIntegrationTest { } + @Test + public void testLoanScheduleWithInterestRecalculationMakePrepaymentAfterRepayment() { + this.loanTransactionHelper = new LoanTransactionHelper(this.requestSpec, this.responseSpec); + DateFormat dateFormat = new SimpleDateFormat("dd MMMM yyyy", Locale.US); + dateFormat.setTimeZone(Utils.getTimeZoneOfTenant()); + GlobalConfigurationHelper.updateEnabledFlagForGlobalConfiguration(this.requestSpec, this.responseSpec, "42", true); + Calendar startDate = Calendar.getInstance(Utils.getTimeZoneOfTenant()); + Calendar currentDate = Calendar.getInstance(Utils.getTimeZoneOfTenant()); + startDate.add(Calendar.MONTH, -8); + + Calendar firstRepaymentDate = (Calendar) startDate.clone(); + firstRepaymentDate.add(Calendar.MONTH, 1); + firstRepaymentDate.add(Calendar.DAY_OF_MONTH, firstRepaymentDate.getActualMaximum(Calendar.DAY_OF_MONTH) - Calendar.DAY_OF_MONTH); + String firstRepayment = dateFormat.format(firstRepaymentDate.getTime()); + + final String loanDisbursementDate = dateFormat.format(startDate.getTime()); + final Integer clientID = ClientHelper.createClient(this.requestSpec, this.responseSpec); + ClientHelper.verifyClientCreatedOnServer(this.requestSpec, this.responseSpec, clientID); + final Integer loanProductID = createLoanProductWithInterestRecalculationAndCompoundingDetails( + LoanProductTestBuilder.INTEREST_PRINCIPAL_PENALTIES_FEES_ORDER_STRATEGY, + LoanProductTestBuilder.RECALCULATION_COMPOUNDING_METHOD_NONE, + LoanProductTestBuilder.RECALCULATION_STRATEGY_REDUCE_NUMBER_OF_INSTALLMENTS, + LoanProductTestBuilder.RECALCULATION_FREQUENCY_TYPE_SAME_AS_REPAYMENT_PERIOD, + LoanProductTestBuilder.INTEREST_APPLICABLE_STRATEGY_ON_PRE_CLOSE_DATE, null, "12"); + + final Integer loanID = applyForLoanApplicationForInterestRecalculation(clientID, loanProductID, loanDisbursementDate, + LoanApplicationTestBuilder.INTEREST_PRINCIPAL_PENALTIES_FEES_ORDER_STRATEGY, firstRepayment); + + Assertions.assertNotNull(loanID); + HashMap loanStatusHashMap = LoanStatusChecker.getStatusOfLoan(this.requestSpec, this.responseSpec, loanID); + LoanStatusChecker.verifyLoanIsPending(loanStatusHashMap); + + LOG.info("-----------------------------------APPROVE LOAN-----------------------------------------"); + loanStatusHashMap = this.loanTransactionHelper.approveLoan(loanDisbursementDate, loanID); + LoanStatusChecker.verifyLoanIsApproved(loanStatusHashMap); + LoanStatusChecker.verifyLoanIsWaitingForDisbursal(loanStatusHashMap); + + LOG.info("-------------------------------DISBURSE LOAN-------------------------------------------"); + String loanDetails = this.loanTransactionHelper.getLoanDetails(this.requestSpec, this.responseSpec, loanID); + loanStatusHashMap = this.loanTransactionHelper.disburseLoanWithNetDisbursalAmount(loanDisbursementDate, loanID, + JsonPath.from(loanDetails).get("netDisbursalAmount").toString()); + LoanStatusChecker.verifyLoanIsActive(loanStatusHashMap); + + ArrayList loanSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(this.requestSpec, this.responseSpec, loanID); + Assertions.assertNotNull(loanSchedule); + startDate.add(Calendar.DAY_OF_MONTH, 2); + String loanFirstRepaymentDate = dateFormat.format(startDate.getTime()); + // + Float earlyPayment = Float.parseFloat("3000"); + this.loanTransactionHelper.makeRepayment(loanFirstRepaymentDate, earlyPayment, loanID); + + HashMap prepayDetail = this.loanTransactionHelper.getPrepayAmount(this.requestSpec, this.responseSpec, loanID); + String prepayAmount = String.valueOf(prepayDetail.get("amount")); + String loanPrepaymentDate = dateFormat.format(currentDate.getTime()); + this.loanTransactionHelper.makeRepayment(loanPrepaymentDate, Float.parseFloat(prepayAmount), loanID); + loanStatusHashMap = LoanStatusChecker.getStatusOfLoan(this.requestSpec, this.responseSpec, loanID); + LoanStatusChecker.verifyLoanAccountIsClosed(loanStatusHashMap); + } + + @Test + public void testLoanScheduleWithInterestRecalculationMakeAdvancePaymentTillSettlement() { + this.loanTransactionHelper = new LoanTransactionHelper(this.requestSpec, this.responseSpec); + final ResponseSpecification errorResponse = new ResponseSpecBuilder().expectStatusCode(403).build(); + final LoanTransactionHelper validationErrorHelper = new LoanTransactionHelper(this.requestSpec, errorResponse); + DateFormat dateFormat = new SimpleDateFormat("dd MMMM yyyy", Locale.US); + dateFormat.setTimeZone(Utils.getTimeZoneOfTenant()); + GlobalConfigurationHelper.updateEnabledFlagForGlobalConfiguration(this.requestSpec, this.responseSpec, "42", true); + Calendar startDate = Calendar.getInstance(Utils.getTimeZoneOfTenant()); + Calendar currentDate = Calendar.getInstance(Utils.getTimeZoneOfTenant()); + startDate.add(Calendar.MONTH, -8); + + Calendar firstRepaymentDate = (Calendar) startDate.clone(); + firstRepaymentDate.add(Calendar.MONTH, 1); + firstRepaymentDate.add(Calendar.DAY_OF_MONTH, firstRepaymentDate.getActualMaximum(Calendar.DAY_OF_MONTH) - Calendar.DAY_OF_MONTH); + String firstRepayment = dateFormat.format(firstRepaymentDate.getTime()); + + final String loanDisbursementDate = dateFormat.format(startDate.getTime()); + final Integer clientID = ClientHelper.createClient(this.requestSpec, this.responseSpec); + ClientHelper.verifyClientCreatedOnServer(this.requestSpec, this.responseSpec, clientID); + final Integer loanProductID = createLoanProductWithInterestRecalculationAndCompoundingDetails( + LoanProductTestBuilder.INTEREST_PRINCIPAL_PENALTIES_FEES_ORDER_STRATEGY, + LoanProductTestBuilder.RECALCULATION_COMPOUNDING_METHOD_NONE, + LoanProductTestBuilder.RECALCULATION_STRATEGY_REDUCE_NUMBER_OF_INSTALLMENTS, + LoanProductTestBuilder.RECALCULATION_FREQUENCY_TYPE_SAME_AS_REPAYMENT_PERIOD, + LoanProductTestBuilder.INTEREST_APPLICABLE_STRATEGY_ON_PRE_CLOSE_DATE, null, "12"); + + final Integer loanID = applyForLoanApplicationForInterestRecalculation(clientID, loanProductID, loanDisbursementDate, + LoanApplicationTestBuilder.INTEREST_PRINCIPAL_PENALTIES_FEES_ORDER_STRATEGY, firstRepayment); + + Assertions.assertNotNull(loanID); + HashMap loanStatusHashMap = LoanStatusChecker.getStatusOfLoan(this.requestSpec, this.responseSpec, loanID); + LoanStatusChecker.verifyLoanIsPending(loanStatusHashMap); + + LOG.info("-----------------------------------APPROVE LOAN-----------------------------------------"); + loanStatusHashMap = this.loanTransactionHelper.approveLoan(loanDisbursementDate, loanID); + LoanStatusChecker.verifyLoanIsApproved(loanStatusHashMap); + LoanStatusChecker.verifyLoanIsWaitingForDisbursal(loanStatusHashMap); + + LOG.info("-------------------------------DISBURSE LOAN-------------------------------------------"); + String loanDetails = this.loanTransactionHelper.getLoanDetails(this.requestSpec, this.responseSpec, loanID); + loanStatusHashMap = this.loanTransactionHelper.disburseLoanWithNetDisbursalAmount(loanDisbursementDate, loanID, + JsonPath.from(loanDetails).get("netDisbursalAmount").toString()); + LoanStatusChecker.verifyLoanIsActive(loanStatusHashMap); + + ArrayList loanSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(this.requestSpec, this.responseSpec, loanID); + Assertions.assertNotNull(loanSchedule); + Calendar repaymentDate = (Calendar) firstRepaymentDate.clone(); + startDate.add(Calendar.DAY_OF_MONTH, 2); + String loanFirstRepaymentDate = dateFormat.format(startDate.getTime()); + // + Float earlyPayment = Float.parseFloat("3000"); + String retrieveDueDate = null; + Float amount = null; + this.loanTransactionHelper.makeRepayment(loanFirstRepaymentDate, earlyPayment, loanID); + for (int i = 1; i < loanSchedule.size(); i++) { + + retrieveDueDate = dateFormat.format(repaymentDate.getTime()); + amount = (Float) loanSchedule.get(i).get("principalOriginalDue") + (Float) loanSchedule.get(i).get("interestOriginalDue"); + if (currentDate.after(repaymentDate)) { + this.loanTransactionHelper.makeRepayment(retrieveDueDate, amount, loanID); + } else { + break; + } + repaymentDate.add(Calendar.MONTH, 1); + } + HashMap savingsAccountErrorData = validationErrorHelper.makeRepayment(retrieveDueDate, amount, loanID); + ArrayList error = (ArrayList) savingsAccountErrorData.get("errors"); + assertEquals("error.msg.loan.transaction.cannot.be.a.future.date", error.get(0).get("userMessageGlobalisationCode")); + } + @Test public void testCollateralDataIsAvailableWhenRequested() { // given diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/loans/LoanProductTestBuilder.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/loans/LoanProductTestBuilder.java index b11fa5f0b..39ec5f627 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/loans/LoanProductTestBuilder.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/loans/LoanProductTestBuilder.java @@ -131,6 +131,7 @@ public class LoanProductTestBuilder { private boolean syncExpectedWithDisbursementDate = false; private String fixedPrincipalPercentagePerInstallment; private String installmentAmountInMultiplesOf; + private boolean canDefineInstallmentAmount; public String build(final String chargeId) { final HashMap map = build(chargeId, null); @@ -185,7 +186,9 @@ public class LoanProductTestBuilder { map.put("overAppliedNumber", this.overAppliedNumber); } } - + if (this.canDefineInstallmentAmount) { + map.put("canDefineInstallmentAmount", this.canDefineInstallmentAmount); + } if (multiDisburseLoan) { map.put("multiDisburseLoan", this.multiDisburseLoan); map.put("maxTrancheCount", this.maxTrancheCount); @@ -473,6 +476,11 @@ public class LoanProductTestBuilder { return this; } + public LoanProductTestBuilder withDefineInstallmentAmount(final boolean canDefineInstallmentAmount) { + this.canDefineInstallmentAmount = canDefineInstallmentAmount; + return this; + } + public LoanProductTestBuilder currencyDetails(final String digitsAfterDecimal, final String inMultiplesOf) { this.digitsAfterDecimal = digitsAfterDecimal; this.inMultiplesOf = inMultiplesOf;