Mifos-1663:added more flexibilty for center/group meeting reschedule

This commit is contained in:
MithunKashyap
2014-12-09 15:22:41 +05:30
parent 3d399eba10
commit f48f7ed0f5
8 changed files with 239 additions and 11 deletions
@@ -17,7 +17,8 @@ public class CalendarConstants {
"location"), START_DATE("startDate"), END_DATE("endDate"), CREATED_DATE("createdDate"), DURATION("duration"), TYPE_ID(
"typeId"), REPEATING("repeating"), REMIND_BY_ID("remindById"), FIRST_REMINDER("firstReminder"), SECOND_REMINDER(
"secondReminder"), LOCALE("locale"), DATE_FORMAT("dateFormat"), FREQUENCY("frequency"), INTERVAL("interval"), REPEATS_ON_DAY(
"repeatsOnDay");
"repeatsOnDay"), RESCHEDULE_BASED_ON_MEETING_DATES("reschedulebasedOnMeetingDates"), PRESENT_MEETING_DATE(
"presentMeetingDate"), NEW_MEETING_DATE("newMeetingDate"), ;
private final String value;
@@ -169,6 +169,58 @@ public class Calendar extends AbstractAuditableCustom<AppUser, Long> {
firstReminder, secondReminder);
}
public Map<String, Object> updateStartDateAndDerivedFeilds(final LocalDate newMeetingStartDate) {
final Map<String, Object> actualChanges = new LinkedHashMap<>(9);
final LocalDate currentDate = DateUtils.getLocalDateOfTenant();
if (newMeetingStartDate.isBefore(currentDate)) {
final String defaultUserMessage = "New meeting effective from date cannot be in past";
throw new CalendarDateException("new.start.date.cannot.be.in.past", defaultUserMessage, newMeetingStartDate,
getStartDateLocalDate());
} else if (isStartDateAfter(newMeetingStartDate) && isStartDateBeforeOrEqual(currentDate)) {
// new meeting date should be on or after start date or current
// date
final String defaultUserMessage = "New meeting effective from date cannot be a date before existing meeting start date";
throw new CalendarDateException("new.start.date.before.existing.date", defaultUserMessage, newMeetingStartDate,
getStartDateLocalDate());
} else {
actualChanges.put(CALENDAR_SUPPORTED_PARAMETERS.START_DATE.getValue(), newMeetingStartDate.toString());
this.startDate = newMeetingStartDate.toDate();
/*
* If meeting start date is changed then there is possibilities of
* recurring day may change, so derive the recurring day and update
* it if it is changed. For weekly type is weekday and for monthly
* type it is day of the month
*/
CalendarFrequencyType calendarFrequencyType = CalendarUtils.getFrequency(this.recurrence);
Integer interval = CalendarUtils.getInterval(this.recurrence);
Integer repeatsOnDay = null;
/*
* Repeats on day, need to derive based on the start date
*/
if (calendarFrequencyType.isWeekly()) {
repeatsOnDay = newMeetingStartDate.getDayOfWeek();
} else if (calendarFrequencyType.isMonthly()) {
repeatsOnDay = newMeetingStartDate.getDayOfMonth();
}
// TODO cover other recurrence also
this.recurrence = constructRecurrence(calendarFrequencyType, interval, repeatsOnDay);
}
return actualChanges;
}
public Map<String, Object> update(final JsonCommand command) {
final Map<String, Object> actualChanges = new LinkedHashMap<>(9);
@@ -276,6 +328,21 @@ public class Calendar extends AbstractAuditableCustom<AppUser, Long> {
final String newRecurrence = Calendar.constructRecurrence(command, this);
if (!StringUtils.isBlank(this.recurrence) && !newRecurrence.equalsIgnoreCase(this.recurrence)) {
// FIXME: AA - Is this restriction required only for collection type
// meetings or for all?.
// Do not allow to change meeting frequency
if (!CalendarUtils.isFrequencySame(this.recurrence, newRecurrence)) {
final String defaultUserMessage = "Update of meeting frequency is not supported";
throw new CalendarParameterUpdateNotSupportedException("meeting.frequency", defaultUserMessage);
}
// Do not allow to change meeting interval
if (!CalendarUtils.isIntervalSame(this.recurrence, newRecurrence)) {
final String defaultUserMessage = "Update of meeting interval is not supported";
throw new CalendarParameterUpdateNotSupportedException("meeting.interval", defaultUserMessage);
}
actualChanges.put("recurrence", newRecurrence);
this.recurrence = StringUtils.defaultIfEmpty(newRecurrence, null);
}
@@ -82,6 +82,10 @@ public enum CalendarFrequencyType {
return this.value.equals(CalendarFrequencyType.WEEKLY.value);
}
public boolean isMonthly() {
return this.value.equals(CalendarFrequencyType.MONTHLY.value);
}
public boolean isInvalid() {
return this.value.equals(CalendarFrequencyType.INVALID.value);
}
@@ -201,6 +201,26 @@ public class CalendarCommandFromApiJsonDeserializer extends AbstractFromApiJsonD
final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("calendar");
if (this.fromApiJsonHelper.parameterExists(CALENDAR_SUPPORTED_PARAMETERS.RESCHEDULE_BASED_ON_MEETING_DATES.getValue(), element)) {
final Boolean rescheduleBasedOnMeetingDates = this.fromApiJsonHelper.extractBooleanNamed(
CALENDAR_SUPPORTED_PARAMETERS.RESCHEDULE_BASED_ON_MEETING_DATES.getValue(), element);
baseDataValidator.reset().parameter(CALENDAR_SUPPORTED_PARAMETERS.RESCHEDULE_BASED_ON_MEETING_DATES.getValue())
.value(rescheduleBasedOnMeetingDates).validateForBooleanValue();
}
if (this.fromApiJsonHelper.parameterExists(CALENDAR_SUPPORTED_PARAMETERS.PRESENT_MEETING_DATE.getValue(), element)) {
final String presentMeetingDate = this.fromApiJsonHelper.extractStringNamed(
CALENDAR_SUPPORTED_PARAMETERS.PRESENT_MEETING_DATE.getValue(), element);
baseDataValidator.reset().parameter(CALENDAR_SUPPORTED_PARAMETERS.PRESENT_MEETING_DATE.getValue()).value(presentMeetingDate)
.notNull();
}
if (this.fromApiJsonHelper.parameterExists(CALENDAR_SUPPORTED_PARAMETERS.NEW_MEETING_DATE.getValue(), element)) {
final String newMeetingDate = this.fromApiJsonHelper.extractStringNamed(
CALENDAR_SUPPORTED_PARAMETERS.NEW_MEETING_DATE.getValue(), element);
baseDataValidator.reset().parameter(CALENDAR_SUPPORTED_PARAMETERS.NEW_MEETING_DATE.getValue()).value(newMeetingDate).notNull();
}
if (this.fromApiJsonHelper.parameterExists(CALENDAR_SUPPORTED_PARAMETERS.TITLE.getValue(), element)) {
final String title = this.fromApiJsonHelper.extractStringNamed(CALENDAR_SUPPORTED_PARAMETERS.TITLE.getValue(), element);
baseDataValidator.reset().parameter(CALENDAR_SUPPORTED_PARAMETERS.TITLE.getValue()).value(title).notBlank()
@@ -177,12 +177,54 @@ public class CalendarWritePlatformServiceJpaRepositoryImpl implements CalendarWr
final Calendar calendarForUpdate = this.calendarRepository.findOne(calendarId);
if (calendarForUpdate == null) { throw new CalendarNotFoundException(calendarId); }
final Date oldStartDate = calendarForUpdate.getStartDate();
final LocalDate currentDate = DateUtils.getLocalDateOfTenant();
// create calendar history before updating calendar
final CalendarHistory calendarHistory = new CalendarHistory(calendarForUpdate, oldStartDate);
final Map<String, Object> changes = calendarForUpdate.update(command);
Map<String, Object> changes = null;
final Boolean reschedulebasedOnMeetingDates = command
.booleanObjectValueOfParameterNamed(CALENDAR_SUPPORTED_PARAMETERS.RESCHEDULE_BASED_ON_MEETING_DATES.getValue());
/*
* System allows to change the meeting date by two means,
*
* Option 1: reschedulebasedOnMeetingDates = false or reschedulebasedOnMeetingDates is not passed
* By directly editing the recurring day with effective from
* date and system decides the next meeting date based on some sensible
* logic (i.e., number of minimum days between two repayments)
*
*
* Option 2: reschedulebasedOnMeetingDates = true
* By providing alternative meeting date for one of future
* meeting date and derive the day of recurrence from the new meeting
* date. Ex: User proposes new meeting date say "14/Nov/2014" for
* present meeting date "12/Nov/2014", based on this input other values
* re derived and loans are rescheduled
*
*/
LocalDate newMeetingDate = null;
LocalDate presentMeetingDate = null;
if (reschedulebasedOnMeetingDates != null && reschedulebasedOnMeetingDates) {
newMeetingDate = command.localDateValueOfParameterNamed(CALENDAR_SUPPORTED_PARAMETERS.NEW_MEETING_DATE.getValue());
presentMeetingDate = command.localDateValueOfParameterNamed(CALENDAR_SUPPORTED_PARAMETERS.PRESENT_MEETING_DATE.getValue());
/*
* New meeting date proposed will become the new start date for the
* updated calendar
*/
changes = calendarForUpdate.updateStartDateAndDerivedFeilds(newMeetingDate);
} else {
changes = calendarForUpdate.update(command);
}
if (!changes.isEmpty()) {
// update calendar history table only if there is a change in
// calendar start date.
@@ -202,7 +244,9 @@ public class CalendarWritePlatformServiceJpaRepositoryImpl implements CalendarWr
if (!CollectionUtils.isEmpty(loanCalendarInstances)) {
// update all loans associated with modifying calendar
this.loanWritePlatformService.applyMeetingDateChanges(calendarForUpdate, loanCalendarInstances);
this.loanWritePlatformService.applyMeetingDateChanges(calendarForUpdate, loanCalendarInstances,
reschedulebasedOnMeetingDates, presentMeetingDate, newMeetingDate);
}
}
}
@@ -3850,8 +3850,68 @@ public class Loan extends AbstractPersistable<Long> {
return this.closedOnDate;
}
public void updateLoanRepaymentScheduleDates(final LocalDate meetingStartDate, final String recuringRule,
final boolean isHolidayEnabled, final List<Holiday> holidays, final WorkingDays workingDays,
final Boolean reschedulebasedOnMeetingDates, final LocalDate presentMeetingDate, final LocalDate newMeetingDate) {
// first repayment's from date is same as disbursement date.
/*
* meetingStartDate is used as seedDate Capture the seedDate from
* user and use the seedDate as meetingStart date
*/
LocalDate tmpFromDate = getDisbursementDate();
final PeriodFrequencyType repaymentPeriodFrequencyType = this.loanRepaymentScheduleDetail.getRepaymentPeriodFrequencyType();
final Integer loanRepaymentInterval = this.loanRepaymentScheduleDetail.getRepayEvery();
final String frequency = CalendarUtils.getMeetingFrequencyFromPeriodFrequencyType(repaymentPeriodFrequencyType);
LocalDate newRepaymentDate = null;
Boolean isFirstTime = true;
for (final LoanRepaymentScheduleInstallment loanRepaymentScheduleInstallment : this.repaymentScheduleInstallments) {
LocalDate oldDueDate = loanRepaymentScheduleInstallment.getDueDate();
if ( oldDueDate.isEqual(presentMeetingDate) || oldDueDate.isAfter(presentMeetingDate) ) {
if(isFirstTime){
isFirstTime = false;
newRepaymentDate = newMeetingDate;
}else{
// tmpFromDate.plusDays(1) is done to make sure
// getNewRepaymentMeetingDate method returns next meeting
// date and not the same as tmpFromDate
newRepaymentDate = CalendarUtils.getNewRepaymentMeetingDate(recuringRule, tmpFromDate, tmpFromDate.plusDays(1),
loanRepaymentInterval, frequency, workingDays);
}
if (isHolidayEnabled) {
newRepaymentDate = HolidayUtil.getRepaymentRescheduleDateToIfHoliday(newRepaymentDate, holidays);
}
loanRepaymentScheduleInstallment.updateDueDate(newRepaymentDate);
// reset from date to get actual daysInPeriod
if(!isFirstTime){
loanRepaymentScheduleInstallment.updateFromDate(tmpFromDate);
}
tmpFromDate = newRepaymentDate;// update with new repayment
// date
} else {
tmpFromDate = oldDueDate;
}
}
}
public void updateLoanRepaymentScheduleDates(final LocalDate meetingStartDate, final String recuringRule,
final boolean isHolidayEnabled, final List<Holiday> holidays, final WorkingDays workingDays) {
// first repayment's from date is same as disbursement date.
LocalDate tmpFromDate = getDisbursementDate();
final PeriodFrequencyType repaymentPeriodFrequencyType = this.loanRepaymentScheduleDetail.getRepaymentPeriodFrequencyType();
@@ -3859,18 +3919,25 @@ public class Loan extends AbstractPersistable<Long> {
final String frequency = CalendarUtils.getMeetingFrequencyFromPeriodFrequencyType(repaymentPeriodFrequencyType);
LocalDate newRepaymentDate = null;
LocalDate seedDate = meetingStartDate;
for (final LoanRepaymentScheduleInstallment loanRepaymentScheduleInstallment : this.repaymentScheduleInstallments) {
final LocalDate oldDueDate = loanRepaymentScheduleInstallment.getDueDate();
LocalDate oldDueDate = loanRepaymentScheduleInstallment.getDueDate();
// FIXME: AA this won't update repayment dates before current date.
if (oldDueDate.isAfter(meetingStartDate) && oldDueDate.isAfter(DateUtils.getLocalDateOfTenant())) {
newRepaymentDate = CalendarUtils.getNewRepaymentMeetingDate(recuringRule, meetingStartDate, oldDueDate,
if ( oldDueDate.isAfter(seedDate) && oldDueDate.isAfter(DateUtils.getLocalDateOfTenant()) ) {
newRepaymentDate = CalendarUtils.getNewRepaymentMeetingDate(recuringRule, seedDate, oldDueDate,
loanRepaymentInterval, frequency, workingDays);
final LocalDate maxDateLimitForNewRepayment = getMaxDateLimitForNewRepayment(repaymentPeriodFrequencyType,
loanRepaymentInterval, tmpFromDate);
if (newRepaymentDate.isAfter(maxDateLimitForNewRepayment)) {
newRepaymentDate = CalendarUtils.getNextRepaymentMeetingDate(recuringRule, meetingStartDate, tmpFromDate,
newRepaymentDate = CalendarUtils.getNextRepaymentMeetingDate(recuringRule, seedDate, tmpFromDate,
loanRepaymentInterval, frequency, workingDays);
}
@@ -57,7 +57,8 @@ public interface LoanWritePlatformService {
CommandProcessingResult removeLoanOfficer(Long loanId, JsonCommand command);
void applyMeetingDateChanges(Calendar calendar, Collection<CalendarInstance> loanCalendarInstances);
void applyMeetingDateChanges(Calendar calendar, Collection<CalendarInstance> loanCalendarInstances,
Boolean reschedulebasedOnMeetingDates, LocalDate presentMeetingDate, LocalDate newMeetingDate);
void applyHolidaysToLoans();
@@ -83,4 +84,6 @@ public interface LoanWritePlatformService {
CommandProcessingResult recoverFromGuarantor(Long loanId);
void applyMeetingDateChanges(Calendar calendar, Collection<CalendarInstance> loanCalendarInstances);
}
@@ -1968,11 +1968,25 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
existingTransactionIds, existingReversedTransactionIds, isAccountTransfer);
this.journalEntryWritePlatformService.createJournalEntriesForLoan(accountingBridgeData);
}
@Transactional
@Override
public void applyMeetingDateChanges(final Calendar calendar, final Collection<CalendarInstance> loanCalendarInstances) {
final Boolean reschedulebasedOnMeetingDates = null;
final LocalDate presentMeetingDate = null;
final LocalDate newMeetingDate = null;
applyMeetingDateChanges(calendar, loanCalendarInstances, reschedulebasedOnMeetingDates, presentMeetingDate, newMeetingDate);
}
@Transactional
@Override
public void applyMeetingDateChanges(final Calendar calendar, final Collection<CalendarInstance> loanCalendarInstances,
final Boolean reschedulebasedOnMeetingDates, final LocalDate presentMeetingDate, final LocalDate newMeetingDate) {
final boolean isHolidayEnabled = this.configurationDomainService.isRescheduleRepaymentsOnHolidaysEnabled();
final WorkingDays workingDays = this.workingDaysRepository.findOne();
final Collection<Integer> loanStatuses = new ArrayList<>(Arrays.asList(LoanStatus.SUBMITTED_AND_PENDING_APPROVAL.getValue(),
@@ -1994,8 +2008,16 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
throw new CalendarParameterUpdateNotSupportedException("jlg.loan.recalculation", defaultUserMessage);
}
holidays = this.holidayRepository.findByOfficeIdAndGreaterThanDate(loan.getOfficeId(), loan.getDisbursementDate().toDate());
loan.updateLoanRepaymentScheduleDates(calendar.getStartDateLocalDate(), calendar.getRecurrence(), isHolidayEnabled,
holidays, workingDays);
if(reschedulebasedOnMeetingDates != null && reschedulebasedOnMeetingDates ){
loan.updateLoanRepaymentScheduleDates(calendar.getStartDateLocalDate(), calendar.getRecurrence(), isHolidayEnabled,
holidays, workingDays, reschedulebasedOnMeetingDates, presentMeetingDate, newMeetingDate);
}else{
loan.updateLoanRepaymentScheduleDates(calendar.getStartDateLocalDate(), calendar.getRecurrence(), isHolidayEnabled,
holidays, workingDays);
}
saveLoanWithDataIntegrityViolationChecks(loan);
}
}