FINERACT-2017: Fix apply holidays to loans job
diff --git a/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/service/DateUtils.java b/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/service/DateUtils.java
index d706e8f..c8a9ccb 100644
--- a/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/service/DateUtils.java
+++ b/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/service/DateUtils.java
@@ -352,6 +352,24 @@
return dateTime == null ? null : dateTime.format(getDateTimeFormatter(format, locale));
}
+ /**
+ * Checks if a specific date falls within a given range (inclusive).
+ *
+ * @param targetDate
+ * the date to be checked
+ * @param startDate
+ * the start date of the range
+ * @param endDate
+ * the end date of the range
+ * @return true if targetDate is within range or equal to start/end dates, otherwise false
+ */
+ public static boolean isDateWithinRange(LocalDate targetDate, LocalDate startDate, LocalDate endDate) {
+ if (targetDate == null || startDate == null || endDate == null) {
+ throw new IllegalArgumentException("Dates must not be null");
+ }
+ return !targetDate.isBefore(startDate) && !targetDate.isAfter(endDate);
+ }
+
@NotNull
private static DateTimeFormatter getDateFormatter(String format, Locale locale) {
DateTimeFormatter formatter = DEFAULT_DATE_FORMATTER;
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 cf77e23..67bbeea 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
@@ -18,6 +18,8 @@
*/
package org.apache.fineract.portfolio.loanaccount.jobs.applyholidaystoloans;
+import static org.apache.fineract.infrastructure.core.service.DateUtils.isDateWithinRange;
+
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.Arrays;
@@ -129,13 +131,13 @@
loanRepaymentScheduleInstallment.updateFromDate(tmpFromDate);
}
- if (!DateUtils.isBefore(oldDueDate, holiday.getFromDate())) {
+ if (isDateWithinRange(oldDueDate, holiday.getFromDate(), holiday.getToDate())) {
// 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);
+ adjustedRescheduleToDate = scheduledDateGenerator.generateNextRepaymentDateWhenHolidayApply(adjustedRescheduleToDate,
+ loanApplicationTerms);
loanRepaymentScheduleInstallment.updateDueDate(adjustedRescheduleToDate);
}
tmpFromDate = loanRepaymentScheduleInstallment.getDueDate();
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/DefaultScheduledDateGenerator.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/DefaultScheduledDateGenerator.java
index 72e529a..5108bff 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/DefaultScheduledDateGenerator.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/DefaultScheduledDateGenerator.java
@@ -358,4 +358,42 @@
}
return adjustedDate;
}
+
+ public LocalDate generateNextRepaymentDateWhenHolidayApply(final LocalDate lastRepaymentDate,
+ final LoanApplicationTerms loanApplicationTerms) {
+ LocalDate seedDate;
+ String reccuringString;
+ Calendar currentCalendar = loanApplicationTerms.getLoanCalendar();
+ LocalDate dueRepaymentPeriodDate = lastRepaymentDate;
+ dueRepaymentPeriodDate = (LocalDate) CalendarUtils.adjustDate(dueRepaymentPeriodDate, loanApplicationTerms.getSeedDate(),
+ loanApplicationTerms.getRepaymentPeriodFrequencyType());
+ if (currentCalendar != null) {
+ // If we have currentCalendar object, this means there is a
+ // calendar associated with
+ // the loan, and we should use it in order to calculate next
+ // repayment
+
+ CalendarHistory calendarHistory = null;
+ CalendarHistoryDataWrapper calendarHistoryDataWrapper = loanApplicationTerms.getCalendarHistoryDataWrapper();
+ if (calendarHistoryDataWrapper != null) {
+ calendarHistory = loanApplicationTerms.getCalendarHistoryDataWrapper().getCalendarHistory(dueRepaymentPeriodDate);
+ }
+
+ // get the start date from the calendar history
+ if (calendarHistory == null) {
+ seedDate = currentCalendar.getStartDateLocalDate();
+ reccuringString = currentCalendar.getRecurrence();
+ } else {
+ seedDate = calendarHistory.getStartDate();
+ reccuringString = calendarHistory.getRecurrence();
+ }
+
+ dueRepaymentPeriodDate = CalendarUtils.getNextRepaymentMeetingDate(reccuringString, seedDate, lastRepaymentDate,
+ loanApplicationTerms.getRepaymentEvery(),
+ CalendarUtils.getMeetingFrequencyFromPeriodFrequencyType(loanApplicationTerms.getLoanTermPeriodFrequencyType()),
+ loanApplicationTerms.isSkipRepaymentOnFirstDayofMonth(), loanApplicationTerms.getNumberOfdays());
+ }
+
+ return dueRepaymentPeriodDate;
+ }
}
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 bb8d478..eb410a0 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
@@ -44,9 +44,11 @@
import java.util.ArrayList;
import java.util.Calendar;
import java.util.HashMap;
+import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
+import java.util.Objects;
import java.util.TimeZone;
import org.apache.fineract.client.models.BusinessDateRequest;
import org.apache.fineract.client.models.GetJournalEntriesTransactionIdResponse;
@@ -299,7 +301,7 @@
final Integer loanProductID = createLoanProduct(null);
Assertions.assertNotNull(loanProductID);
- final Integer loanID = applyForLoanApplication(clientID.toString(), loanProductID.toString(), null, "10 January 2013");
+ final Integer loanID = applyForLoanApplication(clientID.toString(), loanProductID.toString(), null, "01 January 2013");
Assertions.assertNotNull(loanID);
HashMap loanStatusHashMap = LoanStatusChecker.getStatusOfLoan(requestSpec, responseSpec, loanID);
@@ -329,21 +331,49 @@
if (!enabled) {
enabled = true;
- configId = GlobalConfigurationHelper.updateEnabledFlagForGlobalConfiguration(requestSpec, responseSpec, configId, enabled);
+ GlobalConfigurationHelper.updateEnabledFlagForGlobalConfiguration(requestSpec, responseSpec, configId, enabled);
}
holidayId = HolidayHelper.activateHolidays(requestSpec, responseSpec, holidayId.toString());
Assertions.assertNotNull(holidayId);
+ HashMap holidayData = HolidayHelper.getHolidayById(requestSpec, responseSpec, holidayId.toString());
+ ArrayList<Integer> repaymentsRescheduledDate = (ArrayList<Integer>) holidayData.get("repaymentsRescheduledTo");
+
+ // Loan Repayment Schedule Before Apply Holidays To Loans
+ LinkedHashMap repaymentScheduleHashMap = JsonPath.from(loanDetails).get("repaymentSchedule");
+ ArrayList<LinkedHashMap> periods = (ArrayList<LinkedHashMap>) repaymentScheduleHashMap.get("periods");
+
+ for (LinkedHashMap period : periods) {
+ ArrayList<Integer> fromDate = (ArrayList<Integer>) period.get("fromDate");
+ if (fromDate != null && Objects.equals(fromDate.get(1), repaymentsRescheduledDate.get(1))) {
+ Assertions.assertNotEquals(repaymentsRescheduledDate.get(2), fromDate.get(2),
+ "Verifying Repayment Rescheduled Day before Running Apply Holidays to Loans Scheduler Job");
+ }
+ }
+
String JobName = "Apply Holidays To Loans";
this.schedulerJobHelper.executeAndAwaitJob(JobName);
- HashMap holidayData = HolidayHelper.getHolidayById(requestSpec, responseSpec, holidayId.toString());
- ArrayList<Integer> repaymentsRescheduledDate = (ArrayList<Integer>) holidayData.get("repaymentsRescheduledTo");
+ // Loan Repayment Schedule After Apply Holidays To Loans
+ loanDetails = this.loanTransactionHelper.getLoanDetails(requestSpec, responseSpec, loanID);
+ repaymentScheduleHashMap = JsonPath.from(loanDetails).get("repaymentSchedule");
+ periods = (ArrayList<LinkedHashMap>) repaymentScheduleHashMap.get("periods");
+ ArrayList<Integer> dateToApplyHolidays = null;
- Assertions.assertEquals(repaymentsRescheduledDate, repaymentsRescheduledDate,
- "Verifying Repayment Rescheduled Date after Running Apply Holidays to Loans Scheduler Job");
+ for (LinkedHashMap period : periods) {
+ ArrayList<Integer> fromDate = (ArrayList<Integer>) period.get("fromDate");
+ if (fromDate != null && Objects.equals(fromDate.get(1), repaymentsRescheduledDate.get(1))) {
+ dateToApplyHolidays = fromDate;
+ }
+ }
+
+ Assertions.assertNotNull(dateToApplyHolidays);
+ Assertions.assertEquals(repaymentsRescheduledDate.get(0), dateToApplyHolidays.get(0),
+ "Verifying Repayment Rescheduled Year after Running Apply Holidays to Loans Scheduler Job");
+ Assertions.assertEquals(repaymentsRescheduledDate.get(2), dateToApplyHolidays.get(2),
+ "Verifying Repayment Rescheduled Day after Running Apply Holidays to Loans Scheduler Job");
}
@Test