Skip to content

Commit 50eefd2

Browse files
2vinodhkumaradamsaghy
authored andcommitted
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.
1 parent e27a0bb commit 50eefd2

3 files changed

Lines changed: 197 additions & 1 deletion

File tree

fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/jobs/applyholidaystoloans/ApplyHolidaysToLoansTasklet.java

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,14 +96,19 @@ public RepeatStatus execute(StepContribution contribution, ChunkContext chunkCon
9696

9797
public void applyHolidayToRepaymentScheduleDates(Loan loan, Holiday holiday) {
9898
LocalDate adjustedRescheduleToDate = null;
99+
boolean isResheduleToNextRepaymentDate = holiday.getReScheduleType().isResheduleToNextRepaymentDate();
99100
if (holiday.getReScheduleType().isResheduleToNextRepaymentDate()) {
100101
adjustedRescheduleToDate = getNextRepaymentDate(loan, holiday);
101102
} else {
102103
adjustedRescheduleToDate = holiday.getRepaymentsRescheduledTo();
103104
}
104105

105106
if (isRepaymentScheduleAdjustmentNeeded(adjustedRescheduleToDate)) {
106-
adjustRepaymentSchedules(loan, holiday, adjustedRescheduleToDate);
107+
if (isResheduleToNextRepaymentDate) {
108+
adjustAllRepaymentSchedules(loan, holiday, adjustedRescheduleToDate);
109+
} else {
110+
adjustRepaymentSchedules(loan, holiday, adjustedRescheduleToDate);
111+
}
107112
businessEventNotifierService.notifyPostBusinessEvent(new LoanRescheduledDueHolidayBusinessEvent(loan));
108113
}
109114
}
@@ -144,6 +149,38 @@ private void adjustRepaymentSchedules(Loan loan, Holiday holiday, LocalDate adju
144149
}
145150
}
146151

