From fc289c94e3bcd9104195f409af27f9c7305870cb Mon Sep 17 00:00:00 2001 From: Manoj <56669674+fynmanoj@users.noreply.github.com> Date: Thu, 25 Mar 2021 15:26:50 +0530 Subject: [PATCH] FINERACT-1316-fix-for-advance-payment-with-int-recalculation (#1676) --- ...RepaymentScheduleTransactionProcessor.java | 5 + .../domain/AbstractLoanScheduleGenerator.java | 3 + .../ClientLoanIntegrationTest.java | 223 ++++++++++++++++++ .../loans/LoanApplicationTestBuilder.java | 1 + .../common/loans/LoanProductTestBuilder.java | 1 + 5 files changed, 233 insertions(+) diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/InterestPrincipalPenaltyFeesOrderLoanRepaymentScheduleTransactionProcessor.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/InterestPrincipalPenaltyFeesOrderLoanRepaymentScheduleTransactionProcessor.java index dd25afc3d..aa75d446f 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/InterestPrincipalPenaltyFeesOrderLoanRepaymentScheduleTransactionProcessor.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/InterestPrincipalPenaltyFeesOrderLoanRepaymentScheduleTransactionProcessor.java @@ -163,4 +163,9 @@ public class InterestPrincipalPenaltyFeesOrderLoanRepaymentScheduleTransactionPr } return transactionAmountRemaining; } + + @Override + public boolean isInterestFirstRepaymentScheduleTransactionProcessor() { + return true; + } } 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 ae107e387..2f797411c 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 @@ -2237,6 +2237,9 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener do { actualRepaymentDate = this.scheduledDateGenerator.generateNextRepaymentDate(actualRepaymentDate, loanApplicationTerms, isFirstRepayment); + if (actualRepaymentDate.isAfter(rescheduleFrom)) { + actualRepaymentDate = lastInstallmentDate; + } isFirstRepayment = false; lastInstallmentDate = this.scheduledDateGenerator .adjustRepaymentDate(actualRepaymentDate, loanApplicationTerms, holidayDetailDTO).getChangedScheduleDate(); 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 2ffdf1e36..e42fa892b 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 @@ -5032,6 +5032,229 @@ public class ClientLoanIntegrationTest { } + @Test + public void testLoanScheduleWithInterestRecalculation_WITH_INTEREST_FIRST_STRATEGY_AND_REST_DAILY_INTEREST_COMPOUND_INTEREST_STRATEGY_REDUCE_NUMBER_OF_INSTALLMENTS() { + this.loanTransactionHelper = new LoanTransactionHelper(this.requestSpec, this.responseSpec); + + DateFormat dateFormat = new SimpleDateFormat("dd MMMM yyyy"); + dateFormat.setTimeZone(Utils.getTimeZoneOfTenant()); + + Calendar todaysDate = Calendar.getInstance(Utils.getTimeZoneOfTenant()); + todaysDate.add(Calendar.DAY_OF_MONTH, -14); + final String LOAN_DISBURSEMENT_DATE = dateFormat.format(todaysDate.getTime()); + Integer dayOfWeek = getDayOfWeek(todaysDate); + + 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_INTEREST, + LoanProductTestBuilder.RECALCULATION_STRATEGY_REDUCE_NUMBER_OF_INSTALLMENTS, + LoanProductTestBuilder.RECALCULATION_FREQUENCY_TYPE_DAILY, "1", LOAN_DISBURSEMENT_DATE, + LoanProductTestBuilder.RECALCULATION_FREQUENCY_TYPE_WEEKLY, "1", LOAN_DISBURSEMENT_DATE, + LoanProductTestBuilder.INTEREST_APPLICABLE_STRATEGY_ON_PRE_CLOSE_DATE, null, null, dayOfWeek, null, dayOfWeek); + + final Integer loanID = applyForLoanApplicationForInterestRecalculation(clientID, loanProductID, LOAN_DISBURSEMENT_DATE, + LoanApplicationTestBuilder.INTEREST_PRINCIPAL_PENALTIES_FEES_ORDER_STRATEGY, new ArrayList(0)); + + Assertions.assertNotNull(loanID); + HashMap loanStatusHashMap = LoanStatusChecker.getStatusOfLoan(this.requestSpec, this.responseSpec, loanID); + LoanStatusChecker.verifyLoanIsPending(loanStatusHashMap); + + ArrayList loanSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(this.requestSpec, this.responseSpec, loanID); + List> expectedvalues = new ArrayList<>(); + todaysDate = Calendar.getInstance(Utils.getTimeZoneOfTenant()); + addRepaymentValues(expectedvalues, todaysDate, -1, false, "2482.76", "46.15", "0.0", "0.0"); + addRepaymentValues(expectedvalues, todaysDate, 1, false, "2494.22", "34.69", "0.0", "0.0"); + addRepaymentValues(expectedvalues, todaysDate, 1, false, "2505.73", "23.18", "0.0", "0.0"); + addRepaymentValues(expectedvalues, todaysDate, 1, false, "2517.29", "11.62", "0.0", "0.0"); + + verifyLoanRepaymentSchedule(loanSchedule, expectedvalues); + + LOG.info("-----------------------------------APPROVE LOAN-----------------------------------------"); + loanStatusHashMap = this.loanTransactionHelper.approveLoan(LOAN_DISBURSEMENT_DATE, loanID); + LoanStatusChecker.verifyLoanIsApproved(loanStatusHashMap); + LoanStatusChecker.verifyLoanIsWaitingForDisbursal(loanStatusHashMap); + + LOG.info("-------------------------------DISBURSE LOAN-------------------------------------------"); + loanStatusHashMap = this.loanTransactionHelper.disburseLoan(LOAN_DISBURSEMENT_DATE, loanID); + LoanStatusChecker.verifyLoanIsActive(loanStatusHashMap); + + loanSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(this.requestSpec, this.responseSpec, loanID); + expectedvalues = new ArrayList<>(); + todaysDate = Calendar.getInstance(Utils.getTimeZoneOfTenant()); + addRepaymentValues(expectedvalues, todaysDate, -1, false, "2482.76", "46.15", "0.0", "0.0"); + addRepaymentValues(expectedvalues, todaysDate, 1, false, "2482.54", "46.37", "0.0", "0.0"); + addRepaymentValues(expectedvalues, todaysDate, 1, false, "2505.67", "23.24", "0.0", "0.0"); + addRepaymentValues(expectedvalues, todaysDate, 1, false, "2529.03", "11.67", "0.0", "0.0"); + + verifyLoanRepaymentSchedule(loanSchedule, expectedvalues); + + loanSchedule = this.loanTransactionHelper.getLoanFutureRepaymentSchedule(this.requestSpec, this.responseSpec, loanID); + expectedvalues = new ArrayList<>(); + todaysDate = Calendar.getInstance(Utils.getTimeZoneOfTenant()); + addRepaymentValues(expectedvalues, todaysDate, 0, false, "4965.3", "92.52", "0.0", "0.0"); + addRepaymentValues(expectedvalues, todaysDate, 1, false, "2505.67", "23.24", "0.0", "0.0"); + addRepaymentValues(expectedvalues, todaysDate, 1, false, "2529.03", "11.67", "0.0", "0.0"); + + verifyLoanRepaymentSchedule(loanSchedule, expectedvalues, 0); + + todaysDate = Calendar.getInstance(Utils.getTimeZoneOfTenant()); + todaysDate.add(Calendar.DAY_OF_MONTH, -7); + final String LOAN_FIRST_REPAYMENT_DATE = dateFormat.format(todaysDate.getTime()); + Float totalDueForCurrentPeriod = (Float) loanSchedule.get(1).get("totalDueForPeriod"); + this.loanTransactionHelper.makeRepayment(LOAN_FIRST_REPAYMENT_DATE, totalDueForCurrentPeriod, loanID); + + loanSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(this.requestSpec, this.responseSpec, loanID); + expectedvalues = new ArrayList<>(); + todaysDate = Calendar.getInstance(Utils.getTimeZoneOfTenant()); + addRepaymentValues(expectedvalues, todaysDate, -1, false, "2482.76", "46.15", "0.0", "0.0"); + addRepaymentValues(expectedvalues, todaysDate, 1, false, "2494.22", "34.69", "0.0", "0.0"); + addRepaymentValues(expectedvalues, todaysDate, 1, false, "2505.73", "23.18", "0.0", "0.0"); + addRepaymentValues(expectedvalues, todaysDate, 1, false, "2517.29", "11.62", "0.0", "0.0"); + verifyLoanRepaymentSchedule(loanSchedule, expectedvalues); + + Float earlyPayment = Float.parseFloat("4000"); + todaysDate = Calendar.getInstance(Utils.getTimeZoneOfTenant()); + todaysDate.add(Calendar.DAY_OF_MONTH, -5); + final String LOAN_SECOND_REPAYMENT_DATE = dateFormat.format(todaysDate.getTime()); + this.loanTransactionHelper.makeRepayment(LOAN_SECOND_REPAYMENT_DATE, earlyPayment, loanID); + loanSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(this.requestSpec, this.responseSpec, loanID); + expectedvalues = new ArrayList<>(); + todaysDate = Calendar.getInstance(Utils.getTimeZoneOfTenant()); + Calendar today = Calendar.getInstance(Utils.getTimeZoneOfTenant()); + Map paymentday = new HashMap<>(3); + paymentday.put("dueDate", getDateAsArray(today, -5, Calendar.DAY_OF_MONTH)); + paymentday.put("principalDue", "3990.09"); + paymentday.put("interestDue", "9.91"); + paymentday.put("feeChargesDue", "0"); + paymentday.put("penaltyChargesDue", "0"); + addRepaymentValues(expectedvalues, todaysDate, -1, false, "2482.76", "46.15", "0.0", "0.0"); + expectedvalues.add(paymentday); + addRepaymentValues(expectedvalues, todaysDate, 1, false, "2517.31", "11.6", "0.0", "0.0"); + addRepaymentValues(expectedvalues, todaysDate, 1, false, "1009.84", "4.66", "0.0", "0.0"); + verifyLoanRepaymentSchedule(loanSchedule, expectedvalues); + + HashMap prepayDetail = this.loanTransactionHelper.getPrepayAmount(this.requestSpec, this.responseSpec, loanID); + String prepayAmount = String.valueOf(prepayDetail.get("amount")); + todaysDate = Calendar.getInstance(Utils.getTimeZoneOfTenant()); + final String loanRepaymentDate = dateFormat.format(todaysDate.getTime()); + this.loanTransactionHelper.makeRepayment(loanRepaymentDate, Float.parseFloat(prepayAmount), loanID); + loanStatusHashMap = LoanStatusChecker.getStatusOfLoan(this.requestSpec, this.responseSpec, loanID); + LoanStatusChecker.verifyLoanAccountIsClosed(loanStatusHashMap); + + } + + @Test + public void testLoanScheduleWithInterestRecalculation_WITH_INTEREST_FIRST_STRATEGY_AND_REST_DAILY_INTEREST_COMPOUND_INTEREST_STRATEGY_REDUCE_NUMBER_OF_INSTALLMENTS_EARLY_REPAYMENT() { + this.loanTransactionHelper = new LoanTransactionHelper(this.requestSpec, this.responseSpec); + + DateFormat dateFormat = new SimpleDateFormat("dd MMMM yyyy"); + dateFormat.setTimeZone(Utils.getTimeZoneOfTenant()); + + Calendar todaysDate = Calendar.getInstance(Utils.getTimeZoneOfTenant()); + todaysDate.add(Calendar.DAY_OF_MONTH, -14); + final String LOAN_DISBURSEMENT_DATE = dateFormat.format(todaysDate.getTime()); + Integer dayOfWeek = getDayOfWeek(todaysDate); + + 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_INTEREST, + LoanProductTestBuilder.RECALCULATION_STRATEGY_REDUCE_NUMBER_OF_INSTALLMENTS, + LoanProductTestBuilder.RECALCULATION_FREQUENCY_TYPE_DAILY, "1", LOAN_DISBURSEMENT_DATE, + LoanProductTestBuilder.RECALCULATION_FREQUENCY_TYPE_WEEKLY, "1", LOAN_DISBURSEMENT_DATE, + LoanProductTestBuilder.INTEREST_APPLICABLE_STRATEGY_ON_PRE_CLOSE_DATE, null, null, dayOfWeek, null, dayOfWeek); + + final Integer loanID = applyForLoanApplicationForInterestRecalculation(clientID, loanProductID, LOAN_DISBURSEMENT_DATE, + LoanApplicationTestBuilder.INTEREST_PRINCIPAL_PENALTIES_FEES_ORDER_STRATEGY, new ArrayList(0)); + + Assertions.assertNotNull(loanID); + HashMap loanStatusHashMap = LoanStatusChecker.getStatusOfLoan(this.requestSpec, this.responseSpec, loanID); + LoanStatusChecker.verifyLoanIsPending(loanStatusHashMap); + + ArrayList loanSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(this.requestSpec, this.responseSpec, loanID); + List> expectedvalues = new ArrayList<>(); + todaysDate = Calendar.getInstance(Utils.getTimeZoneOfTenant()); + addRepaymentValues(expectedvalues, todaysDate, -1, false, "2482.76", "46.15", "0.0", "0.0"); + addRepaymentValues(expectedvalues, todaysDate, 1, false, "2494.22", "34.69", "0.0", "0.0"); + addRepaymentValues(expectedvalues, todaysDate, 1, false, "2505.73", "23.18", "0.0", "0.0"); + addRepaymentValues(expectedvalues, todaysDate, 1, false, "2517.29", "11.62", "0.0", "0.0"); + + verifyLoanRepaymentSchedule(loanSchedule, expectedvalues); + + LOG.info("-----------------------------------APPROVE LOAN-----------------------------------------"); + loanStatusHashMap = this.loanTransactionHelper.approveLoan(LOAN_DISBURSEMENT_DATE, loanID); + LoanStatusChecker.verifyLoanIsApproved(loanStatusHashMap); + LoanStatusChecker.verifyLoanIsWaitingForDisbursal(loanStatusHashMap); + + LOG.info("-------------------------------DISBURSE LOAN-------------------------------------------"); + loanStatusHashMap = this.loanTransactionHelper.disburseLoan(LOAN_DISBURSEMENT_DATE, loanID); + LoanStatusChecker.verifyLoanIsActive(loanStatusHashMap); + + loanSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(this.requestSpec, this.responseSpec, loanID); + expectedvalues = new ArrayList<>(); + todaysDate = Calendar.getInstance(Utils.getTimeZoneOfTenant()); + addRepaymentValues(expectedvalues, todaysDate, -1, false, "2482.76", "46.15", "0.0", "0.0"); + addRepaymentValues(expectedvalues, todaysDate, 1, false, "2482.54", "46.37", "0.0", "0.0"); + addRepaymentValues(expectedvalues, todaysDate, 1, false, "2505.67", "23.24", "0.0", "0.0"); + addRepaymentValues(expectedvalues, todaysDate, 1, false, "2529.03", "11.67", "0.0", "0.0"); + + verifyLoanRepaymentSchedule(loanSchedule, expectedvalues); + + loanSchedule = this.loanTransactionHelper.getLoanFutureRepaymentSchedule(this.requestSpec, this.responseSpec, loanID); + expectedvalues = new ArrayList<>(); + todaysDate = Calendar.getInstance(Utils.getTimeZoneOfTenant()); + addRepaymentValues(expectedvalues, todaysDate, 0, false, "4965.3", "92.52", "0.0", "0.0"); + addRepaymentValues(expectedvalues, todaysDate, 1, false, "2505.67", "23.24", "0.0", "0.0"); + addRepaymentValues(expectedvalues, todaysDate, 1, false, "2529.03", "11.67", "0.0", "0.0"); + + verifyLoanRepaymentSchedule(loanSchedule, expectedvalues, 0); + + todaysDate = Calendar.getInstance(Utils.getTimeZoneOfTenant()); + todaysDate.add(Calendar.DAY_OF_MONTH, -7); + final String LOAN_FIRST_REPAYMENT_DATE = dateFormat.format(todaysDate.getTime()); + Float totalDueForCurrentPeriod = (Float) loanSchedule.get(1).get("totalDueForPeriod"); + this.loanTransactionHelper.makeRepayment(LOAN_FIRST_REPAYMENT_DATE, totalDueForCurrentPeriod, loanID); + + loanSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(this.requestSpec, this.responseSpec, loanID); + expectedvalues = new ArrayList<>(); + todaysDate = Calendar.getInstance(Utils.getTimeZoneOfTenant()); + addRepaymentValues(expectedvalues, todaysDate, -1, false, "2482.76", "46.15", "0.0", "0.0"); + addRepaymentValues(expectedvalues, todaysDate, 1, false, "2494.22", "34.69", "0.0", "0.0"); + addRepaymentValues(expectedvalues, todaysDate, 1, false, "2505.73", "23.18", "0.0", "0.0"); + addRepaymentValues(expectedvalues, todaysDate, 1, false, "2517.29", "11.62", "0.0", "0.0"); + verifyLoanRepaymentSchedule(loanSchedule, expectedvalues); + + // early repayment - pay exact due amount 2 days before due date + Float earlyPayment = Float.parseFloat("2528.91"); + todaysDate = Calendar.getInstance(Utils.getTimeZoneOfTenant()); + todaysDate.add(Calendar.DAY_OF_MONTH, -2); + final String LOAN_SECOND_REPAYMENT_DATE = dateFormat.format(todaysDate.getTime()); + this.loanTransactionHelper.makeRepayment(LOAN_SECOND_REPAYMENT_DATE, earlyPayment, loanID); + loanSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(this.requestSpec, this.responseSpec, loanID); + expectedvalues = new ArrayList<>(); + todaysDate = Calendar.getInstance(Utils.getTimeZoneOfTenant()); + Calendar today = Calendar.getInstance(Utils.getTimeZoneOfTenant()); + addRepaymentValues(expectedvalues, todaysDate, -1, false, "2482.76", "46.15", "0.0", "0.0"); + // early-repayment + addRepaymentValues(expectedvalues, todaysDate, 5, true, "2504.13", "24.78", "0.0", "0.0"); + + addRepaymentValues(expectedvalues, todaysDate, 2, true, "2522.33", "6.58", "0.0", "0.0"); + addRepaymentValues(expectedvalues, todaysDate, 1, false, "2490.78", "11.5", "0.0", "0.0"); + verifyLoanRepaymentSchedule(loanSchedule, expectedvalues); + + HashMap prepayDetail = this.loanTransactionHelper.getPrepayAmount(this.requestSpec, this.responseSpec, loanID); + String prepayAmount = String.valueOf(prepayDetail.get("amount")); + todaysDate = Calendar.getInstance(Utils.getTimeZoneOfTenant()); + final String loanRepaymentDate = dateFormat.format(todaysDate.getTime()); + this.loanTransactionHelper.makeRepayment(loanRepaymentDate, Float.parseFloat(prepayAmount), loanID); + loanStatusHashMap = LoanStatusChecker.getStatusOfLoan(this.requestSpec, this.responseSpec, loanID); + LoanStatusChecker.verifyLoanAccountIsClosed(loanStatusHashMap); + + } + private void validateIfValuesAreNotOverridden(Integer loanID, Integer loanProductID) { String loanProductDetails = this.loanTransactionHelper.getLoanProductDetails(this.requestSpec, this.responseSpec, loanProductID); String loanDetails = this.loanTransactionHelper.getLoanDetails(this.requestSpec, this.responseSpec, loanID); diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/loans/LoanApplicationTestBuilder.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/loans/LoanApplicationTestBuilder.java index 72e4c43ba..3eb5d120c 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/loans/LoanApplicationTestBuilder.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/loans/LoanApplicationTestBuilder.java @@ -41,6 +41,7 @@ public class LoanApplicationTestBuilder { private static final String LOCALE = "en_GB"; public static final String DEFAULT_STRATEGY = "1"; public static final String RBI_INDIA_STRATEGY = "4"; + public static final String INTEREST_PRINCIPAL_PENALTIES_FEES_ORDER_STRATEGY = "6"; private String principal = "10,000"; private String glimPrincipal = "1000"; 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 f0cc62638..df0c593cd 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 @@ -41,6 +41,7 @@ public class LoanProductTestBuilder { private static final String DECLINING_BALANCE = "0"; private static final String FLAT_BALANCE = "1"; public static final String DEFAULT_STRATEGY = "1"; + public static final String INTEREST_PRINCIPAL_PENALTIES_FEES_ORDER_STRATEGY = "6"; // private static final String HEAVENS_FAMILY_STRATEGY ="2"; // private static final String CREO_CORE_STRATEGY ="3"; public static final String RBI_INDIA_STRATEGY = "4";