holiday changes for next repayment date

This commit is contained in:
nazeer shaik
2017-07-20 17:10:11 +05:30
parent 6946f1c3fd
commit b9017e2be2
23 changed files with 596 additions and 160 deletions
@@ -41,6 +41,7 @@ public class HolidayApiConstants {
public static final String repaymentsRescheduledToParamName = "repaymentsRescheduledTo";
public static final String processed = "processed";
public static final String status = "status";
public static final String reschedulingType = "reschedulingType";
protected static final Set<String> HOLIDAY_RESPONSE_DATA_PARAMETERS = new HashSet<>(
Arrays.asList(idParamName, nameParamName, fromDateParamName, descriptionParamName, toDateParamName,
@@ -180,4 +180,13 @@ public class HolidaysApiResource {
final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
return this.toApiJsonSerializer.serialize(settings, holidays, HOLIDAY_RESPONSE_DATA_PARAMETERS);
}
@GET
@Path("/template")
@Consumes({MediaType.APPLICATION_JSON})
@Produces({MediaType.APPLICATION_JSON})
public String retrieveRepaymentScheduleUpdationTyeOptions(@Context final UriInfo uriInfo){
this.context.authenticatedUser().validateHasReadPermission(HOLIDAY_RESOURCE_NAME);
return this.toApiJsonSerializer.serialize(this.holidayReadPlatformService.retrieveRepaymentScheduleUpdationTyeOptions());
}
}
@@ -39,9 +39,10 @@ public class HolidayData {
private final Long officeId;
@SuppressWarnings("unused")
private final EnumOptionData status;
private final Integer reschedulingType;
public HolidayData(final Long id, final String name, final String description, final LocalDate fromDate, final LocalDate toDate,
final LocalDate repaymentsRescheduledTo, final EnumOptionData status) {
final LocalDate repaymentsRescheduledTo, final EnumOptionData status, final Integer reschedulingType) {
this.id = id;
this.name = name;
this.description = description;
@@ -50,5 +51,6 @@ public class HolidayData {
this.repaymentsRescheduledTo = repaymentsRescheduledTo;
this.officeId = null;
this.status = status;
this.reschedulingType = reschedulingType;
}
}
@@ -23,6 +23,7 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
@@ -33,6 +34,7 @@ import org.apache.fineract.infrastructure.core.exception.InvalidJsonException;
import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
import org.apache.fineract.organisation.holiday.api.HolidayApiConstants;
import org.apache.fineract.organisation.holiday.domain.RescheduleType;
import org.joda.time.LocalDate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@@ -50,7 +52,8 @@ public class HolidayDataValidator {
Arrays.asList(HolidayApiConstants.localeParamName, HolidayApiConstants.dateFormatParamName,
HolidayApiConstants.nameParamName, HolidayApiConstants.fromDateParamName,
HolidayApiConstants.toDateParamName, HolidayApiConstants.descriptionParamName,
HolidayApiConstants.officesParamName, HolidayApiConstants.repaymentsRescheduledToParamName));
HolidayApiConstants.officesParamName, HolidayApiConstants.repaymentsRescheduledToParamName,
HolidayApiConstants.reschedulingType));
@Autowired
public HolidayDataValidator(final FromJsonHelper fromApiJsonHelper) {
@@ -79,11 +82,18 @@ public class HolidayDataValidator {
final LocalDate toDate = this.fromApiJsonHelper.extractLocalDateNamed(HolidayApiConstants.toDateParamName, element);
baseDataValidator.reset().parameter(HolidayApiConstants.toDateParamName).value(toDate).notNull();
final LocalDate repaymentsRescheduledTo = this.fromApiJsonHelper.extractLocalDateNamed(
HolidayApiConstants.repaymentsRescheduledToParamName, element);
baseDataValidator.reset().parameter(HolidayApiConstants.repaymentsRescheduledToParamName).value(repaymentsRescheduledTo).notNull();
Integer reschedulingType = null;
if(this.fromApiJsonHelper.parameterExists(HolidayApiConstants.reschedulingType, element)){
reschedulingType = this.fromApiJsonHelper.extractIntegerNamed(HolidayApiConstants.reschedulingType, element, Locale.ENGLISH);
}
LocalDate repaymentsRescheduledTo = null;
if(reschedulingType == null || reschedulingType.equals(RescheduleType.RESCHEDULETOSPECIFICDATE.getValue())){
repaymentsRescheduledTo = this.fromApiJsonHelper.extractLocalDateNamed(
HolidayApiConstants.repaymentsRescheduledToParamName, element);
baseDataValidator.reset().parameter(HolidayApiConstants.repaymentsRescheduledToParamName).value(repaymentsRescheduledTo).notNull();
}
Set<Long> offices = null;
final JsonObject topLevelJsonElement = element.getAsJsonObject();
@@ -73,9 +73,12 @@ public class Holiday extends AbstractPersistableCustom<Long> {
@Temporal(TemporalType.DATE)
private Date toDate;
@Column(name = "repayments_rescheduled_to", nullable = false)
@Column(name = "repayments_rescheduled_to", nullable = true)
@Temporal(TemporalType.DATE)
private Date repaymentsRescheduledTo;
@Column(name = "rescheduling_type", nullable = false)
private int reschedulingType;
@Column(name = "status_enum", nullable = false)
private Integer status;
@@ -94,13 +97,21 @@ public class Holiday extends AbstractPersistableCustom<Long> {
final String name = command.stringValueOfParameterNamed(HolidayApiConstants.nameParamName);
final LocalDate fromDate = command.localDateValueOfParameterNamed(HolidayApiConstants.fromDateParamName);
final LocalDate toDate = command.localDateValueOfParameterNamed(HolidayApiConstants.toDateParamName);
final LocalDate repaymentsRescheduledTo = command
.localDateValueOfParameterNamed(HolidayApiConstants.repaymentsRescheduledToParamName);
Integer reschedulingType = null;
if(command.parameterExists(HolidayApiConstants.reschedulingType)){
reschedulingType = command.integerValueOfParameterNamed(HolidayApiConstants.reschedulingType);
}
LocalDate repaymentsRescheduledTo = null;
if(reschedulingType == null || reschedulingType.equals(RescheduleType.RESCHEDULETOSPECIFICDATE.getValue())){
repaymentsRescheduledTo = command
.localDateValueOfParameterNamed(HolidayApiConstants.repaymentsRescheduledToParamName);
}
final Integer status = HolidayStatusType.PENDING_FOR_ACTIVATION.getValue();
final boolean processed = false;// default it to false. Only batch job
// should update this field.
final String description = command.stringValueOfParameterNamed(HolidayApiConstants.descriptionParamName);
return new Holiday(name, fromDate, toDate, repaymentsRescheduledTo, status, processed, description, offices);
return new Holiday(name, fromDate, toDate, repaymentsRescheduledTo, status, processed, description, offices, reschedulingType);
}
public Map<String, Object> update(final JsonCommand command) {
@@ -125,6 +136,16 @@ public class Holiday extends AbstractPersistableCustom<Long> {
actualChanges.put(descriptionParamName, newValue);
this.description = StringUtils.defaultIfEmpty(newValue, null);
}
if (command.isChangeInIntegerParameterNamed(HolidayApiConstants.reschedulingType,this.reschedulingType)) {
final Integer newValue =command.integerValueOfParameterNamed(HolidayApiConstants.reschedulingType);
actualChanges.put(HolidayApiConstants.reschedulingType, newValue);
this.reschedulingType = RescheduleType.fromInt(newValue).getValue();
if(newValue.equals(RescheduleType.RESCHEDULETONEXTREPAYMENTDATE.getValue())){
this.repaymentsRescheduledTo = null;
}
}
if (currentStatus.isPendingActivation()) {
if (command.isChangeInLocalDateParameterNamed(fromDateParamName, getFromDateLocalDate())) {
@@ -205,7 +226,7 @@ public class Holiday extends AbstractPersistableCustom<Long> {
}
private Holiday(final String name, final LocalDate fromDate, final LocalDate toDate, final LocalDate repaymentsRescheduledTo,
final Integer status, final boolean processed, final String description, final Set<Office> offices) {
final Integer status, final boolean processed, final String description, final Set<Office> offices, final int reschedulingType) {
if (StringUtils.isNotBlank(name)) {
this.name = name.trim();
}
@@ -234,6 +255,7 @@ public class Holiday extends AbstractPersistableCustom<Long> {
if (offices != null) {
this.offices = offices;
}
this.reschedulingType = reschedulingType;
}
protected Holiday() {}
@@ -298,4 +320,8 @@ public class Holiday extends AbstractPersistableCustom<Long> {
}
this.status = HolidayStatusType.DELETED.getValue();
}
public RescheduleType getReScheduleType() {
return RescheduleType.fromInt(this.reschedulingType);
}
}
@@ -0,0 +1,64 @@
/**
* 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.organisation.holiday.domain;
public enum RescheduleType {
INVALID(0, "rescheduletype.invalid"), RESCHEDULETOSPECIFICDATE(2, "rescheduletype.rescheduletospecificdate"), //
RESCHEDULETONEXTREPAYMENTDATE(1, "rescheduletype.rescheduletonextrepaymentdate");
private final Integer value;
private final String code;
private RescheduleType(Integer value, String code) {
this.value = value;
this.code = code;
}
public static RescheduleType fromInt(int rescheduleTypeValue) {
RescheduleType enumerration = RescheduleType.INVALID;
switch (rescheduleTypeValue) {
case 1:
enumerration = RescheduleType.RESCHEDULETONEXTREPAYMENTDATE;
break;
case 2:
enumerration = RescheduleType.RESCHEDULETOSPECIFICDATE;
break;
}
return enumerration;
}
public boolean isRescheduleToSpecificDate(){
return this.value.equals(RescheduleType.RESCHEDULETOSPECIFICDATE.getValue());
}
public boolean isResheduleToNextRepaymentDate(){
return this.value.equals(RescheduleType.RESCHEDULETONEXTREPAYMENTDATE.getValue());
}
public Integer getValue() {
return this.value;
}
public String getCode() {
return this.code;
}
}
@@ -20,6 +20,7 @@ package org.apache.fineract.organisation.holiday.service;
import org.apache.fineract.infrastructure.core.data.EnumOptionData;
import org.apache.fineract.organisation.holiday.domain.HolidayStatusType;
import org.apache.fineract.organisation.holiday.domain.RescheduleType;
public class HolidayEnumerations {
@@ -49,4 +50,29 @@ public class HolidayEnumerations {
}
return optionData;
}
public static EnumOptionData rescheduleType(final int id) {
return rescheduleType(RescheduleType.fromInt(id));
}
public static EnumOptionData rescheduleType(final RescheduleType type) {
EnumOptionData optionData = null;
switch (type) {
case RESCHEDULETONEXTREPAYMENTDATE:
optionData = new EnumOptionData(RescheduleType.RESCHEDULETONEXTREPAYMENTDATE.getValue().longValue(),
RescheduleType.RESCHEDULETONEXTREPAYMENTDATE.getCode(), "Reschedule to next repayment date");
break;
case RESCHEDULETOSPECIFICDATE:
optionData = new EnumOptionData(RescheduleType.RESCHEDULETOSPECIFICDATE.getValue().longValue(),
RescheduleType.RESCHEDULETOSPECIFICDATE.getCode(), "Reschedule to specified date");
break;
default:
optionData = new EnumOptionData(RescheduleType.INVALID.getValue().longValue(),
RescheduleType.INVALID.getCode(), "Invalid");
break;
}
return optionData;
}
}
@@ -20,7 +20,9 @@ package org.apache.fineract.organisation.holiday.service;
import java.util.Collection;
import java.util.Date;
import java.util.List;
import org.apache.fineract.infrastructure.core.data.EnumOptionData;
import org.apache.fineract.organisation.holiday.data.HolidayData;
public interface HolidayReadPlatformService {
@@ -28,4 +30,6 @@ public interface HolidayReadPlatformService {
Collection<HolidayData> retrieveAllHolidaysBySearchParamerters(final Long officeId, Date fromDate, Date toDate);
HolidayData retrieveHoliday(final Long holidayId);
List<EnumOptionData> retrieveRepaymentScheduleUpdationTyeOptions();
}
@@ -25,12 +25,14 @@ import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Collection;
import java.util.Date;
import java.util.List;
import org.apache.fineract.infrastructure.core.data.EnumOptionData;
import org.apache.fineract.infrastructure.core.domain.JdbcSupport;
import org.apache.fineract.infrastructure.core.service.RoutingDataSource;
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
import org.apache.fineract.organisation.holiday.data.HolidayData;
import org.apache.fineract.organisation.holiday.domain.RescheduleType;
import org.apache.fineract.organisation.holiday.exception.HolidayNotFoundException;
import org.joda.time.LocalDate;
import org.springframework.beans.factory.annotation.Autowired;
@@ -58,7 +60,7 @@ public class HolidayReadPlatformServiceImpl implements HolidayReadPlatformServic
public HolidayMapper() {
final StringBuilder sqlBuilder = new StringBuilder(200);
sqlBuilder.append("h.id as id, h.name as name, h.description as description, h.from_date as fromDate, h.to_date as toDate, ");
sqlBuilder.append("h.repayments_rescheduled_to as repaymentsScheduleTO, h.status_enum as statusEnum ");
sqlBuilder.append("h.repayments_rescheduled_to as repaymentsScheduleTO, h.rescheduling_type as reschedulingType, h.status_enum as statusEnum ");
sqlBuilder.append("from m_holiday h ");
this.schema = sqlBuilder.toString();
}
@@ -76,9 +78,10 @@ public class HolidayReadPlatformServiceImpl implements HolidayReadPlatformServic
final LocalDate toDate = JdbcSupport.getLocalDate(rs, "toDate");
final LocalDate repaymentsScheduleTO = JdbcSupport.getLocalDate(rs, "repaymentsScheduleTO");
final Integer statusEnum = JdbcSupport.getInteger(rs, "statusEnum");
final Integer reschedulingType = JdbcSupport.getInteger(rs, "reschedulingType");
final EnumOptionData status = HolidayEnumerations.holidayStatusType(statusEnum);
return new HolidayData(id, name, description, fromDate, toDate, repaymentsScheduleTO, status);
return new HolidayData(id, name, description, fromDate, toDate, repaymentsScheduleTO, status, reschedulingType);
}
}
@@ -133,5 +136,14 @@ public class HolidayReadPlatformServiceImpl implements HolidayReadPlatformServic
throw new HolidayNotFoundException(holidayId);
}
}
@Override
public List<EnumOptionData> retrieveRepaymentScheduleUpdationTyeOptions(){
final List<EnumOptionData> repSchUpdationTypeOptions = Arrays.asList(
HolidayEnumerations.rescheduleType(RescheduleType.RESCHEDULETOSPECIFICDATE),
HolidayEnumerations.rescheduleType(RescheduleType.RESCHEDULETONEXTREPAYMENTDATE));
return repSchUpdationTypeOptions;
}
}
@@ -21,6 +21,7 @@ package org.apache.fineract.organisation.holiday.service;
import java.util.List;
import org.apache.fineract.organisation.holiday.domain.Holiday;
import org.apache.fineract.organisation.workingdays.data.AdjustedDateDetailsDTO;
import org.joda.time.LocalDate;
public class HolidayUtil {
@@ -56,4 +57,20 @@ public class HolidayUtil {
return false;
}
public static Holiday getApplicableHoliday(final LocalDate repaymentDate, final List<Holiday> holidays) {
Holiday referedHoliday = null;
for (final Holiday holiday : holidays) {
if (!repaymentDate.isBefore(holiday.getFromDateLocalDate()) && !repaymentDate.isAfter(holiday.getToDateLocalDate())) {
referedHoliday = holiday;
}
}
return referedHoliday;
}
public static void updateRepaymentRescheduleDateToWorkingDayIfItIsHoliday(final AdjustedDateDetailsDTO adjustedDateDetailsDTO,
final Holiday holiday) {
if (holiday.getReScheduleType().isRescheduleToSpecificDate()) {
adjustedDateDetailsDTO.setChangedScheduleDate(holiday.getRepaymentsRescheduledToLocalDate());
}
}
}
@@ -194,9 +194,18 @@ public class HolidayWritePlatformServiceJpaRepositoryImpl implements HolidayWrit
private void validateInputDates(final JsonCommand command) {
final LocalDate fromDate = command.localDateValueOfParameterNamed(HolidayApiConstants.fromDateParamName);
final LocalDate toDate = command.localDateValueOfParameterNamed(HolidayApiConstants.toDateParamName);
final LocalDate repaymentsRescheduledTo = command
.localDateValueOfParameterNamed(HolidayApiConstants.repaymentsRescheduledToParamName);
this.validateInputDates(fromDate, toDate, repaymentsRescheduledTo);
Integer reshedulingType = null;
if(command.parameterExists(HolidayApiConstants.reschedulingType)){
reshedulingType = command.integerValueOfParameterNamed(HolidayApiConstants.reschedulingType);
}
LocalDate repaymentsRescheduledTo = null;
if(reshedulingType != null && reshedulingType.equals(2)){
repaymentsRescheduledTo = command
.localDateValueOfParameterNamed(HolidayApiConstants.repaymentsRescheduledToParamName);
}
if(repaymentsRescheduledTo != null){
this.validateInputDates(fromDate, toDate, repaymentsRescheduledTo);
}
}
private void validateInputDates(final LocalDate fromDate, final LocalDate toDate, final LocalDate repaymentsRescheduledTo) {
@@ -208,37 +217,40 @@ public class HolidayWritePlatformServiceJpaRepositoryImpl implements HolidayWrit
throw new HolidayDateException("to.date.cannot.be.before.from.date", defaultUserMessage, fromDate.toString(),
toDate.toString());
}
if(repaymentsRescheduledTo != null){
if ((repaymentsRescheduledTo.isEqual(fromDate) || repaymentsRescheduledTo.isEqual(toDate)
|| (repaymentsRescheduledTo.isAfter(fromDate) && repaymentsRescheduledTo.isBefore(toDate)))) {
if (repaymentsRescheduledTo.isEqual(fromDate) || repaymentsRescheduledTo.isEqual(toDate)
|| (repaymentsRescheduledTo.isAfter(fromDate) && repaymentsRescheduledTo.isBefore(toDate))) {
defaultUserMessage = "Repayments rescheduled date should be before from date or after to date.";
throw new HolidayDateException("repayments.rescheduled.date.should.be.before.from.date.or.after.to.date", defaultUserMessage,
repaymentsRescheduledTo.toString());
}
defaultUserMessage = "Repayments rescheduled date should be before from date or after to date.";
throw new HolidayDateException("repayments.rescheduled.date.should.be.before.from.date.or.after.to.date", defaultUserMessage,
repaymentsRescheduledTo.toString());
final WorkingDays workingDays = this.daysRepositoryWrapper.findOne();
final Boolean isRepaymentOnWorkingDay = WorkingDaysUtil.isWorkingDay(workingDays, repaymentsRescheduledTo);
if (!isRepaymentOnWorkingDay) {
defaultUserMessage = "Repayments rescheduled date should not fall on non working days";
throw new HolidayDateException("repayments.rescheduled.date.should.not.fall.on.non.working.day", defaultUserMessage,
repaymentsRescheduledTo.toString());
}
// validate repaymentsRescheduledTo date
// 1. should be within a 7 days date range.
// 2. Alternative date should not be an exist holiday.//TBD
// 3. Holiday should not be on an repaymentsRescheduledTo date of
// another holiday.//TBD
// restricting repaymentsRescheduledTo date to be within 7 days range
// before or after from date and to date.
if (repaymentsRescheduledTo.isBefore(fromDate.minusDays(7)) || repaymentsRescheduledTo.isAfter(toDate.plusDays(7))) {
defaultUserMessage = "Repayments Rescheduled to date must be within 7 days before or after from and to dates";
throw new HolidayDateException("repayments.rescheduled.to.must.be.within.range", defaultUserMessage, fromDate.toString(),
toDate.toString(), repaymentsRescheduledTo.toString());
}
}
final WorkingDays workingDays = this.daysRepositoryWrapper.findOne();
final Boolean isRepaymentOnWorkingDay = WorkingDaysUtil.isWorkingDay(workingDays, repaymentsRescheduledTo);
if (!isRepaymentOnWorkingDay) {
defaultUserMessage = "Repayments rescheduled date should not fall on non working days";
throw new HolidayDateException("repayments.rescheduled.date.should.not.fall.on.non.working.day", defaultUserMessage,
repaymentsRescheduledTo.toString());
}
// validate repaymentsRescheduledTo date
// 1. should be within a 7 days date range.
// 2. Alternative date should not be an exist holiday.//TBD
// 3. Holiday should not be on an repaymentsRescheduledTo date of
// another holiday.//TBD
// restricting repaymentsRescheduledTo date to be within 7 days range
// before or after from date and to date.
if (repaymentsRescheduledTo.isBefore(fromDate.minusDays(7)) || repaymentsRescheduledTo.isAfter(toDate.plusDays(7))) {
defaultUserMessage = "Repayments Rescheduled to date must be within 7 days before or after from and to dates";
throw new HolidayDateException("repayments.rescheduled.to.must.be.within.range", defaultUserMessage, fromDate.toString(),
toDate.toString(), repaymentsRescheduledTo.toString());
}
}
}
@@ -0,0 +1,76 @@
/**
* 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.organisation.workingdays.data;
import org.joda.time.LocalDate;
public class AdjustedDateDetailsDTO {
/**
* Variable tracks the current schedule date that has been changed
*/
LocalDate changedScheduleDate;
/**
* Variable tracks If the meeting has been changed , i.e future schedule
* also changes along with the current repayments date.
*/
LocalDate changedActualRepaymentDate;
/**
* Variable tracks the next repayment period due date
*/
LocalDate nextRepaymentPeriodDueDate;
public AdjustedDateDetailsDTO(final LocalDate changedScheduleDate, final LocalDate changedActualRepaymentDate) {
this.changedScheduleDate = changedScheduleDate;
this.changedActualRepaymentDate = changedActualRepaymentDate;
}
public AdjustedDateDetailsDTO(final LocalDate changedScheduleDate, final LocalDate changedActualRepaymentDate,
final LocalDate nextRepaymentPeriodDueDate) {
this.changedScheduleDate = changedScheduleDate;
this.changedActualRepaymentDate = changedActualRepaymentDate;
this.nextRepaymentPeriodDueDate = nextRepaymentPeriodDueDate;
}
public LocalDate getChangedScheduleDate() {
return this.changedScheduleDate;
}
public LocalDate getChangedActualRepaymentDate() {
return this.changedActualRepaymentDate;
}
public void setChangedScheduleDate(final LocalDate changedScheduleDate) {
this.changedScheduleDate = changedScheduleDate;
}
public void setChangedActualRepaymentDate(final LocalDate changedActualRepaymentDate) {
this.changedActualRepaymentDate = changedActualRepaymentDate;
}
public LocalDate getNextRepaymentPeriodDueDate() {
return this.nextRepaymentPeriodDueDate;
}
public void setNextRepaymentPeriodDueDate(final LocalDate nextRepaymentPeriodDueDate) {
this.nextRepaymentPeriodDueDate = nextRepaymentPeriodDueDate;
}
}
@@ -26,7 +26,8 @@ public enum RepaymentRescheduleType {
INVALID(0, "RepaymentRescheduleType.invalid"), SAME_DAY(1, "RepaymentRescheduleType.same.day"), MOVE_TO_NEXT_WORKING_DAY(2,
"RepaymentRescheduleType.move.to.next.working.day"), MOVE_TO_NEXT_REPAYMENT_MEETING_DAY(3,
"RepaymentRescheduleType.move.to.next.repayment.meeting.day"), MOVE_TO_PREVIOUS_WORKING_DAY(4,
"RepaymentRescheduleType.move.to.previous.working.day");
"RepaymentRescheduleType.move.to.previous.working.day"),
MOVE_TO_NEXT_MEETING_DAY(5, "RepaymentRescheduleType.move.to.next.meeting.day");
private final Integer value;
private final String code;
@@ -43,6 +44,10 @@ public enum RepaymentRescheduleType {
public String getCode() {
return this.code;
}
public boolean isMoveToNextRepaymentDay() {
return this.value.equals(RepaymentRescheduleType.MOVE_TO_NEXT_REPAYMENT_MEETING_DAY.getValue());
}
private static final Map<Integer, RepaymentRescheduleType> intToEnumMap = new HashMap<>();
private static int minValue;
@@ -18,6 +18,7 @@
*/
package org.apache.fineract.organisation.workingdays.service;
import org.apache.fineract.organisation.workingdays.data.AdjustedDateDetailsDTO;
import org.apache.fineract.organisation.workingdays.domain.RepaymentRescheduleType;
import org.apache.fineract.organisation.workingdays.domain.WorkingDays;
import org.apache.fineract.portfolio.calendar.service.CalendarUtils;
@@ -56,4 +57,15 @@ public class WorkingDaysUtil {
public static boolean isNonWorkingDay(final WorkingDays workingDays, final LocalDate date) {
return !isWorkingDay(workingDays, date);
}
public static void updateWorkingDayIfRepaymentDateIsNonWorkingDay(final AdjustedDateDetailsDTO adjustedDateDetailsDTO, final WorkingDays workingDays) {
final LocalDate changedScheduleDate = getOffSetDateIfNonWorkingDay(adjustedDateDetailsDTO.getChangedScheduleDate(),
adjustedDateDetailsDTO.getNextRepaymentPeriodDueDate(), workingDays);
adjustedDateDetailsDTO.setChangedScheduleDate(changedScheduleDate);
}
public static RepaymentRescheduleType getRepaymentRescheduleType(final WorkingDays workingDays, final LocalDate date) {
RepaymentRescheduleType rescheduleType = RepaymentRescheduleType.fromInt(workingDays.getRepaymentReschedulingType());
return rescheduleType;
}
}
@@ -700,4 +700,112 @@ public class CalendarUtils {
}
return monthOnDay;
}
public static LocalDate getNextRepaymentMeetingDate(final String recurringRule, final LocalDate seedDate,
final LocalDate repaymentDate, final Integer loanRepaymentInterval, final String frequency, final WorkingDays workingDays,
boolean isSkipRepaymentOnFirstDayOfMonth, final Integer numberOfDays, boolean applyWorkingDays) {
boolean isCalledFirstTime = true;
return getNextRepaymentMeetingDate(recurringRule, seedDate, repaymentDate, loanRepaymentInterval, frequency,
workingDays, isSkipRepaymentOnFirstDayOfMonth, numberOfDays, isCalledFirstTime, applyWorkingDays);
}
public static LocalDate getNextRepaymentMeetingDate(final String recurringRule, final LocalDate seedDate,
final LocalDate repaymentDate, final Integer loanRepaymentInterval, final String frequency,
boolean isSkipRepaymentOnFirstDayOfMonth, final Integer numberOfDays) {
boolean isCalledFirstTime = true;
final WorkingDays workingDays = null;
boolean applyWorkingDays = false;
return getNextRepaymentMeetingDate(recurringRule, seedDate, repaymentDate, loanRepaymentInterval, frequency,
workingDays, isSkipRepaymentOnFirstDayOfMonth, numberOfDays, isCalledFirstTime, applyWorkingDays);
}
public static LocalDate getNextRepaymentMeetingDate(final String recurringRule, final LocalDate seedDate,
final LocalDate repaymentDate, final Integer loanRepaymentInterval, final String frequency, final WorkingDays workingDays,
boolean isSkipRepaymentOnFirstDayOfMonth, final Integer numberOfDays, boolean isCalledFirstTime, boolean applyWorkingDays) {
final Recur recur = CalendarUtils.getICalRecur(recurringRule);
if (recur == null) { return null; }
LocalDate tmpDate = repaymentDate;
final Integer repaymentInterval = getMeetingIntervalFromFrequency(loanRepaymentInterval, frequency, recur);
/*
* Recurring dates should follow loanRepaymentInterval.
*
* e.g. The weekly meeting will have interval of 1, if the loan product
* with fortnightly frequency will have interval of 2, to generate right
* set of meeting dates reset interval same as loan repayment interval.
*/
int meetingInterval = recur.getInterval();
if(meetingInterval < 1){
meetingInterval = 1;
}
int rep = repaymentInterval<meetingInterval ? 1: repaymentInterval / meetingInterval ;
/*
* Recurring dates should follow loanRepayment frequency. //e.g. daily
* meeting frequency should support all loan products with any type of
* frequency. to generate right set of meeting dates reset frequency
* same as loan repayment frequency.
*/
if (recur.getFrequency().equals(Recur.DAILY)) {
recur.setFrequency(frequency);
}
/**
* Below code modified as discussed with Pramod N
*/
LocalDate newRepaymentDate = tmpDate;
int newRepayment = rep;
while (newRepayment > 0) {
newRepaymentDate = getNextRecurringDate(recur, seedDate, newRepaymentDate);
newRepayment--;
}
LocalDate nextRepaymentDate = null;
if (applyWorkingDays) {
if (WorkingDaysUtil.isNonWorkingDay(workingDays, newRepaymentDate)
&& WorkingDaysUtil.getRepaymentRescheduleType(workingDays, newRepaymentDate).isMoveToNextRepaymentDay()) {
newRepaymentDate = getNextRepaymentMeetingDate(recurringRule, seedDate, newRepaymentDate.plusDays(1),
loanRepaymentInterval, frequency, workingDays, isSkipRepaymentOnFirstDayOfMonth, numberOfDays, isCalledFirstTime,
applyWorkingDays);
} else {
newRepaymentDate = WorkingDaysUtil.getOffSetDateIfNonWorkingDay(newRepaymentDate, nextRepaymentDate, workingDays);
}
}
if(isCalledFirstTime && newRepaymentDate.equals(repaymentDate)){
isCalledFirstTime = false;
newRepaymentDate = getNextRepaymentMeetingDate(recurringRule, seedDate, repaymentDate.plusDays(1), loanRepaymentInterval,
frequency, workingDays, isSkipRepaymentOnFirstDayOfMonth, numberOfDays, isCalledFirstTime, applyWorkingDays);
}
if (isSkipRepaymentOnFirstDayOfMonth) {
final LocalDate newRepaymentDateTemp = adjustRecurringDate(newRepaymentDate, numberOfDays);
if (applyWorkingDays) {
if (WorkingDaysUtil.isNonWorkingDay(workingDays, newRepaymentDateTemp)
&& WorkingDaysUtil.getRepaymentRescheduleType(workingDays, newRepaymentDateTemp).isMoveToNextRepaymentDay()) {
newRepaymentDate = getNextRepaymentMeetingDate(recurringRule, seedDate, newRepaymentDate.plusDays(1),
loanRepaymentInterval, frequency, workingDays, isSkipRepaymentOnFirstDayOfMonth, numberOfDays,
isCalledFirstTime, applyWorkingDays);
} else {
newRepaymentDate = WorkingDaysUtil.getOffSetDateIfNonWorkingDay(newRepaymentDateTemp, nextRepaymentDate, workingDays);
}
}
}
return newRepaymentDate;
}
public static Integer getMeetingIntervalFromFrequency(final Integer loanRepaymentInterval, final String frequency, final Recur recur) {
final Integer interval = 4;
Integer repaymentInterval = loanRepaymentInterval;
/*
* check loanRepaymentInterval equal to 1, if repayments frequency is
* monthly and meeting frequency is weekly, then generate repayments
* schedule as every 4 weeks
*/
if (frequency.equals(Recur.MONTHLY) && recur.getFrequency().equals(Recur.WEEKLY)) {
repaymentInterval = loanRepaymentInterval*interval;
}
return repaymentInterval;
}
}
@@ -34,6 +34,7 @@ import org.apache.fineract.infrastructure.core.service.DateUtils;
import org.apache.fineract.organisation.monetary.domain.ApplicationCurrency;
import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
import org.apache.fineract.organisation.monetary.domain.Money;
import org.apache.fineract.organisation.workingdays.data.AdjustedDateDetailsDTO;
import org.apache.fineract.organisation.workingdays.domain.RepaymentRescheduleType;
import org.apache.fineract.portfolio.calendar.domain.CalendarInstance;
import org.apache.fineract.portfolio.calendar.service.CalendarUtils;
@@ -121,7 +122,7 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
boolean isFirstRepayment = true;
LocalDate firstRepaymentdate = this.scheduledDateGenerator.generateNextRepaymentDate(
loanApplicationTerms.getExpectedDisbursementDate(), loanApplicationTerms, isFirstRepayment, holidayDetailDTO);
loanApplicationTerms.getExpectedDisbursementDate(), loanApplicationTerms, isFirstRepayment);
final LocalDate idealDisbursementDate = this.scheduledDateGenerator.idealDisbursementDateBasedOnFirstRepaymentDate(
loanApplicationTerms.getLoanTermPeriodFrequencyType(), loanApplicationTerms.getRepaymentEvery(), firstRepaymentdate,
loanApplicationTerms.getLoanCalendar(), loanApplicationTerms.getHolidayDetailDTO(), loanApplicationTerms);
@@ -179,10 +180,12 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
while (!scheduleParams.getOutstandingBalance().isZero() || !scheduleParams.getDisburseDetailMap().isEmpty()) {
LocalDate previousRepaymentDate = scheduleParams.getActualRepaymentDate();
scheduleParams.setActualRepaymentDate(this.scheduledDateGenerator.generateNextRepaymentDate(
scheduleParams.getActualRepaymentDate(), loanApplicationTerms, isFirstRepayment, holidayDetailDTO));
scheduleParams.getActualRepaymentDate(), loanApplicationTerms, isFirstRepayment));
AdjustedDateDetailsDTO adjustedDateDetailsDTO = this.scheduledDateGenerator.adjustRepaymentDate(
scheduleParams.getActualRepaymentDate(), loanApplicationTerms, holidayDetailDTO);
scheduleParams.setActualRepaymentDate(adjustedDateDetailsDTO.getChangedActualRepaymentDate());
isFirstRepayment = false;
LocalDate scheduledDueDate = this.scheduledDateGenerator.adjustRepaymentDate(scheduleParams.getActualRepaymentDate(),
loanApplicationTerms, holidayDetailDTO);
LocalDate scheduledDueDate = adjustedDateDetailsDTO.getChangedScheduleDate();
// calculated interest start date for the period
LocalDate periodStartDateApplicableForInterest = calculateInterestStartDateForPeriod(loanApplicationTerms,
@@ -1320,7 +1323,7 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
do {
params.setActualRepaymentDate(this.scheduledDateGenerator.generateNextRepaymentDate(params.getActualRepaymentDate(),
loanApplicationTerms, isFirstRepayment, holidayDetailDTO));
loanApplicationTerms, isFirstRepayment));
if (params.getActualRepaymentDate().isAfter(currentDate)) {
params.setActualRepaymentDate(currentDate);
}
@@ -2172,41 +2175,17 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
ArrayList<LoanTermVariationsData> dueDateVariationsDataList = new ArrayList<>();
// check for date changes
// check for date changes
do {
actualRepaymentDate = this.scheduledDateGenerator.generateNextRepaymentDate(actualRepaymentDate,
loanApplicationTerms, isFirstRepayment, holidayDetailDTO);
isFirstRepayment = false;
lastInstallmentDate = this.scheduledDateGenerator.adjustRepaymentDate(actualRepaymentDate, loanApplicationTerms,
holidayDetailDTO);
while (loanApplicationTerms.getLoanTermVariations().hasDueDateVariation(lastInstallmentDate)) {
LoanTermVariationsData variation = loanApplicationTerms.getLoanTermVariations().nextDueDateVariation();
if (!variation.isSpecificToInstallment()) {
actualRepaymentDate = variation.getDateValue();
/*if (!isDueDateChangeApplied) {
previousRepaymentDate = actualRepaymentDate;
isDueDateChangeApplied = true;
}*/
lastInstallmentDate = actualRepaymentDate;
}
dueDateVariationsDataList.add(variation);
while (loanApplicationTerms.getLoanTermVariations().hasDueDateVariation(lastInstallmentDate)) {
LoanTermVariationsData variation = loanApplicationTerms.getLoanTermVariations().nextDueDateVariation();
if (!variation.isSpecificToInstallment()) {
actualRepaymentDate = variation.getDateValue();
loanApplicationTerms.setSeedDate(actualRepaymentDate);
}
loanTermVariationParams = applyExceptionLoanTermVariations(loanApplicationTerms, lastInstallmentDate,
exceptionDataListIterator, instalmentNumber, totalCumulativePrincipal, totalCumulativeInterest, mc);
} while (loanTermVariationParams != null && loanTermVariationParams.isSkipPeriod());
/*if (!lastInstallmentDate.isBefore(rescheduleFrom)) {
actualRepaymentDate = previousRepaymentDate;
if(isDueDateChangeApplied){
int numberOfDateChangesApplied = dueDateVariationsDataList.size();
while(numberOfDateChangesApplied >0 ){
loanApplicationTerms.getLoanTermVariations().previousDueDateVariation();
numberOfDateChangesApplied--;
}
}
break;
}*/
lastInstallmentDate = variation.getDateValue();
dueDateVariationsDataList.add(variation);
}
periodNumber++;
for (LoanTermVariationsData dueDateVariation : dueDateVariationsDataList) {
@@ -20,6 +20,7 @@ package org.apache.fineract.portfolio.loanaccount.loanschedule.domain;
import org.apache.fineract.organisation.holiday.domain.Holiday;
import org.apache.fineract.organisation.holiday.service.HolidayUtil;
import org.apache.fineract.organisation.workingdays.data.AdjustedDateDetailsDTO;
import org.apache.fineract.organisation.workingdays.domain.RepaymentRescheduleType;
import org.apache.fineract.organisation.workingdays.domain.WorkingDays;
import org.apache.fineract.organisation.workingdays.service.WorkingDaysUtil;
@@ -45,25 +46,26 @@ public class DefaultScheduledDateGenerator implements ScheduledDateGenerator {
LocalDate lastRepaymentDate = loanApplicationTerms.getExpectedDisbursementDate();
boolean isFirstRepayment = true;
for (int repaymentPeriod = 1; repaymentPeriod <= numberOfRepayments; repaymentPeriod++) {
lastRepaymentDate = generateNextRepaymentDate(lastRepaymentDate, loanApplicationTerms, isFirstRepayment, holidayDetailDTO);
lastRepaymentDate = generateNextRepaymentDate(lastRepaymentDate, loanApplicationTerms, isFirstRepayment);
isFirstRepayment = false;
}
lastRepaymentDate = adjustRepaymentDate(lastRepaymentDate, loanApplicationTerms, holidayDetailDTO);
lastRepaymentDate = adjustRepaymentDate(lastRepaymentDate, loanApplicationTerms, holidayDetailDTO).getChangedScheduleDate();
return lastRepaymentDate;
}
@Override
public LocalDate generateNextRepaymentDate(final LocalDate lastRepaymentDate, final LoanApplicationTerms loanApplicationTerms,
boolean isFirstRepayment, final HolidayDetailDTO holidayDetailDTO) {
boolean isFirstRepayment) {
final LocalDate firstRepaymentPeriodDate = loanApplicationTerms.getCalculatedRepaymentsStartingFromLocalDate();
LocalDate dueRepaymentPeriodDate = null;
Calendar currentCalendar = loanApplicationTerms.getLoanCalendar();
if (isFirstRepayment && firstRepaymentPeriodDate != null) {
dueRepaymentPeriodDate = firstRepaymentPeriodDate;
} else {
LocalDate seedDate = null;
String reccuringString = null;
Calendar currentCalendar = loanApplicationTerms.getLoanCalendar();
dueRepaymentPeriodDate = getRepaymentPeriodDate(loanApplicationTerms.getRepaymentPeriodFrequencyType(),
loanApplicationTerms.getRepaymentEvery(), lastRepaymentDate, null,
null);
loanApplicationTerms.getRepaymentEvery(), lastRepaymentDate);
dueRepaymentPeriodDate = CalendarUtils.adjustDate(dueRepaymentPeriodDate, loanApplicationTerms.getSeedDate(),
loanApplicationTerms.getRepaymentPeriodFrequencyType());
if (currentCalendar != null) {
@@ -79,8 +81,6 @@ public class DefaultScheduledDateGenerator implements ScheduledDateGenerator {
}
// get the start date from the calendar history
LocalDate seedDate = null;
String reccuringString = null;
if (calendarHistory == null) {
seedDate = currentCalendar.getStartDateLocalDate();
reccuringString = currentCalendar.getRecurrence();
@@ -89,80 +89,130 @@ public class DefaultScheduledDateGenerator implements ScheduledDateGenerator {
reccuringString = calendarHistory.getRecurrence();
}
dueRepaymentPeriodDate = CalendarUtils.getNewRepaymentMeetingDate(reccuringString, seedDate, lastRepaymentDate.plusDays(1),
dueRepaymentPeriodDate = CalendarUtils.getNextRepaymentMeetingDate(reccuringString, seedDate, lastRepaymentDate,
loanApplicationTerms.getRepaymentEvery(),
CalendarUtils.getMeetingFrequencyFromPeriodFrequencyType(loanApplicationTerms.getLoanTermPeriodFrequencyType()),
holidayDetailDTO.getWorkingDays(), loanApplicationTerms.isSkipRepaymentOnFirstDayofMonth(),
loanApplicationTerms.isSkipRepaymentOnFirstDayofMonth(),
loanApplicationTerms.getNumberOfdays());
}
}
if (currentCalendar == null && holidayDetailDTO.getWorkingDays().getExtendTermForRepaymentsOnHolidays()) {
boolean repaymentDateIsOnHoliday = false;
// compile the list of holidays into Intervals to see if this date lands on a holiday
final List<Interval> holidayInterval = new ArrayList<>();
for (Holiday holiday : holidayDetailDTO.getHolidays()) {
holidayInterval.add(new Interval(
holiday.getFromDateLocalDate().toDateTimeAtStartOfDay(),
holiday.getToDateLocalDate().toDateTime(LocalTime.parse("23:59:59"))
));
}
while (intervalListContainsDate(holidayInterval, dueRepaymentPeriodDate)) {
LocalDate previousDate = dueRepaymentPeriodDate;
dueRepaymentPeriodDate = getRepaymentPeriodDate(loanApplicationTerms.getRepaymentPeriodFrequencyType(),
loanApplicationTerms.getRepaymentEvery(), dueRepaymentPeriodDate, loanApplicationTerms.getNthDay(),
loanApplicationTerms.getWeekDayType());
}
}
return dueRepaymentPeriodDate;
}
private boolean intervalListContainsDate(List<Interval> intervalList, LocalDate date) {
for (Interval interval : intervalList) {
if (interval.contains(date.toDateTime(LocalTime.parse("11:59:59")))) {
return true;
}
}
return false;
}
@Override
public LocalDate adjustRepaymentDate(final LocalDate dueRepaymentPeriodDate, final LoanApplicationTerms loanApplicationTerms,
final HolidayDetailDTO holidayDetailDTO) {
public AdjustedDateDetailsDTO adjustRepaymentDate(final LocalDate dueRepaymentPeriodDate,
final LoanApplicationTerms loanApplicationTerms, final HolidayDetailDTO holidayDetailDTO) {
final LocalDate adjustedDate = dueRepaymentPeriodDate;
return getAdjustedDateDetailsDTO(dueRepaymentPeriodDate, loanApplicationTerms, holidayDetailDTO, adjustedDate);
}
LocalDate adjustedDate = dueRepaymentPeriodDate;
private AdjustedDateDetailsDTO getAdjustedDateDetailsDTO(final LocalDate dueRepaymentPeriodDate,
final LoanApplicationTerms loanApplicationTerms, final HolidayDetailDTO holidayDetailDTO, final LocalDate adjustedDate) {
final boolean isFirstRepayment = false;
final LocalDate nextRepaymentPeriodDueDate = generateNextRepaymentDate(adjustedDate, loanApplicationTerms, isFirstRepayment);
final AdjustedDateDetailsDTO newAdjustedDateDetailsDTO = new AdjustedDateDetailsDTO(adjustedDate, dueRepaymentPeriodDate,
nextRepaymentPeriodDueDate);
return recursivelyCheckNonWorkingDaysAndHolidaysAndWorkingDaysExemptionToGenerateNextRepaymentPeriodDate(newAdjustedDateDetailsDTO,
loanApplicationTerms, holidayDetailDTO, isFirstRepayment);
}
LocalDate nextDueRepaymentPeriodDate = getRepaymentPeriodDate(loanApplicationTerms.getRepaymentPeriodFrequencyType(),
loanApplicationTerms.getRepaymentEvery(), adjustedDate, loanApplicationTerms.getNthDay(),
loanApplicationTerms.getWeekDayType());
final RepaymentRescheduleType rescheduleType = RepaymentRescheduleType.fromInt(holidayDetailDTO.getWorkingDays()
.getRepaymentReschedulingType());
/**
* Recursively checking non working days and holidays and working days
* exemption to generate next repayment period date Base on the
* configuration
*
* @param adjustedDateDetailsDTO
* @param loanApplicationTerms
* @param holidayDetailDTO
* @param nextRepaymentPeriodDueDate
* @param rescheduleType
* @param isFirstRepayment
* @return
*/
private AdjustedDateDetailsDTO recursivelyCheckNonWorkingDaysAndHolidaysAndWorkingDaysExemptionToGenerateNextRepaymentPeriodDate(
final AdjustedDateDetailsDTO adjustedDateDetailsDTO, final LoanApplicationTerms loanApplicationTerms,
final HolidayDetailDTO holidayDetailDTO, final boolean isFirstRepayment) {
checkAndUpdateWorkingDayIfRepaymentDateIsNonWorkingDay(adjustedDateDetailsDTO, holidayDetailDTO, loanApplicationTerms,
isFirstRepayment);
checkAndUpdateWorkingDayIfRepaymentDateIsHolidayDay(adjustedDateDetailsDTO, holidayDetailDTO, loanApplicationTerms,
isFirstRepayment);
/**
* Fix for https://mifosforge.jira.com/browse/MIFOSX-1357
* Check Changed Schedule Date is holiday or is not a working day Then
* re-call this method to get the non holiday and working day
*/
// 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());
if ((holidayDetailDTO.isHolidayEnabled() && HolidayUtil.getApplicableHoliday(adjustedDateDetailsDTO.getChangedScheduleDate(),
holidayDetailDTO.getHolidays()) != null)
|| WorkingDaysUtil.isNonWorkingDay(holidayDetailDTO.getWorkingDays(), adjustedDateDetailsDTO.getChangedScheduleDate())) {
recursivelyCheckNonWorkingDaysAndHolidaysAndWorkingDaysExemptionToGenerateNextRepaymentPeriodDate(adjustedDateDetailsDTO,
loanApplicationTerms, holidayDetailDTO, isFirstRepayment);
}
adjustedDate = WorkingDaysUtil.getOffSetDateIfNonWorkingDay(adjustedDate, nextDueRepaymentPeriodDate,
holidayDetailDTO.getWorkingDays());
return adjustedDateDetailsDTO;
}
/**
* This method to check and update the working day if repayment date is
* holiday
*
* @param adjustedDateDetailsDTO
* @param holidayDetailDTO
* @param loanApplicationTerms
* @param isFirstRepayment
*/
private void checkAndUpdateWorkingDayIfRepaymentDateIsHolidayDay(final AdjustedDateDetailsDTO adjustedDateDetailsDTO,
final HolidayDetailDTO holidayDetailDTO, final LoanApplicationTerms loanApplicationTerms, final boolean isFirstRepayment) {
if (holidayDetailDTO.isHolidayEnabled()) {
adjustedDate = HolidayUtil.getRepaymentRescheduleDateToIfHoliday(adjustedDate, holidayDetailDTO.getHolidays());
Holiday applicableHolidayForNewAdjustedDate = null;
while ((applicableHolidayForNewAdjustedDate = HolidayUtil.getApplicableHoliday(adjustedDateDetailsDTO.getChangedScheduleDate(),
holidayDetailDTO.getHolidays())) != null) {
if (applicableHolidayForNewAdjustedDate.getReScheduleType().isResheduleToNextRepaymentDate()) {
LocalDate nextRepaymentPeriodDueDate = adjustedDateDetailsDTO.getChangedActualRepaymentDate();
while (!nextRepaymentPeriodDueDate.isAfter(adjustedDateDetailsDTO.getChangedScheduleDate())) {
nextRepaymentPeriodDueDate = generateNextRepaymentDate(nextRepaymentPeriodDueDate, loanApplicationTerms,
isFirstRepayment);
}
adjustedDateDetailsDTO.setChangedScheduleDate(nextRepaymentPeriodDueDate);
adjustedDateDetailsDTO.setNextRepaymentPeriodDueDate(nextRepaymentPeriodDueDate);
adjustedDateDetailsDTO.setChangedActualRepaymentDate(adjustedDateDetailsDTO.getChangedScheduleDate());
} else {
HolidayUtil.updateRepaymentRescheduleDateToWorkingDayIfItIsHoliday(adjustedDateDetailsDTO,
applicableHolidayForNewAdjustedDate);
}
}
}
}
return adjustedDate;
/**
* This method to check and update the working day if repayment date is non
* working day
*
* @param adjustedDateDetailsDTO
* @param holidayDetailDTO
* @param isFirstRepayment
* @param loanApplicationTerms
*/
private void checkAndUpdateWorkingDayIfRepaymentDateIsNonWorkingDay(final AdjustedDateDetailsDTO adjustedDateDetailsDTO,
final HolidayDetailDTO holidayDetailDTO, final LoanApplicationTerms loanApplicationTerms, final boolean isFirstRepayment) {
while (WorkingDaysUtil.isNonWorkingDay(holidayDetailDTO.getWorkingDays(), adjustedDateDetailsDTO.getChangedScheduleDate())) {
if (WorkingDaysUtil.getRepaymentRescheduleType(holidayDetailDTO.getWorkingDays(),
adjustedDateDetailsDTO.getChangedScheduleDate()).isMoveToNextRepaymentDay()) {
while (WorkingDaysUtil.isNonWorkingDay(holidayDetailDTO.getWorkingDays(),
adjustedDateDetailsDTO.getNextRepaymentPeriodDueDate())
|| adjustedDateDetailsDTO.getChangedScheduleDate().isAfter(adjustedDateDetailsDTO.getNextRepaymentPeriodDueDate())) {
final LocalDate nextRepaymentPeriodDueDate = generateNextRepaymentDate(
adjustedDateDetailsDTO.getNextRepaymentPeriodDueDate(), loanApplicationTerms, isFirstRepayment);
adjustedDateDetailsDTO.setNextRepaymentPeriodDueDate(nextRepaymentPeriodDueDate);
}
}
WorkingDaysUtil.updateWorkingDayIfRepaymentDateIsNonWorkingDay(adjustedDateDetailsDTO, holidayDetailDTO.getWorkingDays());
}
}
@Override
public LocalDate getRepaymentPeriodDate(final PeriodFrequencyType frequency, final int repaidEvery, final LocalDate startDate,
Integer nthDay, DayOfWeekType dayOfWeek) {
public LocalDate getRepaymentPeriodDate(final PeriodFrequencyType frequency, final int repaidEvery, final LocalDate startDate) {
LocalDate dueRepaymentPeriodDate = startDate;
switch (frequency) {
case DAYS:
@@ -173,9 +223,6 @@ public class DefaultScheduledDateGenerator implements ScheduledDateGenerator {
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);
@@ -282,10 +329,10 @@ public class DefaultScheduledDateGenerator implements ScheduledDateGenerator {
LocalDate generatedDate = loanApplicationTerms.getExpectedDisbursementDate();
boolean isFirstRepayment = true;
while (!generatedDate.isAfter(lastRepaymentDate)) {
generatedDate = generateNextRepaymentDate(generatedDate, loanApplicationTerms, isFirstRepayment, holidayDetailDTO);
generatedDate = generateNextRepaymentDate(generatedDate, loanApplicationTerms, isFirstRepayment);
isFirstRepayment = false;
}
generatedDate = adjustRepaymentDate(generatedDate, loanApplicationTerms, holidayDetailDTO);
generatedDate = adjustRepaymentDate(generatedDate, loanApplicationTerms, holidayDetailDTO).getChangedScheduleDate();
return generatedDate;
}
}
@@ -183,7 +183,7 @@ public final class LoanApplicationTerms {
private Money adjustPrincipalForFlatLoans;
private final LocalDate seedDate;
private LocalDate seedDate;
private final CalendarHistoryDataWrapper calendarHistoryDataWrapper;
@@ -1742,5 +1742,9 @@ public final class LoanApplicationTerms {
public void updateTotalInterestAccounted(Money totalInterestAccounted){
this.totalInterestAccounted = totalInterestAccounted;
}
public void setSeedDate(LocalDate seedDate) {
this.seedDate = seedDate;
}
}
@@ -18,6 +18,7 @@
*/
package org.apache.fineract.portfolio.loanaccount.loanschedule.domain;
import org.apache.fineract.organisation.workingdays.data.AdjustedDateDetailsDTO;
import org.apache.fineract.portfolio.calendar.domain.Calendar;
import org.apache.fineract.portfolio.common.domain.DayOfWeekType;
import org.apache.fineract.portfolio.common.domain.PeriodFrequencyType;
@@ -32,14 +33,12 @@ public interface ScheduledDateGenerator {
final LocalDate firstRepaymentDate, final Calendar loanCalendar, final HolidayDetailDTO holidayDetailDTO,
final LoanApplicationTerms loanApplicationTerms);
LocalDate generateNextRepaymentDate(LocalDate lastRepaymentDate, LoanApplicationTerms loanApplicationTerms, boolean isFirstRepayment,
LocalDate generateNextRepaymentDate(LocalDate lastRepaymentDate, LoanApplicationTerms loanApplicationTerms, boolean isFirstRepayment);
AdjustedDateDetailsDTO adjustRepaymentDate(LocalDate dueRepaymentPeriodDate, LoanApplicationTerms loanApplicationTerms,
final HolidayDetailDTO holidayDetailDTO);
LocalDate adjustRepaymentDate(LocalDate dueRepaymentPeriodDate, LoanApplicationTerms loanApplicationTerms,
final HolidayDetailDTO holidayDetailDTO);
LocalDate getRepaymentPeriodDate(PeriodFrequencyType frequency, int repaidEvery, LocalDate startDate, Integer nthDay,
DayOfWeekType dayOfWeek);
LocalDate getRepaymentPeriodDate(PeriodFrequencyType frequency, int repaidEvery, LocalDate startDate);
Boolean isDateFallsInSchedule(PeriodFrequencyType frequency, int repaidEvery, LocalDate startDate, LocalDate date);
@@ -114,10 +114,10 @@ public class LoanReschedulePreviewPlatformServiceImpl implements LoanRescheduleP
for (LoanTermVariationsData loanTermVariation : loanApplicationTerms.getLoanTermVariations().getDueDateVariation()) {
if (rescheduleFromDate.isBefore(loanTermVariation.getTermApplicableFrom())) {
LocalDate applicableDate = this.scheduledDateGenerator.generateNextRepaymentDate(rescheduleFromDate, loanApplicationTerms,
false, loanApplicationTerms.getHolidayDetailDTO());
false);
if (loanTermVariation.getTermApplicableFrom().equals(applicableDate)) {
LocalDate adjustedDate = this.scheduledDateGenerator.generateNextRepaymentDate(adjustedApplicableDate,
loanApplicationTerms, false, loanApplicationTerms.getHolidayDetailDTO());
loanApplicationTerms, false);
loanTermVariation.setApplicableFromDate(adjustedDate);
loanTermVariationsData.add(loanTermVariation);
}
@@ -410,9 +410,9 @@ public class LoanRescheduleRequestWritePlatformServiceImpl implements LoanResche
} else if (!activeLoanTermVariation.fetchTermApplicaDate().isBefore(fromScheduleDate)) {
while (currentScheduleDate.isBefore(activeLoanTermVariation.fetchTermApplicaDate())) {
currentScheduleDate = this.scheduledDateGenerator.generateNextRepaymentDate(currentScheduleDate,
loanApplicationTerms, false, loanApplicationTerms.getHolidayDetailDTO());
loanApplicationTerms, false);
modifiedScheduleDate = this.scheduledDateGenerator.generateNextRepaymentDate(modifiedScheduleDate,
loanApplicationTerms, false, loanApplicationTerms.getHolidayDetailDTO());
loanApplicationTerms, false);
changeMap.put(currentScheduleDate, modifiedScheduleDate);
}
if (changeMap.containsKey(activeLoanTermVariation.fetchTermApplicaDate())) {
@@ -2421,7 +2421,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
while (!startDate.isAfter(DateUtils.getLocalDateOfTenant())) {
scheduleDates.put(frequencyNunber++, startDate.minusDays(diff.intValue()));
LocalDate scheduleDate = scheduledDateGenerator.getRepaymentPeriodDate(PeriodFrequencyType.fromInt(feeFrequency),
chargeDefinition.feeInterval(), startDate, null, null);
chargeDefinition.feeInterval(), startDate);
startDate = scheduleDate;
}
@@ -0,0 +1,23 @@
--
-- 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.
--
ALTER TABLE m_holiday
MODIFY COLUMN repayments_rescheduled_to DATETIME NULL DEFAULT NULL,
ADD COLUMN `rescheduling_type` INT(5) NOT NULL DEFAULT '2';