Mifos-1663:added more flexibilty for center/group meeting reschedule
This commit is contained in:
+2
-1
@@ -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;
|
||||
|
||||
|
||||
+67
@@ -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);
|
||||
}
|
||||
|
||||
+4
@@ -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);
|
||||
}
|
||||
|
||||
+20
@@ -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()
|
||||
|
||||
+46
-2
@@ -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);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+71
-4
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
+4
-1
@@ -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);
|
||||
|
||||
}
|
||||
+25
-3
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user