152+
private void adjustAllRepaymentSchedules(Loan loan, Holiday holiday, LocalDate adjustedRescheduleToDate) {
153+
final DefaultScheduledDateGenerator scheduledDateGenerator = new DefaultScheduledDateGenerator();
154+
ScheduleGeneratorDTO scheduleGeneratorDTO = loanUtilService.buildScheduleGeneratorDTO(loan, holiday.getFromDate());
155+
final LoanApplicationTerms loanApplicationTerms = loan.constructLoanApplicationTerms(scheduleGeneratorDTO);
156+
157+
// first repayment's from date is same as disbursement date.
158+
LocalDate tmpFromDate = loan.getDisbursementDate();
159+
160+
// Loop through all loanRepayments
161+
List<LoanRepaymentScheduleInstallment> installments = loan.getRepaymentScheduleInstallments();
162+
for (final LoanRepaymentScheduleInstallment loanRepaymentScheduleInstallment : installments) {
163+
final LocalDate oldDueDate = loanRepaymentScheduleInstallment.getDueDate();
164+
165+
// update from date if it's not same as previous installment's due
166+
// date.
167+
if (!DateUtils.isEqual(tmpFromDate, loanRepaymentScheduleInstallment.getFromDate())) {
168+
loanRepaymentScheduleInstallment.updateFromDate(tmpFromDate);
169+
}
170+
171+
if (!DateUtils.isBefore(oldDueDate, holiday.getFromDate())) {
172+
// FIXME: AA do we need to apply non-working days.
173+
// Assuming holiday's repayment reschedule to date cannot be
174+
// created on a non-working day.
175+
176+
adjustedRescheduleToDate = scheduledDateGenerator.generateNextRepaymentDate(adjustedRescheduleToDate, loanApplicationTerms,
177+
false);
178+
loanRepaymentScheduleInstallment.updateDueDate(adjustedRescheduleToDate);
179+
}
180+
tmpFromDate = loanRepaymentScheduleInstallment.getDueDate();
181+
}
182+
}
183+
147184
private LocalDate getNextRepaymentDate(Loan loan, Holiday holiday) {
148185
LocalDate adjustedRescheduleToDate = null;
149186
final LocalDate rescheduleToDate = holiday.getToDate();

integration-tests/src/test/java/org/apache/fineract/integrationtests/SchedulerJobsTestResults.java

Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -402,6 +402,142 @@ private void assertEqualDay(ArrayList<Integer> fromDateBefore, ArrayList<Integer
402402
Assertions.assertEquals(fromDateDayBefore, fromDateDayAfter, message);
403403
}
404404

405+
@Test
406+
public void testApplyType1HolidaysToLoansJobOutcome() throws InterruptedException {
407+
this.loanTransactionHelper = new LoanTransactionHelper(requestSpec, responseSpec);
408+
409+
final Integer clientID = ClientHelper.createClient(requestSpec, responseSpec);
410+
Assertions.assertNotNull(clientID);
411+
412+
Integer holidayId = HolidayHelper.createTyoe1Holidays(requestSpec, responseSpec);
413+
Assertions.assertNotNull(holidayId);
414+
415+
final Integer loanProductID = createLoanProduct(null);
416+
Assertions.assertNotNull(loanProductID);
417+
418+
final Integer loanID = applyForLoanApplication(clientID.toString(), loanProductID.toString(), null, "04 January 2024");
419+
Assertions.assertNotNull(loanID);
420+
421+
HashMap loanStatusHashMap = LoanStatusChecker.getStatusOfLoan(requestSpec, responseSpec, loanID);
422+
LoanStatusChecker.verifyLoanIsPending(loanStatusHashMap);
423+
424+
loanStatusHashMap = this.loanTransactionHelper.approveLoan("04 January 2024", loanID);
425+
LoanStatusChecker.verifyLoanIsApproved(loanStatusHashMap);
426+
427+
String loanDetails = this.loanTransactionHelper.getLoanDetails(requestSpec, responseSpec, loanID);
428+
loanStatusHashMap = this.loanTransactionHelper.disburseLoanWithNetDisbursalAmount("04 January 2024", loanID,
429+
JsonPath.from(loanDetails).get("netDisbursalAmount").toString());
430+
LoanStatusChecker.verifyLoanIsActive(loanStatusHashMap);
431+
432+
// Retrieving All Global Configuration details
433+
final ArrayList<HashMap> globalConfig = GlobalConfigurationHelper.getAllGlobalConfigurations(requestSpec, responseSpec);
434+
Assertions.assertNotNull(globalConfig);
435+
436+
// Updating Value for reschedule-repayments-on-holidays Global
437+
// Configuration
438+
Integer configId = (Integer) globalConfig.get(3).get("id");
439+
Assertions.assertNotNull(configId);
440+
441+
HashMap configData = GlobalConfigurationHelper.getGlobalConfigurationById(requestSpec, responseSpec, configId.toString());
442+
Assertions.assertNotNull(configData);
443+
444+
Boolean enabled = (Boolean) globalConfig.get(3).get("enabled");
445+
446+
if (!enabled) {
447+
enabled = true;
448+
GlobalConfigurationHelper.updateEnabledFlagForGlobalConfiguration(requestSpec, responseSpec, configId, enabled);
449+
}
450+
451+
holidayId = HolidayHelper.activateHolidays(requestSpec, responseSpec, holidayId.toString());
452+
Assertions.assertNotNull(holidayId);
453+
454+
HashMap holidayData = HolidayHelper.getHolidayById(requestSpec, responseSpec, holidayId.toString());
455+
456+
LinkedHashMap repaymentScheduleHashMap = JsonPath.from(loanDetails).get("repaymentSchedule");
457+
ArrayList<LinkedHashMap> periods = (ArrayList<LinkedHashMap>) repaymentScheduleHashMap.get("periods");
458+
String JobName = "Apply Holidays To Loans";
459+
460+
this.schedulerJobHelper.executeAndAwaitJob(JobName);
461+
462+
// Loan Repayment Schedule After Apply Holidays To Loans
463+
loanDetails = this.loanTransactionHelper.getLoanDetails(requestSpec, responseSpec, loanID);
464+
repaymentScheduleHashMap = JsonPath.from(loanDetails).get("repaymentSchedule");
465+
ArrayList<LinkedHashMap> periodsAfterRescheduleApplied = (ArrayList<LinkedHashMap>) repaymentScheduleHashMap.get("periods");
466+
467+
ArrayList<Integer> fromDateValues = (ArrayList<Integer>) periods.get(1).get("fromDate");
468+
LocalDate fromDate = LocalDate.of(fromDateValues.get(0), fromDateValues.get(1), fromDateValues.get(2));
469+
ArrayList<Integer> dueDateValues = (ArrayList<Integer>) periods.get(1).get("dueDate");
470+
LocalDate dueDate = LocalDate.of(dueDateValues.get(0), dueDateValues.get(1), dueDateValues.get(2));
471+
Assertions.assertEquals(LocalDate.of(2024, 1, 4), fromDate,
472+
"Verifying Repayment Rescheduled Date before Running Apply Holidays to Loans Scheduler Job");
473+
Assertions.assertEquals(LocalDate.of(2024, 2, 4), dueDate,
474+
"Verifying Repayment Rescheduled Date before Running Apply Holidays to Loans Scheduler Job");
475+
476+
fromDateValues = (ArrayList<Integer>) periods.get(2).get("fromDate");
477+
fromDate = LocalDate.of(fromDateValues.get(0), fromDateValues.get(1), fromDateValues.get(2));
478+
dueDateValues = (ArrayList<Integer>) periods.get(2).get("dueDate");
479+
dueDate = LocalDate.of(dueDateValues.get(0), dueDateValues.get(1), dueDateValues.get(2));
480+
Assertions.assertEquals(LocalDate.of(2024, 2, 4), fromDate,
481+
"Verifying Repayment Rescheduled Date before Running Apply Holidays to Loans Scheduler Job");
482+
Assertions.assertEquals(LocalDate.of(2024, 3, 4), dueDate,
483+
"Verifying Repayment Rescheduled Date before Running Apply Holidays to Loans Scheduler Job");
484+
485+
fromDateValues = (ArrayList<Integer>) periods.get(3).get("fromDate");
486+
fromDate = LocalDate.of(fromDateValues.get(0), fromDateValues.get(1), fromDateValues.get(2));
487+
dueDateValues = (ArrayList<Integer>) periods.get(3).get("dueDate");
488+
dueDate = LocalDate.of(dueDateValues.get(0), dueDateValues.get(1), dueDateValues.get(2));
489+
Assertions.assertEquals(LocalDate.of(2024, 3, 4), fromDate,
490+
"Verifying Repayment Rescheduled Date before Running Apply Holidays to Loans Scheduler Job");
491+
Assertions.assertEquals(LocalDate.of(2024, 4, 4), dueDate,
492+
"Verifying Repayment Rescheduled Date before Running Apply Holidays to Loans Scheduler Job");
493+
494+
fromDateValues = (ArrayList<Integer>) periods.get(4).get("fromDate");
495+
fromDate = LocalDate.of(fromDateValues.get(0), fromDateValues.get(1), fromDateValues.get(2));
496+
dueDateValues = (ArrayList<Integer>) periods.get(4).get("dueDate");
497+
dueDate = LocalDate.of(dueDateValues.get(0), dueDateValues.get(1), dueDateValues.get(2));
498+
Assertions.assertEquals(LocalDate.of(2024, 4, 4), fromDate,
499+
"Verifying Repayment Rescheduled Date before Running Apply Holidays to Loans Scheduler Job");
500+
Assertions.assertEquals(LocalDate.of(2024, 5, 4), dueDate,
501+
"Verifying Repayment Rescheduled Date before Running Apply Holidays to Loans Scheduler Job");
502+
503+
fromDateValues = (ArrayList<Integer>) periodsAfterRescheduleApplied.get(1).get("fromDate");
504+
fromDate = LocalDate.of(fromDateValues.get(0), fromDateValues.get(1), fromDateValues.get(2));
505+
dueDateValues = (ArrayList<Integer>) periodsAfterRescheduleApplied.get(1).get("dueDate");
506+
dueDate = LocalDate.of(dueDateValues.get(0), dueDateValues.get(1), dueDateValues.get(2));
507+
Assertions.assertEquals(LocalDate.of(2024, 1, 4), fromDate,
508+
"Verifying Repayment Rescheduled Date after Running Apply Holidays to Loans Scheduler Job");
509+
Assertions.assertEquals(LocalDate.of(2024, 2, 4), dueDate,
510+
"Verifying Repayment Rescheduled Date after Running Apply Holidays to Loans Scheduler Job");
511+
512+
fromDateValues = (ArrayList<Integer>) periodsAfterRescheduleApplied.get(2).get("fromDate");
513+
fromDate = LocalDate.of(fromDateValues.get(0), fromDateValues.get(1), fromDateValues.get(2));
514+
dueDateValues = (ArrayList<Integer>) periodsAfterRescheduleApplied.get(2).get("dueDate");
515+
dueDate = LocalDate.of(dueDateValues.get(0), dueDateValues.get(1), dueDateValues.get(2));
516+
Assertions.assertEquals(LocalDate.of(2024, 2, 4), fromDate,
517+
"Verifying Repayment Rescheduled Date after Running Apply Holidays to Loans Scheduler Job");
518+
Assertions.assertEquals(LocalDate.of(2024, 3, 4), dueDate,
519+
"Verifying Repayment Rescheduled Date after Running Apply Holidays to Loans Scheduler Job");
520+
521+
fromDateValues = (ArrayList<Integer>) periodsAfterRescheduleApplied.get(3).get("fromDate");
522+
fromDate = LocalDate.of(fromDateValues.get(0), fromDateValues.get(1), fromDateValues.get(2));
523+
dueDateValues = (ArrayList<Integer>) periodsAfterRescheduleApplied.get(3).get("dueDate");
524+
dueDate = LocalDate.of(dueDateValues.get(0), dueDateValues.get(1), dueDateValues.get(2));
525+
Assertions.assertEquals(LocalDate.of(2024, 3, 4), fromDate,
526+
"Verifying Repayment Rescheduled Date after Running Apply Holidays to Loans Scheduler Job");
527+
Assertions.assertEquals(LocalDate.of(2024, 5, 4), dueDate,
528+
"Verifying Repayment Rescheduled Date after Running Apply Holidays to Loans Scheduler Job");
529+
530+
fromDateValues = (ArrayList<Integer>) periodsAfterRescheduleApplied.get(4).get("fromDate");
531+
fromDate = LocalDate.of(fromDateValues.get(0), fromDateValues.get(1), fromDateValues.get(2));
532+
dueDateValues = (ArrayList<Integer>) periodsAfterRescheduleApplied.get(4).get("dueDate");
533+
dueDate = LocalDate.of(dueDateValues.get(0), dueDateValues.get(1), dueDateValues.get(2));
534+
Assertions.assertEquals(LocalDate.of(2024, 5, 4), fromDate,
535+
"Verifying Repayment Rescheduled Date after Running Apply Holidays to Loans Scheduler Job");
536+
Assertions.assertEquals(LocalDate.of(2024, 6, 4), dueDate,
537+
"Verifying Repayment Rescheduled Date after Running Apply Holidays to Loans Scheduler Job");
538+
539+
}
540+
405541
@Test
406542
public void testApplyDueFeeChargesForSavingsJobOutcome() throws InterruptedException {
407543
this.savingsAccountHelper = new SavingsAccountHelper(requestSpec, responseSpec);

integration-tests/src/test/java/org/apache/fineract/integrationtests/common/HolidayHelper.java

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,25 @@ public static String getCreateHolidayDataAsJSON() {
6464
return HolidayCreateJson;
6565
}
6666

67+
public static String getCreateType1HolidayDataAsJSON() {
68+
final HashMap<String, Object> map = new HashMap<>();
69+
List<HashMap<String, String>> offices = new ArrayList<HashMap<String, String>>();
70+
HashMap<String, String> officeMap = new HashMap<>();
71+
officeMap.put("officeId", OFFICE_ID);
72+
offices.add(officeMap);
73+
74+
map.put("offices", offices);
75+
map.put("locale", "en");
76+
map.put("dateFormat", "dd MMMM yyyy");
77+
map.put("name", Utils.uniqueRandomStringGenerator("HOLIDAY_", 5));
78+
map.put("fromDate", "04 April 2024");
79+
map.put("toDate", "04 April 2024");
80+
map.put("reschedulingType", 1);
81+
String HolidayCreateJson = new Gson().toJson(map);
82+
LOG.info("{}", HolidayCreateJson);
83+
return HolidayCreateJson;
84+
}
85+
6786
public static String getActivateHolidayDataAsJSON() {
6887
final HashMap<String, String> map = new HashMap<>();
6988
String activateHoliday = new Gson().toJson(map);
@@ -75,6 +94,10 @@ public static Integer createHolidays(final RequestSpecification requestSpec, fin
7594
return Utils.performServerPost(requestSpec, responseSpec, CREATE_HOLIDAY_URL, getCreateHolidayDataAsJSON(), "resourceId");
7695
}
7796

97+
public static Integer createTyoe1Holidays(final RequestSpecification requestSpec, final ResponseSpecification responseSpec) {
98+
return Utils.performServerPost(requestSpec, responseSpec, CREATE_HOLIDAY_URL, getCreateType1HolidayDataAsJSON(), "resourceId");
99+
}
100+
78101
public static Integer activateHolidays(final RequestSpecification requestSpec, final ResponseSpecification responseSpec,
79102
final String holidayID) {
80103
final String ACTIVATE_HOLIDAY_URL = HOLIDAYS_URL + "/" + holidayID + "?command=activate&" + Utils.TENANT_IDENTIFIER;

0 commit comments

Comments
 (0)