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 repaymentsRescheduledToParamName = "repaymentsRescheduledTo";
public static final String processed = "processed"; public static final String processed = "processed";
public static final String status = "status"; public static final String status = "status";
public static final String reschedulingType = "reschedulingType";
protected static final Set<String> HOLIDAY_RESPONSE_DATA_PARAMETERS = new HashSet<>( protected static final Set<String> HOLIDAY_RESPONSE_DATA_PARAMETERS = new HashSet<>(
Arrays.asList(idParamName, nameParamName, fromDateParamName, descriptionParamName, toDateParamName, Arrays.asList(idParamName, nameParamName, fromDateParamName, descriptionParamName, toDateParamName,
@@ -180,4 +180,13 @@ public class HolidaysApiResource {
final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters()); final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
return this.toApiJsonSerializer.serialize(settings, holidays, HOLIDAY_RESPONSE_DATA_PARAMETERS); 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; private final Long officeId;
@SuppressWarnings("unused") @SuppressWarnings("unused")
private final EnumOptionData status; 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, 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.id = id;
this.name = name; this.name = name;
this.description = description; this.description = description;
@@ -50,5 +51,6 @@ public class HolidayData {
this.repaymentsRescheduledTo = repaymentsRescheduledTo; this.repaymentsRescheduledTo = repaymentsRescheduledTo;
this.officeId = null; this.officeId = null;
this.status = status; this.status = status;
this.reschedulingType = reschedulingType;
} }
} }
@@ -23,6 +23,7 @@ import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Locale;
import java.util.Map; import java.util.Map;
import java.util.Set; 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.exception.PlatformApiDataValidationException;
import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper; import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
import org.apache.fineract.organisation.holiday.api.HolidayApiConstants; import org.apache.fineract.organisation.holiday.api.HolidayApiConstants;
import org.apache.fineract.organisation.holiday.domain.RescheduleType;
import org.joda.time.LocalDate; import org.joda.time.LocalDate;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
@@ -50,7 +52,8 @@ public class HolidayDataValidator {
Arrays.asList(HolidayApiConstants.localeParamName, HolidayApiConstants.dateFormatParamName, Arrays.asList(HolidayApiConstants.localeParamName, HolidayApiConstants.dateFormatParamName,
HolidayApiConstants.nameParamName, HolidayApiConstants.fromDateParamName, HolidayApiConstants.nameParamName, HolidayApiConstants.fromDateParamName,
HolidayApiConstants.toDateParamName, HolidayApiConstants.descriptionParamName, HolidayApiConstants.toDateParamName, HolidayApiConstants.descriptionParamName,
HolidayApiConstants.officesParamName, HolidayApiConstants.repaymentsRescheduledToParamName)); HolidayApiConstants.officesParamName, HolidayApiConstants.repaymentsRescheduledToParamName,
HolidayApiConstants.reschedulingType));
@Autowired @Autowired
public HolidayDataValidator(final FromJsonHelper fromApiJsonHelper) { public HolidayDataValidator(final FromJsonHelper fromApiJsonHelper) {
@@ -79,11 +82,18 @@ public class HolidayDataValidator {
final LocalDate toDate = this.fromApiJsonHelper.extractLocalDateNamed(HolidayApiConstants.toDateParamName, element); final LocalDate toDate = this.fromApiJsonHelper.extractLocalDateNamed(HolidayApiConstants.toDateParamName, element);
baseDataValidator.reset().parameter(HolidayApiConstants.toDateParamName).value(toDate).notNull(); baseDataValidator.reset().parameter(HolidayApiConstants.toDateParamName).value(toDate).notNull();
final LocalDate repaymentsRescheduledTo = this.fromApiJsonHelper.extractLocalDateNamed( Integer reschedulingType = null;
HolidayApiConstants.repaymentsRescheduledToParamName, element); if(this.fromApiJsonHelper.parameterExists(HolidayApiConstants.reschedulingType, element)){
baseDataValidator.reset().parameter(HolidayApiConstants.repaymentsRescheduledToParamName).value(repaymentsRescheduledTo).notNull(); 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; Set<Long> offices = null;
final JsonObject topLevelJsonElement = element.getAsJsonObject(); final JsonObject topLevelJsonElement = element.getAsJsonObject();
@@ -73,9 +73,12 @@ public class Holiday extends AbstractPersistableCustom<Long> {
@Temporal(TemporalType.DATE) @Temporal(TemporalType.DATE)
private Date toDate; private Date toDate;
@Column(name = "repayments_rescheduled_to", nullable = false) @Column(name = "repayments_rescheduled_to", nullable = true)
@Temporal(TemporalType.DATE) @Temporal(TemporalType.DATE)
private Date repaymentsRescheduledTo; private Date repaymentsRescheduledTo;
@Column(name = "rescheduling_type", nullable = false)
private int reschedulingType;
@Column(name = "status_enum", nullable = false) @Column(name = "status_enum", nullable = false)
private Integer status; private Integer status;
@@ -94,13 +97,21 @@ public class Holiday extends AbstractPersistableCustom<Long> {
final String name = command.stringValueOfParameterNamed(HolidayApiConstants.nameParamName); final String name = command.stringValueOfParameterNamed(HolidayApiConstants.nameParamName);
final LocalDate fromDate = command.localDateValueOfParameterNamed(HolidayApiConstants.fromDateParamName); final LocalDate fromDate = command.localDateValueOfParameterNamed(HolidayApiConstants.fromDateParamName);
final LocalDate toDate = command.localDateValueOfParameterNamed(HolidayApiConstants.toDateParamName); final LocalDate toDate = command.localDateValueOfParameterNamed(HolidayApiConstants.toDateParamName);
final LocalDate repaymentsRescheduledTo = command Integer reschedulingType = null;
.localDateValueOfParameterNamed(HolidayApiConstants.repaymentsRescheduledToParamName); 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 Integer status = HolidayStatusType.PENDING_FOR_ACTIVATION.getValue();
final boolean processed = false;// default it to false. Only batch job final boolean processed = false;// default it to false. Only batch job
// should update this field. // should update this field.
final String description = command.stringValueOfParameterNamed(HolidayApiConstants.descriptionParamName); 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) { public Map<String, Object> update(final JsonCommand command) {
@@ -125,6 +136,16 @@ public class Holiday extends AbstractPersistableCustom<Long> {
actualChanges.put(descriptionParamName, newValue); actualChanges.put(descriptionParamName, newValue);
this.description = StringUtils.defaultIfEmpty(newValue, null); 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 (currentStatus.isPendingActivation()) {
if (command.isChangeInLocalDateParameterNamed(fromDateParamName, getFromDateLocalDate())) { 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, 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)) { if (StringUtils.isNotBlank(name)) {
this.name = name.trim(); this.name = name.trim();
} }
@@ -234,6 +255,7 @@ public class Holiday extends AbstractPersistableCustom<Long> {
if (offices != null) { if (offices != null) {
this.offices = offices; this.offices = offices;
} }
this.reschedulingType = reschedulingType;
} }
protected Holiday() {} protected Holiday() {}
@@ -298,4 +320,8 @@ public class Holiday extends AbstractPersistableCustom<Long> {
} }
this.status = HolidayStatusType.DELETED.getValue(); 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.infrastructure.core.data.EnumOptionData;
import org.apache.fineract.organisation.holiday.domain.HolidayStatusType; import org.apache.fineract.organisation.holiday.domain.HolidayStatusType;
import org.apache.fineract.organisation.holiday.domain.RescheduleType;
public class HolidayEnumerations { public class HolidayEnumerations {
@@ -49,4 +50,29 @@ public class HolidayEnumerations {
} }
return optionData; 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.Collection;
import java.util.Date; import java.util.Date;
import java.util.List;
import org.apache.fineract.infrastructure.core.data.EnumOptionData;
import org.apache.fineract.organisation.holiday.data.HolidayData; import org.apache.fineract.organisation.holiday.data.HolidayData;
public interface HolidayReadPlatformService { public interface HolidayReadPlatformService {
@@ -28,4 +30,6 @@ public interface HolidayReadPlatformService {
Collection<HolidayData> retrieveAllHolidaysBySearchParamerters(final Long officeId, Date fromDate, Date toDate); Collection<HolidayData> retrieveAllHolidaysBySearchParamerters(final Long officeId, Date fromDate, Date toDate);
HolidayData retrieveHoliday(final Long holidayId); HolidayData retrieveHoliday(final Long holidayId);
List<EnumOptionData> retrieveRepaymentScheduleUpdationTyeOptions();
} }
@@ -25,12 +25,14 @@ import java.text.SimpleDateFormat;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
import java.util.Date; import java.util.Date;
import java.util.List;
import org.apache.fineract.infrastructure.core.data.EnumOptionData; import org.apache.fineract.infrastructure.core.data.EnumOptionData;
import org.apache.fineract.infrastructure.core.domain.JdbcSupport; import org.apache.fineract.infrastructure.core.domain.JdbcSupport;
import org.apache.fineract.infrastructure.core.service.RoutingDataSource; import org.apache.fineract.infrastructure.core.service.RoutingDataSource;
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext; import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
import org.apache.fineract.organisation.holiday.data.HolidayData; 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.apache.fineract.organisation.holiday.exception.HolidayNotFoundException;
import org.joda.time.LocalDate; import org.joda.time.LocalDate;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
@@ -58,7 +60,7 @@ public class HolidayReadPlatformServiceImpl implements HolidayReadPlatformServic
public HolidayMapper() { public HolidayMapper() {
final StringBuilder sqlBuilder = new StringBuilder(200); 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.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 "); sqlBuilder.append("from m_holiday h ");
this.schema = sqlBuilder.toString(); this.schema = sqlBuilder.toString();
} }
@@ -76,9 +78,10 @@ public class HolidayReadPlatformServiceImpl implements HolidayReadPlatformServic
final LocalDate toDate = JdbcSupport.getLocalDate(rs, "toDate"); final LocalDate toDate = JdbcSupport.getLocalDate(rs, "toDate");
final LocalDate repaymentsScheduleTO = JdbcSupport.getLocalDate(rs, "repaymentsScheduleTO"); final LocalDate repaymentsScheduleTO = JdbcSupport.getLocalDate(rs, "repaymentsScheduleTO");
final Integer statusEnum = JdbcSupport.getInteger(rs, "statusEnum"); final Integer statusEnum = JdbcSupport.getInteger(rs, "statusEnum");
final Integer reschedulingType = JdbcSupport.getInteger(rs, "reschedulingType");
final EnumOptionData status = HolidayEnumerations.holidayStatusType(statusEnum); 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); 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 java.util.List;
import org.apache.fineract.organisation.holiday.domain.Holiday; import org.apache.fineract.organisation.holiday.domain.Holiday;
import org.apache.fineract.organisation.workingdays.data.AdjustedDateDetailsDTO;
import org.joda.time.LocalDate; import org.joda.time.LocalDate;
public class HolidayUtil { public class HolidayUtil {
@@ -56,4 +57,20 @@ public class HolidayUtil {
return false; 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) { private void validateInputDates(final JsonCommand command) {
final LocalDate fromDate = command.localDateValueOfParameterNamed(HolidayApiConstants.fromDateParamName); final LocalDate fromDate = command.localDateValueOfParameterNamed(HolidayApiConstants.fromDateParamName);
final LocalDate toDate = command.localDateValueOfParameterNamed(HolidayApiConstants.toDateParamName); final LocalDate toDate = command.localDateValueOfParameterNamed(HolidayApiConstants.toDateParamName);
final LocalDate repaymentsRescheduledTo = command Integer reshedulingType = null;
.localDateValueOfParameterNamed(HolidayApiConstants.repaymentsRescheduledToParamName); if(command.parameterExists(HolidayApiConstants.reschedulingType)){
this.validateInputDates(fromDate, toDate, repaymentsRescheduledTo); 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) { 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(), throw new HolidayDateException("to.date.cannot.be.before.from.date", defaultUserMessage, fromDate.toString(),
toDate.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) defaultUserMessage = "Repayments rescheduled date should be before from date or after to date.";
|| (repaymentsRescheduledTo.isAfter(fromDate) && repaymentsRescheduledTo.isBefore(toDate))) { 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."; final WorkingDays workingDays = this.daysRepositoryWrapper.findOne();
throw new HolidayDateException("repayments.rescheduled.date.should.be.before.from.date.or.after.to.date", defaultUserMessage, final Boolean isRepaymentOnWorkingDay = WorkingDaysUtil.isWorkingDay(workingDays, repaymentsRescheduledTo);
repaymentsRescheduledTo.toString());
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, 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.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.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 Integer value;
private final String code; private final String code;
@@ -43,6 +44,10 @@ public enum RepaymentRescheduleType {
public String getCode() { public String getCode() {
return this.code; 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 final Map<Integer, RepaymentRescheduleType> intToEnumMap = new HashMap<>();
private static int minValue; private static int minValue;
@@ -18,6 +18,7 @@
*/ */
package org.apache.fineract.organisation.workingdays.service; 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.RepaymentRescheduleType;
import org.apache.fineract.organisation.workingdays.domain.WorkingDays; import org.apache.fineract.organisation.workingdays.domain.WorkingDays;
import org.apache.fineract.portfolio.calendar.service.CalendarUtils; 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) { public static boolean isNonWorkingDay(final WorkingDays workingDays, final LocalDate date) {
return !isWorkingDay(workingDays, 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; 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.ApplicationCurrency;
import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency; import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
import org.apache.fineract.organisation.monetary.domain.Money; 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.organisation.workingdays.domain.RepaymentRescheduleType;
import org.apache.fineract.portfolio.calendar.domain.CalendarInstance; import org.apache.fineract.portfolio.calendar.domain.CalendarInstance;
import org.apache.fineract.portfolio.calendar.service.CalendarUtils; import org.apache.fineract.portfolio.calendar.service.CalendarUtils;
@@ -121,7 +122,7 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
boolean isFirstRepayment = true; boolean isFirstRepayment = true;
LocalDate firstRepaymentdate = this.scheduledDateGenerator.generateNextRepaymentDate( LocalDate firstRepaymentdate = this.scheduledDateGenerator.generateNextRepaymentDate(
loanApplicationTerms.getExpectedDisbursementDate(), loanApplicationTerms, isFirstRepayment, holidayDetailDTO); loanApplicationTerms.getExpectedDisbursementDate(), loanApplicationTerms, isFirstRepayment);
final LocalDate idealDisbursementDate = this.scheduledDateGenerator.idealDisbursementDateBasedOnFirstRepaymentDate( final LocalDate idealDisbursementDate = this.scheduledDateGenerator.idealDisbursementDateBasedOnFirstRepaymentDate(
loanApplicationTerms.getLoanTermPeriodFrequencyType(), loanApplicationTerms.getRepaymentEvery(), firstRepaymentdate, loanApplicationTerms.getLoanTermPeriodFrequencyType(), loanApplicationTerms.getRepaymentEvery(), firstRepaymentdate,
loanApplicationTerms.getLoanCalendar(), loanApplicationTerms.getHolidayDetailDTO(), loanApplicationTerms); loanApplicationTerms.getLoanCalendar(), loanApplicationTerms.getHolidayDetailDTO(), loanApplicationTerms);
@@ -179,10 +180,12 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
while (!scheduleParams.getOutstandingBalance().isZero() || !scheduleParams.getDisburseDetailMap().isEmpty()) { while (!scheduleParams.getOutstandingBalance().isZero() || !scheduleParams.getDisburseDetailMap().isEmpty()) {
LocalDate previousRepaymentDate = scheduleParams.getActualRepaymentDate(); LocalDate previousRepaymentDate = scheduleParams.getActualRepaymentDate();
scheduleParams.setActualRepaymentDate(this.scheduledDateGenerator.generateNextRepaymentDate( 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; isFirstRepayment = false;
LocalDate scheduledDueDate = this.scheduledDateGenerator.adjustRepaymentDate(scheduleParams.getActualRepaymentDate(), LocalDate scheduledDueDate = adjustedDateDetailsDTO.getChangedScheduleDate();
loanApplicationTerms, holidayDetailDTO);
// calculated interest start date for the period // calculated interest start date for the period
LocalDate periodStartDateApplicableForInterest = calculateInterestStartDateForPeriod(loanApplicationTerms, LocalDate periodStartDateApplicableForInterest = calculateInterestStartDateForPeriod(loanApplicationTerms,
@@ -1320,7 +1323,7 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
do { do {
params.setActualRepaymentDate(this.scheduledDateGenerator.generateNextRepaymentDate(params.getActualRepaymentDate(), params.setActualRepaymentDate(this.scheduledDateGenerator.generateNextRepaymentDate(params.getActualRepaymentDate(),
loanApplicationTerms, isFirstRepayment, holidayDetailDTO)); loanApplicationTerms, isFirstRepayment));
if (params.getActualRepaymentDate().isAfter(currentDate)) { if (params.getActualRepaymentDate().isAfter(currentDate)) {
params.setActualRepaymentDate(currentDate); params.setActualRepaymentDate(currentDate);
} }
@@ -2172,41 +2175,17 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
ArrayList<LoanTermVariationsData> dueDateVariationsDataList = new ArrayList<>(); ArrayList<LoanTermVariationsData> dueDateVariationsDataList = new ArrayList<>();
// check for date changes // check for date changes
do { while (loanApplicationTerms.getLoanTermVariations().hasDueDateVariation(lastInstallmentDate)) {
actualRepaymentDate = this.scheduledDateGenerator.generateNextRepaymentDate(actualRepaymentDate, LoanTermVariationsData variation = loanApplicationTerms.getLoanTermVariations().nextDueDateVariation();
loanApplicationTerms, isFirstRepayment, holidayDetailDTO); if (!variation.isSpecificToInstallment()) {
isFirstRepayment = false; actualRepaymentDate = variation.getDateValue();
lastInstallmentDate = this.scheduledDateGenerator.adjustRepaymentDate(actualRepaymentDate, loanApplicationTerms, loanApplicationTerms.setSeedDate(actualRepaymentDate);
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);
} }
loanTermVariationParams = applyExceptionLoanTermVariations(loanApplicationTerms, lastInstallmentDate, lastInstallmentDate = variation.getDateValue();
exceptionDataListIterator, instalmentNumber, totalCumulativePrincipal, totalCumulativeInterest, mc); dueDateVariationsDataList.add(variation);
} 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;
}*/
periodNumber++; periodNumber++;
for (LoanTermVariationsData dueDateVariation : dueDateVariationsDataList) { 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.domain.Holiday;
import org.apache.fineract.organisation.holiday.service.HolidayUtil; 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.RepaymentRescheduleType;
import org.apache.fineract.organisation.workingdays.domain.WorkingDays; import org.apache.fineract.organisation.workingdays.domain.WorkingDays;
import org.apache.fineract.organisation.workingdays.service.WorkingDaysUtil; import org.apache.fineract.organisation.workingdays.service.WorkingDaysUtil;
@@ -45,25 +46,26 @@ public class DefaultScheduledDateGenerator implements ScheduledDateGenerator {
LocalDate lastRepaymentDate = loanApplicationTerms.getExpectedDisbursementDate(); LocalDate lastRepaymentDate = loanApplicationTerms.getExpectedDisbursementDate();
boolean isFirstRepayment = true; boolean isFirstRepayment = true;
for (int repaymentPeriod = 1; repaymentPeriod <= numberOfRepayments; repaymentPeriod++) { for (int repaymentPeriod = 1; repaymentPeriod <= numberOfRepayments; repaymentPeriod++) {
lastRepaymentDate = generateNextRepaymentDate(lastRepaymentDate, loanApplicationTerms, isFirstRepayment, holidayDetailDTO); lastRepaymentDate = generateNextRepaymentDate(lastRepaymentDate, loanApplicationTerms, isFirstRepayment);
isFirstRepayment = false; isFirstRepayment = false;
} }
lastRepaymentDate = adjustRepaymentDate(lastRepaymentDate, loanApplicationTerms, holidayDetailDTO); lastRepaymentDate = adjustRepaymentDate(lastRepaymentDate, loanApplicationTerms, holidayDetailDTO).getChangedScheduleDate();
return lastRepaymentDate; return lastRepaymentDate;
} }
@Override @Override
public LocalDate generateNextRepaymentDate(final LocalDate lastRepaymentDate, final LoanApplicationTerms loanApplicationTerms, public LocalDate generateNextRepaymentDate(final LocalDate lastRepaymentDate, final LoanApplicationTerms loanApplicationTerms,
boolean isFirstRepayment, final HolidayDetailDTO holidayDetailDTO) { boolean isFirstRepayment) {
final LocalDate firstRepaymentPeriodDate = loanApplicationTerms.getCalculatedRepaymentsStartingFromLocalDate(); final LocalDate firstRepaymentPeriodDate = loanApplicationTerms.getCalculatedRepaymentsStartingFromLocalDate();
LocalDate dueRepaymentPeriodDate = null; LocalDate dueRepaymentPeriodDate = null;
Calendar currentCalendar = loanApplicationTerms.getLoanCalendar();
if (isFirstRepayment && firstRepaymentPeriodDate != null) { if (isFirstRepayment && firstRepaymentPeriodDate != null) {
dueRepaymentPeriodDate = firstRepaymentPeriodDate; dueRepaymentPeriodDate = firstRepaymentPeriodDate;
} else { } else {
LocalDate seedDate = null;
String reccuringString = null;
Calendar currentCalendar = loanApplicationTerms.getLoanCalendar();
dueRepaymentPeriodDate = getRepaymentPeriodDate(loanApplicationTerms.getRepaymentPeriodFrequencyType(), dueRepaymentPeriodDate = getRepaymentPeriodDate(loanApplicationTerms.getRepaymentPeriodFrequencyType(),
loanApplicationTerms.getRepaymentEvery(), lastRepaymentDate, null, loanApplicationTerms.getRepaymentEvery(), lastRepaymentDate);
null);
dueRepaymentPeriodDate = CalendarUtils.adjustDate(dueRepaymentPeriodDate, loanApplicationTerms.getSeedDate(), dueRepaymentPeriodDate = CalendarUtils.adjustDate(dueRepaymentPeriodDate, loanApplicationTerms.getSeedDate(),
loanApplicationTerms.getRepaymentPeriodFrequencyType()); loanApplicationTerms.getRepaymentPeriodFrequencyType());
if (currentCalendar != null) { if (currentCalendar != null) {
@@ -79,8 +81,6 @@ public class DefaultScheduledDateGenerator implements ScheduledDateGenerator {
} }
// get the start date from the calendar history // get the start date from the calendar history
LocalDate seedDate = null;
String reccuringString = null;
if (calendarHistory == null) { if (calendarHistory == null) {
seedDate = currentCalendar.getStartDateLocalDate(); seedDate = currentCalendar.getStartDateLocalDate();
reccuringString = currentCalendar.getRecurrence(); reccuringString = currentCalendar.getRecurrence();
@@ -89,80 +89,130 @@ public class DefaultScheduledDateGenerator implements ScheduledDateGenerator {
reccuringString = calendarHistory.getRecurrence(); reccuringString = calendarHistory.getRecurrence();
} }
dueRepaymentPeriodDate = CalendarUtils.getNewRepaymentMeetingDate(reccuringString, seedDate, lastRepaymentDate.plusDays(1), dueRepaymentPeriodDate = CalendarUtils.getNextRepaymentMeetingDate(reccuringString, seedDate, lastRepaymentDate,
loanApplicationTerms.getRepaymentEvery(), loanApplicationTerms.getRepaymentEvery(),
CalendarUtils.getMeetingFrequencyFromPeriodFrequencyType(loanApplicationTerms.getLoanTermPeriodFrequencyType()), CalendarUtils.getMeetingFrequencyFromPeriodFrequencyType(loanApplicationTerms.getLoanTermPeriodFrequencyType()),
holidayDetailDTO.getWorkingDays(), loanApplicationTerms.isSkipRepaymentOnFirstDayofMonth(), loanApplicationTerms.isSkipRepaymentOnFirstDayofMonth(),
loanApplicationTerms.getNumberOfdays()); 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; 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 @Override
public LocalDate adjustRepaymentDate(final LocalDate dueRepaymentPeriodDate, final LoanApplicationTerms loanApplicationTerms, public AdjustedDateDetailsDTO adjustRepaymentDate(final LocalDate dueRepaymentPeriodDate,
final HolidayDetailDTO holidayDetailDTO) { 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(), * Recursively checking non working days and holidays and working days
loanApplicationTerms.getWeekDayType()); * exemption to generate next repayment period date Base on the
* configuration
final RepaymentRescheduleType rescheduleType = RepaymentRescheduleType.fromInt(holidayDetailDTO.getWorkingDays() *
.getRepaymentReschedulingType()); * @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. if ((holidayDetailDTO.isHolidayEnabled() && HolidayUtil.getApplicableHoliday(adjustedDateDetailsDTO.getChangedScheduleDate(),
while (WorkingDaysUtil.isNonWorkingDay(holidayDetailDTO.getWorkingDays(), nextDueRepaymentPeriodDate) holidayDetailDTO.getHolidays()) != null)
&& rescheduleType == RepaymentRescheduleType.MOVE_TO_NEXT_REPAYMENT_MEETING_DAY) { || WorkingDaysUtil.isNonWorkingDay(holidayDetailDTO.getWorkingDays(), adjustedDateDetailsDTO.getChangedScheduleDate())) {
recursivelyCheckNonWorkingDaysAndHolidaysAndWorkingDaysExemptionToGenerateNextRepaymentPeriodDate(adjustedDateDetailsDTO,
nextDueRepaymentPeriodDate = getRepaymentPeriodDate(loanApplicationTerms.getRepaymentPeriodFrequencyType(), loanApplicationTerms, holidayDetailDTO, isFirstRepayment);
loanApplicationTerms.getRepaymentEvery(), nextDueRepaymentPeriodDate, loanApplicationTerms.getNthDay(),
loanApplicationTerms.getWeekDayType());
nextDueRepaymentPeriodDate = CalendarUtils.adjustDate(nextDueRepaymentPeriodDate, loanApplicationTerms.getSeedDate(),
loanApplicationTerms.getRepaymentPeriodFrequencyType());
} }
adjustedDate = WorkingDaysUtil.getOffSetDateIfNonWorkingDay(adjustedDate, nextDueRepaymentPeriodDate, return adjustedDateDetailsDTO;
holidayDetailDTO.getWorkingDays()); }
/**
* 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()) { 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 @Override
public LocalDate getRepaymentPeriodDate(final PeriodFrequencyType frequency, final int repaidEvery, final LocalDate startDate, public LocalDate getRepaymentPeriodDate(final PeriodFrequencyType frequency, final int repaidEvery, final LocalDate startDate) {
Integer nthDay, DayOfWeekType dayOfWeek) {
LocalDate dueRepaymentPeriodDate = startDate; LocalDate dueRepaymentPeriodDate = startDate;
switch (frequency) { switch (frequency) {
case DAYS: case DAYS:
@@ -173,9 +223,6 @@ public class DefaultScheduledDateGenerator implements ScheduledDateGenerator {
break; break;
case MONTHS: case MONTHS:
dueRepaymentPeriodDate = startDate.plusMonths(repaidEvery); dueRepaymentPeriodDate = startDate.plusMonths(repaidEvery);
if (!(nthDay == null || dayOfWeek == null || dayOfWeek == DayOfWeekType.INVALID)) {
dueRepaymentPeriodDate = adjustToNthWeekDay(dueRepaymentPeriodDate, nthDay, dayOfWeek.getValue());
}
break; break;
case YEARS: case YEARS:
dueRepaymentPeriodDate = startDate.plusYears(repaidEvery); dueRepaymentPeriodDate = startDate.plusYears(repaidEvery);
@@ -282,10 +329,10 @@ public class DefaultScheduledDateGenerator implements ScheduledDateGenerator {
LocalDate generatedDate = loanApplicationTerms.getExpectedDisbursementDate(); LocalDate generatedDate = loanApplicationTerms.getExpectedDisbursementDate();
boolean isFirstRepayment = true; boolean isFirstRepayment = true;
while (!generatedDate.isAfter(lastRepaymentDate)) { while (!generatedDate.isAfter(lastRepaymentDate)) {
generatedDate = generateNextRepaymentDate(generatedDate, loanApplicationTerms, isFirstRepayment, holidayDetailDTO); generatedDate = generateNextRepaymentDate(generatedDate, loanApplicationTerms, isFirstRepayment);
isFirstRepayment = false; isFirstRepayment = false;
} }
generatedDate = adjustRepaymentDate(generatedDate, loanApplicationTerms, holidayDetailDTO); generatedDate = adjustRepaymentDate(generatedDate, loanApplicationTerms, holidayDetailDTO).getChangedScheduleDate();
return generatedDate; return generatedDate;
} }
} }
@@ -183,7 +183,7 @@ public final class LoanApplicationTerms {
private Money adjustPrincipalForFlatLoans; private Money adjustPrincipalForFlatLoans;
private final LocalDate seedDate; private LocalDate seedDate;
private final CalendarHistoryDataWrapper calendarHistoryDataWrapper; private final CalendarHistoryDataWrapper calendarHistoryDataWrapper;
@@ -1742,5 +1742,9 @@ public final class LoanApplicationTerms {
public void updateTotalInterestAccounted(Money totalInterestAccounted){ public void updateTotalInterestAccounted(Money totalInterestAccounted){
this.totalInterestAccounted = totalInterestAccounted; this.totalInterestAccounted = totalInterestAccounted;
} }
public void setSeedDate(LocalDate seedDate) {
this.seedDate = seedDate;
}
} }
@@ -18,6 +18,7 @@
*/ */
package org.apache.fineract.portfolio.loanaccount.loanschedule.domain; 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.calendar.domain.Calendar;
import org.apache.fineract.portfolio.common.domain.DayOfWeekType; import org.apache.fineract.portfolio.common.domain.DayOfWeekType;
import org.apache.fineract.portfolio.common.domain.PeriodFrequencyType; 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 LocalDate firstRepaymentDate, final Calendar loanCalendar, final HolidayDetailDTO holidayDetailDTO,
final LoanApplicationTerms loanApplicationTerms); 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); final HolidayDetailDTO holidayDetailDTO);
LocalDate adjustRepaymentDate(LocalDate dueRepaymentPeriodDate, LoanApplicationTerms loanApplicationTerms, LocalDate getRepaymentPeriodDate(PeriodFrequencyType frequency, int repaidEvery, LocalDate startDate);
final HolidayDetailDTO holidayDetailDTO);
LocalDate getRepaymentPeriodDate(PeriodFrequencyType frequency, int repaidEvery, LocalDate startDate, Integer nthDay,
DayOfWeekType dayOfWeek);
Boolean isDateFallsInSchedule(PeriodFrequencyType frequency, int repaidEvery, LocalDate startDate, LocalDate date); 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()) { for (LoanTermVariationsData loanTermVariation : loanApplicationTerms.getLoanTermVariations().getDueDateVariation()) {
if (rescheduleFromDate.isBefore(loanTermVariation.getTermApplicableFrom())) { if (rescheduleFromDate.isBefore(loanTermVariation.getTermApplicableFrom())) {
LocalDate applicableDate = this.scheduledDateGenerator.generateNextRepaymentDate(rescheduleFromDate, loanApplicationTerms, LocalDate applicableDate = this.scheduledDateGenerator.generateNextRepaymentDate(rescheduleFromDate, loanApplicationTerms,
false, loanApplicationTerms.getHolidayDetailDTO()); false);
if (loanTermVariation.getTermApplicableFrom().equals(applicableDate)) { if (loanTermVariation.getTermApplicableFrom().equals(applicableDate)) {
LocalDate adjustedDate = this.scheduledDateGenerator.generateNextRepaymentDate(adjustedApplicableDate, LocalDate adjustedDate = this.scheduledDateGenerator.generateNextRepaymentDate(adjustedApplicableDate,
loanApplicationTerms, false, loanApplicationTerms.getHolidayDetailDTO()); loanApplicationTerms, false);
loanTermVariation.setApplicableFromDate(adjustedDate); loanTermVariation.setApplicableFromDate(adjustedDate);
loanTermVariationsData.add(loanTermVariation); loanTermVariationsData.add(loanTermVariation);
} }
@@ -410,9 +410,9 @@ public class LoanRescheduleRequestWritePlatformServiceImpl implements LoanResche
} else if (!activeLoanTermVariation.fetchTermApplicaDate().isBefore(fromScheduleDate)) { } else if (!activeLoanTermVariation.fetchTermApplicaDate().isBefore(fromScheduleDate)) {
while (currentScheduleDate.isBefore(activeLoanTermVariation.fetchTermApplicaDate())) { while (currentScheduleDate.isBefore(activeLoanTermVariation.fetchTermApplicaDate())) {
currentScheduleDate = this.scheduledDateGenerator.generateNextRepaymentDate(currentScheduleDate, currentScheduleDate = this.scheduledDateGenerator.generateNextRepaymentDate(currentScheduleDate,
loanApplicationTerms, false, loanApplicationTerms.getHolidayDetailDTO()); loanApplicationTerms, false);
modifiedScheduleDate = this.scheduledDateGenerator.generateNextRepaymentDate(modifiedScheduleDate, modifiedScheduleDate = this.scheduledDateGenerator.generateNextRepaymentDate(modifiedScheduleDate,
loanApplicationTerms, false, loanApplicationTerms.getHolidayDetailDTO()); loanApplicationTerms, false);
changeMap.put(currentScheduleDate, modifiedScheduleDate); changeMap.put(currentScheduleDate, modifiedScheduleDate);
} }
if (changeMap.containsKey(activeLoanTermVariation.fetchTermApplicaDate())) { if (changeMap.containsKey(activeLoanTermVariation.fetchTermApplicaDate())) {
@@ -2421,7 +2421,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
while (!startDate.isAfter(DateUtils.getLocalDateOfTenant())) { while (!startDate.isAfter(DateUtils.getLocalDateOfTenant())) {
scheduleDates.put(frequencyNunber++, startDate.minusDays(diff.intValue())); scheduleDates.put(frequencyNunber++, startDate.minusDays(diff.intValue()));
LocalDate scheduleDate = scheduledDateGenerator.getRepaymentPeriodDate(PeriodFrequencyType.fromInt(feeFrequency), LocalDate scheduleDate = scheduledDateGenerator.getRepaymentPeriodDate(PeriodFrequencyType.fromInt(feeFrequency),
chargeDefinition.feeInterval(), startDate, null, null); chargeDefinition.feeInterval(), startDate);
startDate = scheduleDate; 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';