From 50eefd2ee54463d085a8bc4b0cac4979f0bba2a2 Mon Sep 17 00:00:00 2001 From: 2vinodhkumar <2vinodhkumar@gmail.com> Date: Wed, 27 Dec 2023 17:01:37 +0530 Subject: [PATCH] FINERACT-2031: Fix reschedule to next repayment date. Schedule should shift all the remaining installments to next repayment date as per the loan frequency. FINERACT-2031: Fix reschedule to next repayment date. Schedule should shift all the remaining installments to next repayment date as per the loan frequency. FINERACT-2031: Fix reschedule to next repayment date. Schedule should shift all the remaining installments to next repayment date as per the loan frequency. FINERACT-2031: Fix reschedule to next repayment date. Schedule should shift all the remaining installments to next repayment date as per the loan frequency. FINERACT-2031: Fix reschedule to next repayment date. Schedule should shift all the remaining installments to next repayment date as per the loan frequency. --- .../ApplyHolidaysToLoansTasklet.java | 39 ++++- .../SchedulerJobsTestResults.java | 136 ++++++++++++++++++ .../common/HolidayHelper.java | 23 +++ 3 files changed, 197 insertions(+), 1 deletion(-) diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/jobs/applyholidaystoloans/ApplyHolidaysToLoansTasklet.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/jobs/applyholidaystoloans/ApplyHolidaysToLoansTasklet.java index 3c3d2bd5c..690004ada 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/jobs/applyholidaystoloans/ApplyHolidaysToLoansTasklet.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/jobs/applyholidaystoloans/ApplyHolidaysToLoansTasklet.java @@ -96,6 +96,7 @@ public class ApplyHolidaysToLoansTasklet implements Tasklet { public void applyHolidayToRepaymentScheduleDates(Loan loan, Holiday holiday) { LocalDate adjustedRescheduleToDate = null; + boolean isResheduleToNextRepaymentDate = holiday.getReScheduleType().isResheduleToNextRepaymentDate(); if (holiday.getReScheduleType().isResheduleToNextRepaymentDate()) { adjustedRescheduleToDate = getNextRepaymentDate(loan, holiday); } else { @@ -103,7 +104,11 @@ public class ApplyHolidaysToLoansTasklet implements Tasklet { } if (isRepaymentScheduleAdjustmentNeeded(adjustedRescheduleToDate)) { - adjustRepaymentSchedules(loan, holiday, adjustedRescheduleToDate); + if (isResheduleToNextRepaymentDate) { + adjustAllRepaymentSchedules(loan, holiday, adjustedRescheduleToDate); + } else { + adjustRepaymentSchedules(loan, holiday, adjustedRescheduleToDate); + } businessEventNotifierService.notifyPostBusinessEvent(new LoanRescheduledDueHolidayBusinessEvent(loan)); } } @@ -144,6 +149,38 @@ public class ApplyHolidaysToLoansTasklet implements Tasklet { } } + private void adjustAllRepaymentSchedules(Loan loan, Holiday holiday, LocalDate adjustedRescheduleToDate) { + final DefaultScheduledDateGenerator scheduledDateGenerator = new DefaultScheduledDateGenerator(); + ScheduleGeneratorDTO scheduleGeneratorDTO = loanUtilService.buildScheduleGeneratorDTO(loan, holiday.getFromDate()); + final LoanApplicationTerms loanApplicationTerms = loan.constructLoanApplicationTerms(scheduleGeneratorDTO); + + // first repayment's from date is same as disbursement date. + LocalDate tmpFromDate = loan.getDisbursementDate(); + + // Loop through all loanRepayments + List installments = loan.getRepaymentScheduleInstallments(); + for (final LoanRepaymentScheduleInstallment loanRepaymentScheduleInstallment : installments) { + final LocalDate oldDueDate = loanRepaymentScheduleInstallment.getDueDate(); + + // update from date if it's not same as previous installment's due + // date. + if (!DateUtils.isEqual(tmpFromDate, loanRepaymentScheduleInstallment.getFromDate())) { + loanRepaymentScheduleInstallment.updateFromDate(tmpFromDate); + } + + if (!DateUtils.isBefore(oldDueDate, holiday.getFromDate())) { + // FIXME: AA do we need to apply non-working days. + // Assuming holiday's repayment reschedule to date cannot be + // created on a non-working day. + + adjustedRescheduleToDate = scheduledDateGenerator.generateNextRepaymentDate(adjustedRescheduleToDate, loanApplicationTerms, + false); + loanRepaymentScheduleInstallment.updateDueDate(adjustedRescheduleToDate); + } + tmpFromDate = loanRepaymentScheduleInstallment.getDueDate(); + } + } + private LocalDate getNextRepaymentDate(Loan loan, Holiday holiday) { LocalDate adjustedRescheduleToDate = null; final LocalDate rescheduleToDate = holiday.getToDate(); diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/SchedulerJobsTestResults.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/SchedulerJobsTestResults.java index f8ec562bd..166019e81 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/SchedulerJobsTestResults.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/SchedulerJobsTestResults.java @@ -402,6 +402,142 @@ public class SchedulerJobsTestResults { Assertions.assertEquals(fromDateDayBefore, fromDateDayAfter, message); } + @Test + public void testApplyType1HolidaysToLoansJobOutcome() throws InterruptedException { + this.loanTransactionHelper = new LoanTransactionHelper(requestSpec, responseSpec); + + final Integer clientID = ClientHelper.createClient(requestSpec, responseSpec); + Assertions.assertNotNull(clientID); + + Integer holidayId = HolidayHelper.createTyoe1Holidays(requestSpec, responseSpec); + Assertions.assertNotNull(holidayId); + + final Integer loanProductID = createLoanProduct(null); + Assertions.assertNotNull(loanProductID); + + final Integer loanID = applyForLoanApplication(clientID.toString(), loanProductID.toString(), null, "04 January 2024"); + Assertions.assertNotNull(loanID); + + HashMap loanStatusHashMap = LoanStatusChecker.getStatusOfLoan(requestSpec, responseSpec, loanID); + LoanStatusChecker.verifyLoanIsPending(loanStatusHashMap); + + loanStatusHashMap = this.loanTransactionHelper.approveLoan("04 January 2024", loanID); + LoanStatusChecker.verifyLoanIsApproved(loanStatusHashMap); + + String loanDetails = this.loanTransactionHelper.getLoanDetails(requestSpec, responseSpec, loanID); + loanStatusHashMap = this.loanTransactionHelper.disburseLoanWithNetDisbursalAmount("04 January 2024", loanID, + JsonPath.from(loanDetails).get("netDisbursalAmount").toString()); + LoanStatusChecker.verifyLoanIsActive(loanStatusHashMap); + + // Retrieving All Global Configuration details + final ArrayList globalConfig = GlobalConfigurationHelper.getAllGlobalConfigurations(requestSpec, responseSpec); + Assertions.assertNotNull(globalConfig); + + // Updating Value for reschedule-repayments-on-holidays Global + // Configuration + Integer configId = (Integer) globalConfig.get(3).get("id"); + Assertions.assertNotNull(configId); + + HashMap configData = GlobalConfigurationHelper.getGlobalConfigurationById(requestSpec, responseSpec, configId.toString()); + Assertions.assertNotNull(configData); + + Boolean enabled = (Boolean) globalConfig.get(3).get("enabled"); + + if (!enabled) { + enabled = true; + GlobalConfigurationHelper.updateEnabledFlagForGlobalConfiguration(requestSpec, responseSpec, configId, enabled); + } + + holidayId = HolidayHelper.activateHolidays(requestSpec, responseSpec, holidayId.toString()); + Assertions.assertNotNull(holidayId); + + HashMap holidayData = HolidayHelper.getHolidayById(requestSpec, responseSpec, holidayId.toString()); + + LinkedHashMap repaymentScheduleHashMap = JsonPath.from(loanDetails).get("repaymentSchedule"); + ArrayList periods = (ArrayList) repaymentScheduleHashMap.get("periods"); + String JobName = "Apply Holidays To Loans"; + + this.schedulerJobHelper.executeAndAwaitJob(JobName); + + // Loan Repayment Schedule After Apply Holidays To Loans + loanDetails = this.loanTransactionHelper.getLoanDetails(requestSpec, responseSpec, loanID); + repaymentScheduleHashMap = JsonPath.from(loanDetails).get("repaymentSchedule"); + ArrayList periodsAfterRescheduleApplied = (ArrayList) repaymentScheduleHashMap.get("periods"); + + ArrayList fromDateValues = (ArrayList) periods.get(1).get("fromDate"); + LocalDate fromDate = LocalDate.of(fromDateValues.get(0), fromDateValues.get(1), fromDateValues.get(2)); + ArrayList dueDateValues = (ArrayList) periods.get(1).get("dueDate"); + LocalDate dueDate = LocalDate.of(dueDateValues.get(0), dueDateValues.get(1), dueDateValues.get(2)); + Assertions.assertEquals(LocalDate.of(2024, 1, 4), fromDate, + "Verifying Repayment Rescheduled Date before Running Apply Holidays to Loans Scheduler Job"); + Assertions.assertEquals(LocalDate.of(2024, 2, 4), dueDate, + "Verifying Repayment Rescheduled Date before Running Apply Holidays to Loans Scheduler Job"); + + fromDateValues = (ArrayList) periods.get(2).get("fromDate"); + fromDate = LocalDate.of(fromDateValues.get(0), fromDateValues.get(1), fromDateValues.get(2)); + dueDateValues = (ArrayList) periods.get(2).get("dueDate"); + dueDate = LocalDate.of(dueDateValues.get(0), dueDateValues.get(1), dueDateValues.get(2)); + Assertions.assertEquals(LocalDate.of(2024, 2, 4), fromDate, + "Verifying Repayment Rescheduled Date before Running Apply Holidays to Loans Scheduler Job"); + Assertions.assertEquals(LocalDate.of(2024, 3, 4), dueDate, + "Verifying Repayment Rescheduled Date before Running Apply Holidays to Loans Scheduler Job"); + + fromDateValues = (ArrayList) periods.get(3).get("fromDate"); + fromDate = LocalDate.of(fromDateValues.get(0), fromDateValues.get(1), fromDateValues.get(2)); + dueDateValues = (ArrayList) periods.get(3).get("dueDate"); + dueDate = LocalDate.of(dueDateValues.get(0), dueDateValues.get(1), dueDateValues.get(2)); + Assertions.assertEquals(LocalDate.of(2024, 3, 4), fromDate, + "Verifying Repayment Rescheduled Date before Running Apply Holidays to Loans Scheduler Job"); + Assertions.assertEquals(LocalDate.of(2024, 4, 4), dueDate, + "Verifying Repayment Rescheduled Date before Running Apply Holidays to Loans Scheduler Job"); + + fromDateValues = (ArrayList) periods.get(4).get("fromDate"); + fromDate = LocalDate.of(fromDateValues.get(0), fromDateValues.get(1), fromDateValues.get(2)); + dueDateValues = (ArrayList) periods.get(4).get("dueDate"); + dueDate = LocalDate.of(dueDateValues.get(0), dueDateValues.get(1), dueDateValues.get(2)); + Assertions.assertEquals(LocalDate.of(2024, 4, 4), fromDate, + "Verifying Repayment Rescheduled Date before Running Apply Holidays to Loans Scheduler Job"); + Assertions.assertEquals(LocalDate.of(2024, 5, 4), dueDate, + "Verifying Repayment Rescheduled Date before Running Apply Holidays to Loans Scheduler Job"); + + fromDateValues = (ArrayList) periodsAfterRescheduleApplied.get(1).get("fromDate"); + fromDate = LocalDate.of(fromDateValues.get(0), fromDateValues.get(1), fromDateValues.get(2)); + dueDateValues = (ArrayList) periodsAfterRescheduleApplied.get(1).get("dueDate"); + dueDate = LocalDate.of(dueDateValues.get(0), dueDateValues.get(1), dueDateValues.get(2)); + Assertions.assertEquals(LocalDate.of(2024, 1, 4), fromDate, + "Verifying Repayment Rescheduled Date after Running Apply Holidays to Loans Scheduler Job"); + Assertions.assertEquals(LocalDate.of(2024, 2, 4), dueDate, + "Verifying Repayment Rescheduled Date after Running Apply Holidays to Loans Scheduler Job"); + + fromDateValues = (ArrayList) periodsAfterRescheduleApplied.get(2).get("fromDate"); + fromDate = LocalDate.of(fromDateValues.get(0), fromDateValues.get(1), fromDateValues.get(2)); + dueDateValues = (ArrayList) periodsAfterRescheduleApplied.get(2).get("dueDate"); + dueDate = LocalDate.of(dueDateValues.get(0), dueDateValues.get(1), dueDateValues.get(2)); + Assertions.assertEquals(LocalDate.of(2024, 2, 4), fromDate, + "Verifying Repayment Rescheduled Date after Running Apply Holidays to Loans Scheduler Job"); + Assertions.assertEquals(LocalDate.of(2024, 3, 4), dueDate, + "Verifying Repayment Rescheduled Date after Running Apply Holidays to Loans Scheduler Job"); + + fromDateValues = (ArrayList) periodsAfterRescheduleApplied.get(3).get("fromDate"); + fromDate = LocalDate.of(fromDateValues.get(0), fromDateValues.get(1), fromDateValues.get(2)); + dueDateValues = (ArrayList) periodsAfterRescheduleApplied.get(3).get("dueDate"); + dueDate = LocalDate.of(dueDateValues.get(0), dueDateValues.get(1), dueDateValues.get(2)); + Assertions.assertEquals(LocalDate.of(2024, 3, 4), fromDate, + "Verifying Repayment Rescheduled Date after Running Apply Holidays to Loans Scheduler Job"); + Assertions.assertEquals(LocalDate.of(2024, 5, 4), dueDate, + "Verifying Repayment Rescheduled Date after Running Apply Holidays to Loans Scheduler Job"); + + fromDateValues = (ArrayList) periodsAfterRescheduleApplied.get(4).get("fromDate"); + fromDate = LocalDate.of(fromDateValues.get(0), fromDateValues.get(1), fromDateValues.get(2)); + dueDateValues = (ArrayList) periodsAfterRescheduleApplied.get(4).get("dueDate"); + dueDate = LocalDate.of(dueDateValues.get(0), dueDateValues.get(1), dueDateValues.get(2)); + Assertions.assertEquals(LocalDate.of(2024, 5, 4), fromDate, + "Verifying Repayment Rescheduled Date after Running Apply Holidays to Loans Scheduler Job"); + Assertions.assertEquals(LocalDate.of(2024, 6, 4), dueDate, + "Verifying Repayment Rescheduled Date after Running Apply Holidays to Loans Scheduler Job"); + + } + @Test public void testApplyDueFeeChargesForSavingsJobOutcome() throws InterruptedException { this.savingsAccountHelper = new SavingsAccountHelper(requestSpec, responseSpec); diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/HolidayHelper.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/HolidayHelper.java index 32f9a08e4..163057505 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/HolidayHelper.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/HolidayHelper.java @@ -64,6 +64,25 @@ public class HolidayHelper { return HolidayCreateJson; } + public static String getCreateType1HolidayDataAsJSON() { + final HashMap map = new HashMap<>(); + List> offices = new ArrayList>(); + HashMap officeMap = new HashMap<>(); + officeMap.put("officeId", OFFICE_ID); + offices.add(officeMap); + + map.put("offices", offices); + map.put("locale", "en"); + map.put("dateFormat", "dd MMMM yyyy"); + map.put("name", Utils.uniqueRandomStringGenerator("HOLIDAY_", 5)); + map.put("fromDate", "04 April 2024"); + map.put("toDate", "04 April 2024"); + map.put("reschedulingType", 1); + String HolidayCreateJson = new Gson().toJson(map); + LOG.info("{}", HolidayCreateJson); + return HolidayCreateJson; + } + public static String getActivateHolidayDataAsJSON() { final HashMap map = new HashMap<>(); String activateHoliday = new Gson().toJson(map); @@ -75,6 +94,10 @@ public class HolidayHelper { return Utils.performServerPost(requestSpec, responseSpec, CREATE_HOLIDAY_URL, getCreateHolidayDataAsJSON(), "resourceId"); } + public static Integer createTyoe1Holidays(final RequestSpecification requestSpec, final ResponseSpecification responseSpec) { + return Utils.performServerPost(requestSpec, responseSpec, CREATE_HOLIDAY_URL, getCreateType1HolidayDataAsJSON(), "resourceId"); + } + public static Integer activateHolidays(final RequestSpecification requestSpec, final ResponseSpecification responseSpec, final String holidayID) { final String ACTIVATE_HOLIDAY_URL = HOLIDAYS_URL + "/" + holidayID + "?command=activate&" + Utils.TENANT_IDENTIFIER;