blob: 0a8ac12148e591e0a8488e76e1854de0a4e03715 [file] [log] [blame]
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.portfolio.loanaccount.loanschedule.domain;
import org.apache.fineract.organisation.holiday.service.HolidayUtil;
import org.apache.fineract.organisation.workingdays.domain.RepaymentRescheduleType;
import org.apache.fineract.organisation.workingdays.service.WorkingDaysUtil;
import org.apache.fineract.portfolio.calendar.domain.Calendar;
import org.apache.fineract.portfolio.calendar.service.CalendarUtils;
import org.apache.fineract.portfolio.common.domain.DayOfWeekType;
import org.apache.fineract.portfolio.common.domain.PeriodFrequencyType;
import org.apache.fineract.portfolio.loanaccount.data.HolidayDetailDTO;
import org.joda.time.Days;
import org.joda.time.LocalDate;
import org.joda.time.Months;
import org.joda.time.Weeks;
import org.joda.time.Years;
public class DefaultScheduledDateGenerator implements ScheduledDateGenerator {
@Override
public LocalDate getLastRepaymentDate(final LoanApplicationTerms loanApplicationTerms, final HolidayDetailDTO holidayDetailDTO) {
final int numberOfRepayments = loanApplicationTerms.getNumberOfRepayments();
LocalDate lastRepaymentDate = loanApplicationTerms.getExpectedDisbursementDate();
boolean isFirstRepayment = true;
for (int repaymentPeriod = 1; repaymentPeriod <= numberOfRepayments; repaymentPeriod++) {
lastRepaymentDate = generateNextRepaymentDate(lastRepaymentDate, loanApplicationTerms, isFirstRepayment, holidayDetailDTO);
isFirstRepayment = false;
}
lastRepaymentDate = adjustRepaymentDate(lastRepaymentDate, loanApplicationTerms, holidayDetailDTO);
return lastRepaymentDate;
}
@Override
public LocalDate generateNextRepaymentDate(final LocalDate lastRepaymentDate, final LoanApplicationTerms loanApplicationTerms,
boolean isFirstRepayment, final HolidayDetailDTO holidayDetailDTO) {
final LocalDate firstRepaymentPeriodDate = loanApplicationTerms.getCalculatedRepaymentsStartingFromLocalDate();
LocalDate dueRepaymentPeriodDate = null;
if (isFirstRepayment && firstRepaymentPeriodDate != null) {
dueRepaymentPeriodDate = firstRepaymentPeriodDate;
} else {
Calendar currentCalendar = loanApplicationTerms.getLoanCalendar();
dueRepaymentPeriodDate = getRepaymentPeriodDate(loanApplicationTerms.getRepaymentPeriodFrequencyType(),
loanApplicationTerms.getRepaymentEvery(), lastRepaymentDate, loanApplicationTerms.getNthDay(),
loanApplicationTerms.getWeekDayType());
dueRepaymentPeriodDate = 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
LocalDate seedDate = currentCalendar.getStartDateLocalDate();
String reccuringString = currentCalendar.getRecurrence();
dueRepaymentPeriodDate = CalendarUtils.getNewRepaymentMeetingDate(reccuringString, seedDate, dueRepaymentPeriodDate,
loanApplicationTerms.getRepaymentEvery(),
CalendarUtils.getMeetingFrequencyFromPeriodFrequencyType(loanApplicationTerms.getLoanTermPeriodFrequencyType()),
holidayDetailDTO.getWorkingDays());
}
}
return dueRepaymentPeriodDate;
}
@Override
public LocalDate adjustRepaymentDate(final LocalDate dueRepaymentPeriodDate, final LoanApplicationTerms loanApplicationTerms,
final HolidayDetailDTO holidayDetailDTO) {
LocalDate adjustedDate = dueRepaymentPeriodDate;
LocalDate nextDueRepaymentPeriodDate = getRepaymentPeriodDate(loanApplicationTerms.getRepaymentPeriodFrequencyType(),
loanApplicationTerms.getRepaymentEvery(), adjustedDate, loanApplicationTerms.getNthDay(),
loanApplicationTerms.getWeekDayType());
final RepaymentRescheduleType rescheduleType = RepaymentRescheduleType.fromInt(holidayDetailDTO.getWorkingDays()
.getRepaymentReschedulingType());
/**
* Fix for https://mifosforge.jira.com/browse/MIFOSX-1357
*/
// recursively check for the next working meeting day.
while (WorkingDaysUtil.isNonWorkingDay(holidayDetailDTO.getWorkingDays(), nextDueRepaymentPeriodDate)
&& rescheduleType == RepaymentRescheduleType.MOVE_TO_NEXT_REPAYMENT_MEETING_DAY) {
nextDueRepaymentPeriodDate = getRepaymentPeriodDate(loanApplicationTerms.getRepaymentPeriodFrequencyType(),
loanApplicationTerms.getRepaymentEvery(), nextDueRepaymentPeriodDate, loanApplicationTerms.getNthDay(),
loanApplicationTerms.getWeekDayType());
nextDueRepaymentPeriodDate = CalendarUtils.adjustDate(nextDueRepaymentPeriodDate, loanApplicationTerms.getSeedDate(),
loanApplicationTerms.getRepaymentPeriodFrequencyType());
}
adjustedDate = WorkingDaysUtil.getOffSetDateIfNonWorkingDay(adjustedDate, nextDueRepaymentPeriodDate,
holidayDetailDTO.getWorkingDays());
if (holidayDetailDTO.isHolidayEnabled()) {
adjustedDate = HolidayUtil.getRepaymentRescheduleDateToIfHoliday(adjustedDate, holidayDetailDTO.getHolidays());
}
return adjustedDate;
}
@Override
public LocalDate getRepaymentPeriodDate(final PeriodFrequencyType frequency, final int repaidEvery, final LocalDate startDate,
Integer nthDay, DayOfWeekType dayOfWeek) {
LocalDate dueRepaymentPeriodDate = startDate;
switch (frequency) {
case DAYS:
dueRepaymentPeriodDate = startDate.plusDays(repaidEvery);
break;
case WEEKS:
dueRepaymentPeriodDate = startDate.plusWeeks(repaidEvery);
break;
case MONTHS:
dueRepaymentPeriodDate = startDate.plusMonths(repaidEvery);
if (!(nthDay == null || dayOfWeek == null || dayOfWeek == DayOfWeekType.INVALID)) {
dueRepaymentPeriodDate = adjustToNthWeekDay(dueRepaymentPeriodDate, nthDay, dayOfWeek.getValue());
}
break;
case YEARS:
dueRepaymentPeriodDate = startDate.plusYears(repaidEvery);
break;
case INVALID:
break;
}
return dueRepaymentPeriodDate;
}
private LocalDate adjustToNthWeekDay(LocalDate dueRepaymentPeriodDate, int nthDay, int dayOfWeek) {
// adjust date to start of month
dueRepaymentPeriodDate = dueRepaymentPeriodDate.withDayOfMonth(1);
// adjust date to next week if current day is past specified day of
// week.
if (dueRepaymentPeriodDate.getDayOfWeek() > dayOfWeek) {
dueRepaymentPeriodDate = dueRepaymentPeriodDate.plusWeeks(1);
}
// adjust date to specified date of week
dueRepaymentPeriodDate = dueRepaymentPeriodDate.withDayOfWeek(dayOfWeek);
// adjust to specified nth week day
dueRepaymentPeriodDate = dueRepaymentPeriodDate.plusWeeks(nthDay - 1);
return dueRepaymentPeriodDate;
}
@Override
public Boolean isDateFallsInSchedule(final PeriodFrequencyType frequency, final int repaidEvery, final LocalDate startDate,
final LocalDate date) {
boolean isScheduledDate = false;
switch (frequency) {
case DAYS:
int diff = Days.daysBetween(startDate, date).getDays();
isScheduledDate = (diff % repaidEvery) == 0;
break;
case WEEKS:
int weekDiff = Weeks.weeksBetween(startDate, date).getWeeks();
isScheduledDate = (weekDiff % repaidEvery) == 0;
if (isScheduledDate) {
LocalDate modifiedDate = startDate.plusWeeks(weekDiff);
isScheduledDate = modifiedDate.isEqual(date);
}
break;
case MONTHS:
int monthDiff = Months.monthsBetween(startDate, date).getMonths();
isScheduledDate = (monthDiff % repaidEvery) == 0;
if (isScheduledDate) {
LocalDate modifiedDate = startDate.plusMonths(monthDiff);
isScheduledDate = modifiedDate.isEqual(date);
}
break;
case YEARS:
int yearDiff = Years.yearsBetween(startDate, date).getYears();
isScheduledDate = (yearDiff % repaidEvery) == 0;
if (isScheduledDate) {
LocalDate modifiedDate = startDate.plusYears(yearDiff);
isScheduledDate = modifiedDate.isEqual(date);
}
break;
case INVALID:
break;
}
return isScheduledDate;
}
@Override
public LocalDate idealDisbursementDateBasedOnFirstRepaymentDate(final PeriodFrequencyType repaymentPeriodFrequencyType,
final int repaidEvery, final LocalDate firstRepaymentDate) {
LocalDate idealDisbursementDate = null;
switch (repaymentPeriodFrequencyType) {
case DAYS:
idealDisbursementDate = firstRepaymentDate.minusDays(repaidEvery);
break;
case WEEKS:
idealDisbursementDate = firstRepaymentDate.minusWeeks(repaidEvery);
break;
case MONTHS:
idealDisbursementDate = firstRepaymentDate.minusMonths(repaidEvery);
break;
case YEARS:
idealDisbursementDate = firstRepaymentDate.minusYears(repaidEvery);
break;
case INVALID:
break;
}
return idealDisbursementDate;
}
@Override
public LocalDate generateNextScheduleDateStartingFromDisburseDate(LocalDate lastRepaymentDate,
LoanApplicationTerms loanApplicationTerms, final HolidayDetailDTO holidayDetailDTO) {
LocalDate generatedDate = loanApplicationTerms.getExpectedDisbursementDate();
boolean isFirstRepayment = true;
while (!generatedDate.isAfter(lastRepaymentDate)) {
generatedDate = generateNextRepaymentDate(generatedDate, loanApplicationTerms, isFirstRepayment, holidayDetailDTO);
isFirstRepayment = false;
}
generatedDate = adjustRepaymentDate(generatedDate, loanApplicationTerms, holidayDetailDTO);
return generatedDate;
}
}