Merge pull request #661 from ashok-conflux/MIFOSX-649

added support for capturing calendar history data and generate meeting dates accordingly
This commit is contained in:
Keith Woodlock
2013-11-14 09:54:28 -08:00
22 changed files with 675 additions and 109 deletions
@@ -0,0 +1,19 @@
CREATE TABLE `m_calendar_history` (
`id` BIGINT(20) NOT NULL AUTO_INCREMENT,
`calendar_id` BIGINT(20) NOT NULL,
`title` VARCHAR(50) NOT NULL,
`description` VARCHAR(100) NULL DEFAULT NULL,
`location` VARCHAR(50) NULL DEFAULT NULL,
`start_date` DATE NOT NULL,
`end_date` DATE NULL DEFAULT NULL,
`duration` SMALLINT(6) NULL DEFAULT NULL,
`calendar_type_enum` SMALLINT(5) NOT NULL,
`repeating` TINYINT(1) NOT NULL DEFAULT '0',
`recurrence` VARCHAR(100) NULL DEFAULT NULL,
`remind_by_enum` SMALLINT(5) NULL DEFAULT NULL,
`first_reminder` SMALLINT(11) NULL DEFAULT NULL,
`second_reminder` SMALLINT(11) NULL DEFAULT NULL,
PRIMARY KEY (`id`),
INDEX `FK_m_calendar_m_calendar_history` (`calendar_id`),
CONSTRAINT `FK_m_calendar_m_calendar_history` FOREIGN KEY (`calendar_id`) REFERENCES `m_calendar` (`id`)
);
@@ -10,6 +10,7 @@ import java.util.Set;
public class CalendarConstants { public class CalendarConstants {
public static final String CALENDAR_RESOURCE_NAME = "calendar";
public static enum CALENDAR_SUPPORTED_PARAMETERS { public static enum CALENDAR_SUPPORTED_PARAMETERS {
CALENDAR_ID("id"), ENTITY_TYPE("entityType"), ENTITY_ID("entityId"), TITLE("title"), DESCRIPTION("description"), LOCATION( CALENDAR_ID("id"), ENTITY_TYPE("entityType"), ENTITY_ID("entityId"), TITLE("title"), DESCRIPTION("description"), LOCATION(
"location"), START_DATE("startDate"), END_DATE("endDate"), CREATED_DATE("createdDate"), DURATION("duration"), TYPE_ID( "location"), START_DATE("startDate"), END_DATE("endDate"), CREATED_DATE("createdDate"), DURATION("duration"), TYPE_ID(
@@ -26,6 +26,7 @@ import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType; import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.UriInfo; import javax.ws.rs.core.UriInfo;
import org.joda.time.LocalDate;
import org.mifosplatform.commands.domain.CommandWrapper; import org.mifosplatform.commands.domain.CommandWrapper;
import org.mifosplatform.commands.service.CommandWrapperBuilder; import org.mifosplatform.commands.service.CommandWrapperBuilder;
import org.mifosplatform.commands.service.PortfolioCommandSourceWritePlatformService; import org.mifosplatform.commands.service.PortfolioCommandSourceWritePlatformService;
@@ -94,7 +95,12 @@ public class CalendarsApiResource {
CalendarData calendarData = this.readPlatformService.retrieveCalendar(calendarId, entityId, entityTypeId); CalendarData calendarData = this.readPlatformService.retrieveCalendar(calendarId, entityId, entityTypeId);
// Include recurring date details // Include recurring date details
calendarData = this.readPlatformService.generateRecurringDate(calendarData); final boolean withHistory = true;
final LocalDate tillDate = null;
final Collection<LocalDate> recurringDates = this.readPlatformService.generateRecurringDates(calendarData, withHistory, tillDate);
final Collection<LocalDate> nextTenRecurringDates = this.readPlatformService.generateNextTenRecurringDates(calendarData);
final LocalDate recentEligibleMeetingDate = null;
calendarData = CalendarData.withRecurringDates(calendarData, recurringDates, nextTenRecurringDates, recentEligibleMeetingDate);
final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters()); final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
if (settings.isTemplate()) { if (settings.isTemplate()) {
@@ -135,7 +141,7 @@ public class CalendarsApiResource {
CalendarEntityType.valueOf(entityType.toUpperCase()).getValue(), calendarTypeOptions)); CalendarEntityType.valueOf(entityType.toUpperCase()).getValue(), calendarTypeOptions));
// Add recurring dates // Add recurring dates
calendarsData = this.readPlatformService.generateRecurringDates(calendarsData); calendarsData = this.readPlatformService.updateWithRecurringDates(calendarsData);
final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters()); final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
return this.toApiJsonSerializer.serialize(settings, calendarsData, this.RESPONSE_DATA_PARAMETERS); return this.toApiJsonSerializer.serialize(settings, calendarsData, this.RESPONSE_DATA_PARAMETERS);
@@ -22,6 +22,7 @@ import org.mifosplatform.portfolio.calendar.service.CalendarEnumerations;
public class CalendarData { public class CalendarData {
private final Long id; private final Long id;
private final Long calendarInstanceId;
private final Long entityId; private final Long entityId;
private final EnumOptionData entityType; private final EnumOptionData entityType;
private final String title; private final String title;
@@ -58,7 +59,7 @@ public class CalendarData {
final List<EnumOptionData> frequencyOptions; final List<EnumOptionData> frequencyOptions;
final List<EnumOptionData> repeatsOnDayOptions; final List<EnumOptionData> repeatsOnDayOptions;
public static CalendarData instance(final Long id, final Long entityId, final EnumOptionData entityType, final String title, public static CalendarData instance(final Long id, final Long calendarInstanceId, final Long entityId, final EnumOptionData entityType, final String title,
final String description, final String location, final LocalDate startDate, final LocalDate endDate, final Integer duration, final String description, final String location, final LocalDate startDate, final LocalDate endDate, final Integer duration,
final EnumOptionData type, final boolean repeating, final String recurrence, final EnumOptionData frequency, final EnumOptionData type, final boolean repeating, final String recurrence, final EnumOptionData frequency,
final Integer interval, final EnumOptionData repeatsOnDay, final EnumOptionData remindBy, final Integer firstReminder, final Integer interval, final EnumOptionData repeatsOnDay, final EnumOptionData remindBy, final Integer firstReminder,
@@ -75,7 +76,7 @@ public class CalendarData {
final List<EnumOptionData> frequencyOptions = null; final List<EnumOptionData> frequencyOptions = null;
final List<EnumOptionData> repeatsOnDayOptions = null; final List<EnumOptionData> repeatsOnDayOptions = null;
return new CalendarData(id, entityId, entityType, title, description, location, startDate, endDate, duration, type, repeating, return new CalendarData(id, calendarInstanceId, entityId, entityType, title, description, location, startDate, endDate, duration, type, repeating,
recurrence, frequency, interval, repeatsOnDay, remindBy, firstReminder, secondReminder, recurringDates, recurrence, frequency, interval, repeatsOnDay, remindBy, firstReminder, secondReminder, recurringDates,
nextTenRecurringDates, humanReadable, recentEligibleMeetingDate, createdDate, lastUpdatedDate, createdByUserId, nextTenRecurringDates, humanReadable, recentEligibleMeetingDate, createdDate, lastUpdatedDate, createdByUserId,
createdByUsername, lastUpdatedByUserId, lastUpdatedByUsername, entityTypeOptions, calendarTypeOptions, remindByOptions, createdByUsername, lastUpdatedByUserId, lastUpdatedByUsername, entityTypeOptions, calendarTypeOptions, remindByOptions,
@@ -84,7 +85,7 @@ public class CalendarData {
public static CalendarData withRecurringDates(final CalendarData calendarData, final Collection<LocalDate> recurringDates, public static CalendarData withRecurringDates(final CalendarData calendarData, final Collection<LocalDate> recurringDates,
final Collection<LocalDate> nextTenRecurringDates, final LocalDate recentEligibleMeetingDate) { final Collection<LocalDate> nextTenRecurringDates, final LocalDate recentEligibleMeetingDate) {
return new CalendarData(calendarData.id, calendarData.entityId, calendarData.entityType, calendarData.title, return new CalendarData(calendarData.id, calendarData.calendarInstanceId, calendarData.entityId, calendarData.entityType, calendarData.title,
calendarData.description, calendarData.location, calendarData.startDate, calendarData.endDate, calendarData.duration, calendarData.description, calendarData.location, calendarData.startDate, calendarData.endDate, calendarData.duration,
calendarData.type, calendarData.repeating, calendarData.recurrence, calendarData.frequency, calendarData.interval, calendarData.type, calendarData.repeating, calendarData.recurrence, calendarData.frequency, calendarData.interval,
calendarData.repeatsOnDay, calendarData.remindBy, calendarData.firstReminder, calendarData.secondReminder, recurringDates, calendarData.repeatsOnDay, calendarData.remindBy, calendarData.firstReminder, calendarData.secondReminder, recurringDates,
@@ -94,9 +95,22 @@ public class CalendarData {
calendarData.calendarTypeOptions, calendarData.remindByOptions, calendarData.frequencyOptions, calendarData.calendarTypeOptions, calendarData.remindByOptions, calendarData.frequencyOptions,
calendarData.repeatsOnDayOptions); calendarData.repeatsOnDayOptions);
} }
public static CalendarData withRecentEligibleMeetingDate(final CalendarData calendarData, final LocalDate recentEligibleMeetingDate) {
return new CalendarData(calendarData.id, calendarData.calendarInstanceId, calendarData.entityId, calendarData.entityType, calendarData.title,
calendarData.description, calendarData.location, calendarData.startDate, calendarData.endDate, calendarData.duration,
calendarData.type, calendarData.repeating, calendarData.recurrence, calendarData.frequency, calendarData.interval,
calendarData.repeatsOnDay, calendarData.remindBy, calendarData.firstReminder, calendarData.secondReminder, calendarData.recurringDates,
calendarData.nextTenRecurringDates, calendarData.humanReadable, recentEligibleMeetingDate, calendarData.createdDate,
calendarData.lastUpdatedDate, calendarData.createdByUserId, calendarData.createdByUsername,
calendarData.lastUpdatedByUserId, calendarData.lastUpdatedByUsername, calendarData.entityTypeOptions,
calendarData.calendarTypeOptions, calendarData.remindByOptions, calendarData.frequencyOptions,
calendarData.repeatsOnDayOptions);
}
public static CalendarData sensibleDefaultsForNewCalendarCreation() { public static CalendarData sensibleDefaultsForNewCalendarCreation() {
final Long id = null; final Long id = null;
final Long calendarInstanceId = null;
final Long entityId = null; final Long entityId = null;
final EnumOptionData entityType = null; final EnumOptionData entityType = null;
final String title = null; final String title = null;
@@ -132,7 +146,7 @@ public class CalendarData {
final Long lastUpdatedByUserId = null; final Long lastUpdatedByUserId = null;
final String lastUpdatedByUsername = null; final String lastUpdatedByUsername = null;
return new CalendarData(id, entityId, entityType, title, description, location, startDate, endDate, duration, type, repeating, return new CalendarData(id, calendarInstanceId, entityId, entityType, title, description, location, startDate, endDate, duration, type, repeating,
recurrence, frequency, interval, repeatsOnDay, remindBy, firstReminder, secondReminder, recurringDates, recurrence, frequency, interval, repeatsOnDay, remindBy, firstReminder, secondReminder, recurringDates,
nextTenRecurringDates, humanReadable, recentEligibleMeetingDate, createdDate, lastUpdatedDate, createdByUserId, nextTenRecurringDates, humanReadable, recentEligibleMeetingDate, createdDate, lastUpdatedDate, createdByUserId,
createdByUsername, lastUpdatedByUserId, lastUpdatedByUsername, entityTypeOptions, calendarTypeOptions, remindByOptions, createdByUsername, lastUpdatedByUserId, lastUpdatedByUsername, entityTypeOptions, calendarTypeOptions, remindByOptions,
@@ -143,7 +157,7 @@ public class CalendarData {
final List<EnumOptionData> calendarTypeOptions, final List<EnumOptionData> remindByOptions, final List<EnumOptionData> calendarTypeOptions, final List<EnumOptionData> remindByOptions,
final List<EnumOptionData> repeatsOptions, final List<EnumOptionData> repeatsOnDayOptions) { final List<EnumOptionData> repeatsOptions, final List<EnumOptionData> repeatsOnDayOptions) {
return new CalendarData(calendarData.id, calendarData.entityId, calendarData.entityType, calendarData.title, return new CalendarData(calendarData.id, calendarData.calendarInstanceId, calendarData.entityId, calendarData.entityType, calendarData.title,
calendarData.description, calendarData.location, calendarData.startDate, calendarData.endDate, calendarData.duration, calendarData.description, calendarData.location, calendarData.startDate, calendarData.endDate, calendarData.duration,
calendarData.type, calendarData.repeating, calendarData.recurrence, calendarData.frequency, calendarData.interval, calendarData.type, calendarData.repeating, calendarData.recurrence, calendarData.frequency, calendarData.interval,
calendarData.repeatsOnDay, calendarData.remindBy, calendarData.firstReminder, calendarData.secondReminder, calendarData.repeatsOnDay, calendarData.remindBy, calendarData.firstReminder, calendarData.secondReminder,
@@ -153,7 +167,7 @@ public class CalendarData {
entityTypeOptions, calendarTypeOptions, remindByOptions, repeatsOptions, repeatsOnDayOptions); entityTypeOptions, calendarTypeOptions, remindByOptions, repeatsOptions, repeatsOnDayOptions);
} }
private CalendarData(final Long id, final Long entityId, final EnumOptionData entityType, final String title, final String description, private CalendarData(final Long id, final Long calendarInstanceId, final Long entityId, final EnumOptionData entityType, final String title, final String description,
final String location, final LocalDate startDate, final LocalDate endDate, final Integer duration, final EnumOptionData type, final String location, final LocalDate startDate, final LocalDate endDate, final Integer duration, final EnumOptionData type,
final boolean repeating, final String recurrence, final EnumOptionData frequency, final Integer interval, final boolean repeating, final String recurrence, final EnumOptionData frequency, final Integer interval,
final EnumOptionData repeatsOnDay, final EnumOptionData remindBy, final Integer firstReminder, final Integer secondReminder, final EnumOptionData repeatsOnDay, final EnumOptionData remindBy, final Integer firstReminder, final Integer secondReminder,
@@ -164,6 +178,7 @@ public class CalendarData {
final List<EnumOptionData> remindByOptions, final List<EnumOptionData> repeatsOptions, final List<EnumOptionData> remindByOptions, final List<EnumOptionData> repeatsOptions,
final List<EnumOptionData> repeatsOnDayOptions) { final List<EnumOptionData> repeatsOnDayOptions) {
this.id = id; this.id = id;
this.calendarInstanceId = calendarInstanceId;
this.entityId = entityId; this.entityId = entityId;
this.entityType = entityType; this.entityType = entityType;
this.title = title; this.title = title;
@@ -278,4 +293,31 @@ public class CalendarData {
return this.humanReadable; return this.humanReadable;
} }
public Long getCalendarInstanceId() {
return this.calendarInstanceId;
}
public boolean isStartDateBeforeOrEqual(final LocalDate compareDate) {
if (this.startDate != null && compareDate != null) {
if (this.startDate.isBefore(compareDate) || this.startDate.equals(compareDate)) { return true; }
}
return false;
}
public boolean isEndDateAfterOrEqual(final LocalDate compareDate) {
if (this.endDate != null && compareDate != null) {
if (this.endDate.isAfter(compareDate) || this.endDate.isEqual(compareDate)) { return true; }
}
return false;
}
public boolean isBetweenStartAndEndDate(final LocalDate compareDate){
if (isStartDateBeforeOrEqual(compareDate)){
if (this.endDate == null || isEndDateAfterOrEqual(compareDate)){
return true;
}
}
return false;
}
} }
@@ -6,12 +6,21 @@
package org.mifosplatform.portfolio.calendar.domain; package org.mifosplatform.portfolio.calendar.domain;
import static org.mifosplatform.portfolio.calendar.CalendarConstants.CALENDAR_RESOURCE_NAME;
import java.util.ArrayList;
import java.util.Date; import java.util.Date;
import java.util.HashSet;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set;
import javax.persistence.Column; import javax.persistence.Column;
import javax.persistence.Entity; import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.JoinColumn;
import javax.persistence.OneToMany;
import javax.persistence.Table; import javax.persistence.Table;
import javax.persistence.Temporal; import javax.persistence.Temporal;
import javax.persistence.TemporalType; import javax.persistence.TemporalType;
@@ -19,7 +28,11 @@ import javax.persistence.TemporalType;
import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.StringUtils;
import org.joda.time.LocalDate; import org.joda.time.LocalDate;
import org.mifosplatform.infrastructure.core.api.JsonCommand; import org.mifosplatform.infrastructure.core.api.JsonCommand;
import org.mifosplatform.infrastructure.core.data.ApiParameterError;
import org.mifosplatform.infrastructure.core.data.DataValidatorBuilder;
import org.mifosplatform.infrastructure.core.domain.AbstractAuditableCustom; import org.mifosplatform.infrastructure.core.domain.AbstractAuditableCustom;
import org.mifosplatform.infrastructure.core.exception.PlatformApiDataValidationException;
import org.mifosplatform.infrastructure.core.service.DateUtils;
import org.mifosplatform.portfolio.calendar.CalendarConstants.CALENDAR_SUPPORTED_PARAMETERS; import org.mifosplatform.portfolio.calendar.CalendarConstants.CALENDAR_SUPPORTED_PARAMETERS;
import org.mifosplatform.portfolio.calendar.exception.CalendarDateException; import org.mifosplatform.portfolio.calendar.exception.CalendarDateException;
import org.mifosplatform.portfolio.calendar.exception.CalendarParameterUpdateNotSupportedException; import org.mifosplatform.portfolio.calendar.exception.CalendarParameterUpdateNotSupportedException;
@@ -68,6 +81,10 @@ public class Calendar extends AbstractAuditableCustom<AppUser, Long> {
@Column(name = "second_reminder", nullable = true) @Column(name = "second_reminder", nullable = true)
private Integer secondReminder; private Integer secondReminder;
@OneToMany(fetch = FetchType.EAGER)
@JoinColumn(name = "calendar_id")
private Set<CalendarHistory> calendarHistory = new HashSet<CalendarHistory>();
protected Calendar() { protected Calendar() {
} }
@@ -75,6 +92,17 @@ public class Calendar extends AbstractAuditableCustom<AppUser, Long> {
public Calendar(final String title, final String description, final String location, final LocalDate startDate, public Calendar(final String title, final String description, final String location, final LocalDate startDate,
final LocalDate endDate, final Integer duration, final Integer typeId, final boolean repeating, final String recurrence, final LocalDate endDate, final Integer duration, final Integer typeId, final boolean repeating, final String recurrence,
final Integer remindById, final Integer firstReminder, final Integer secondReminder) { final Integer remindById, final Integer firstReminder, final Integer secondReminder) {
final List<ApiParameterError> dataValidationErrors = new ArrayList<ApiParameterError>();
final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource(CALENDAR_RESOURCE_NAME);
final CalendarType calendarType = CalendarType.fromInt(typeId);
if (calendarType.isCollection() && !repeating) {
baseDataValidator.reset().parameter(CALENDAR_SUPPORTED_PARAMETERS.REPEATING.getValue())
.failWithCodeNoParameterAddedToErrorCode("must.repeat.for.collection.calendar");
if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
}
this.title = StringUtils.defaultIfEmpty(title, null); this.title = StringUtils.defaultIfEmpty(title, null);
this.description = StringUtils.defaultIfEmpty(description, null); this.description = StringUtils.defaultIfEmpty(description, null);
this.location = StringUtils.defaultIfEmpty(location, null); this.location = StringUtils.defaultIfEmpty(location, null);
@@ -153,17 +181,22 @@ public class Calendar extends AbstractAuditableCustom<AppUser, Long> {
final String valueAsInput = command.stringValueOfParameterNamed(startDateParamName); final String valueAsInput = command.stringValueOfParameterNamed(startDateParamName);
final LocalDate newValue = command.localDateValueOfParameterNamed(startDateParamName); final LocalDate newValue = command.localDateValueOfParameterNamed(startDateParamName);
if (isStartDateBefore(newValue)) { final LocalDate currentDate = DateUtils.getLocalDateOfTenant();
if (newValue.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, newValue,
getStartDateLocalDate());
}else if (isStartDateAfter(newValue) && 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, newValue,
getStartDateLocalDate());
} else {
actualChanges.put(startDateParamName, valueAsInput); actualChanges.put(startDateParamName, valueAsInput);
actualChanges.put("dateFormat", dateFormatAsInput); actualChanges.put("dateFormat", dateFormatAsInput);
actualChanges.put("locale", localeAsInput); actualChanges.put("locale", localeAsInput);
this.startDate = newValue.toDate(); this.startDate = newValue.toDate();
} else {
// new meeting start date should be greater than existing
// meeting start date
final String defaultUserMessage = "New meeting start on or after date cannot be a date before existing meeting start date";
throw new CalendarDateException("new.start.date.before.existing.date", defaultUserMessage, newValue,
getStartDateLocalDate());
} }
} }
@@ -211,8 +244,21 @@ public class Calendar extends AbstractAuditableCustom<AppUser, Long> {
this.repeating = newValue; this.repeating = newValue;
} }
//if repeating is false then update recurrence to NULL
if(!this.repeating) this.recurrence = null;
final List<ApiParameterError> dataValidationErrors = new ArrayList<ApiParameterError>();
final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource(CALENDAR_RESOURCE_NAME);
final CalendarType calendarType = CalendarType.fromInt(this.typeId);
if (calendarType.isCollection() && !this.repeating) {
baseDataValidator.reset().parameter(CALENDAR_SUPPORTED_PARAMETERS.REPEATING.getValue())
.failWithCodeNoParameterAddedToErrorCode("must.repeat.for.collection.calendar");
if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
}
final String newRecurrence = Calendar.constructRecurrence(command, this); final String newRecurrence = Calendar.constructRecurrence(command, this);
if (this.recurrence != null && !newRecurrence.equalsIgnoreCase(this.recurrence)) { if (!StringUtils.isBlank(this.recurrence) && !newRecurrence.equalsIgnoreCase(this.recurrence)) {
// FIXME: AA - Is this restriction required only for collection type // FIXME: AA - Is this restriction required only for collection type
// meetings or for all?. // meetings or for all?.
@@ -319,9 +365,48 @@ public class Calendar extends AbstractAuditableCustom<AppUser, Long> {
} }
return endDateLocalDate; return endDateLocalDate;
} }
public Set<CalendarHistory> history() {
return this.calendarHistory;
}
public boolean isStartDateBefore(final LocalDate newStartDate) { public boolean isStartDateBefore(final LocalDate compareDate) {
if (this.startDate != null && newStartDate != null && getStartDateLocalDate().isBefore(newStartDate)) { return true; } if (this.startDate != null && compareDate != null && getStartDateLocalDate().isBefore(compareDate)) { return true; }
return false;
}
public boolean isStartDateBeforeOrEqual(final LocalDate compareDate) {
if (this.startDate != null && compareDate != null) {
if (getStartDateLocalDate().isBefore(compareDate) || getStartDateLocalDate().equals(compareDate)) { return true; }
}
return false;
}
public boolean isStartDateAfter(final LocalDate compareDate) {
if (this.startDate != null && compareDate != null && getStartDateLocalDate().isAfter(compareDate)) { return true; }
return false;
}
public boolean isStartDateAfterOrEqual(final LocalDate compareDate) {
if (this.startDate != null && compareDate != null) {
if (getStartDateLocalDate().isAfter(compareDate) || getStartDateLocalDate().isEqual(compareDate)) { return true; }
}
return false;
}
public boolean isEndDateAfterOrEqual(final LocalDate compareDate) {
if (this.endDate != null && compareDate != null) {
if (getEndDateLocalDate().isAfter(compareDate) || getEndDateLocalDate().isEqual(compareDate)) { return true; }
}
return false;
}
public boolean isBetweenStartAndEndDate(final LocalDate compareDate){
if (isStartDateBeforeOrEqual(compareDate)){
if (getEndDateLocalDate() == null || isEndDateAfterOrEqual(compareDate)){
return true;
}
}
return false; return false;
} }
@@ -358,4 +443,18 @@ public class Calendar extends AbstractAuditableCustom<AppUser, Long> {
} }
return ""; return "";
} }
public boolean isValidRecurringDate(final LocalDate compareDate) {
if (isBetweenStartAndEndDate(compareDate)) { return CalendarUtils.isValidRedurringDate(getRecurrence(), getStartDateLocalDate(),
compareDate); }
// validate with history details.
for (CalendarHistory history : history()) {
if (history.isBetweenStartAndEndDate(compareDate)) { return CalendarUtils.isValidRedurringDate(history.getRecurrence(),
history.getStartDateLocalDate(), compareDate); }
}
return false;
}
} }
@@ -0,0 +1,134 @@
/**
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/.
*/
package org.mifosplatform.portfolio.calendar.domain;
import java.util.Date;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.Table;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;
import org.joda.time.LocalDate;
import org.springframework.data.jpa.domain.AbstractPersistable;
@Entity
@Table(name = "m_calendar_history")
public class CalendarHistory extends AbstractPersistable<Long>{
@ManyToOne(optional = false)
@JoinColumn(name = "calendar_id", referencedColumnName = "id", nullable = false)
private Calendar calendar;
@Column(name = "title", length = 50, nullable = false)
private String title;
@Column(name = "description", length = 100, nullable = true)
private String description;
@Column(name = "location", length = 100, nullable = true)
private String location;
@Column(name = "start_date", nullable = false)
@Temporal(TemporalType.DATE)
private Date startDate;
@Column(name = "end_date", nullable = true)
@Temporal(TemporalType.DATE)
private Date endDate;
@Column(name = "duration", nullable = true)
private Integer duration;
@Column(name = "calendar_type_enum", nullable = false)
private Integer typeId;
@Column(name = "repeating", nullable = false)
private boolean repeating = false;
@Column(name = "recurrence", length = 100, nullable = true)
private String recurrence;
@Column(name = "remind_by_enum", nullable = true)
private Integer remindById;
@Column(name = "first_reminder", nullable = true)
private Integer firstReminder;
@Column(name = "second_reminder", nullable = true)
private Integer secondReminder;
protected CalendarHistory() {
}
public CalendarHistory(Calendar calendar, Date startDate) {
this.calendar = calendar;
this.title = calendar.getTitle();
this.description = calendar.getDescription();
this.location = calendar.getLocation();
this.startDate = startDate;
this.endDate = calendar.getStartDate();//Calendar start date become end date for history data.
this.duration = calendar.getDuration();
this.typeId = calendar.getTypeId();
this.repeating = calendar.isRepeating();
this.recurrence = calendar.getRecurrence();
this.remindById = calendar.getRemindById();
this.firstReminder = calendar.getFirstReminder();
this.secondReminder = calendar.getSecondReminder();
}
public String getRecurrence() {
return this.recurrence;
}
public LocalDate getStartDateLocalDate() {
LocalDate startDateLocalDate = null;
if (this.startDate != null) {
startDateLocalDate = LocalDate.fromDateFields(this.startDate);
}
return startDateLocalDate;
}
public LocalDate getEndDateLocalDate() {
LocalDate endDateLocalDate = null;
if (this.endDate != null) {
endDateLocalDate = LocalDate.fromDateFields(this.endDate);
}
return endDateLocalDate;
}
public boolean isEndDateAfterOrEqual(final LocalDate compareDate) {
if (this.endDate != null && compareDate != null) {
if (getEndDateLocalDate().isAfter(compareDate) || getEndDateLocalDate().isEqual(compareDate)) { return true; }
}
return false;
}
public boolean isStartDateBeforeOrEqual(final LocalDate compareDate) {
if (this.startDate != null && compareDate != null) {
if (getStartDateLocalDate().isBefore(compareDate) || getStartDateLocalDate().equals(compareDate)) { return true; }
}
return false;
}
public boolean isBetweenStartAndEndDate(final LocalDate compareDate){
if (isStartDateBeforeOrEqual(compareDate)){
if (getEndDateLocalDate() == null || isEndDateAfterOrEqual(compareDate)){
return true;
}
}
return false;
}
public void updateEndDate(Date historyCalEndDate){
this.endDate = historyCalEndDate;
}
}
@@ -0,0 +1,13 @@
/**
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/.
*/
package org.mifosplatform.portfolio.calendar.domain;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
public interface CalendarHistoryRepository extends JpaRepository<CalendarHistory, Long>, JpaSpecificationExecutor<CalendarHistory> {
}
@@ -66,4 +66,8 @@ public enum CalendarType {
public String toString() { public String toString() {
return name().toString(); return name().toString();
} }
public boolean isCollection(){
return this.value.equals(CalendarType.COLLECTION.value);
}
} }
@@ -10,6 +10,7 @@ import java.util.List;
import org.joda.time.LocalDate; import org.joda.time.LocalDate;
import org.mifosplatform.portfolio.calendar.data.CalendarData; import org.mifosplatform.portfolio.calendar.data.CalendarData;
import org.mifosplatform.portfolio.meeting.data.MeetingData;
public interface CalendarReadPlatformService { public interface CalendarReadPlatformService {
@@ -24,14 +25,16 @@ public interface CalendarReadPlatformService {
CalendarData retrieveNewCalendarDetails(); CalendarData retrieveNewCalendarDetails();
CalendarData generateRecurringDate(final CalendarData calendarData); Collection<LocalDate> generateRecurringDates(final CalendarData calendarData, final boolean withHistory, final LocalDate tillDate);
CalendarData generateRecurringDate(final CalendarData calendarData, final LocalDate tillDate); Collection<LocalDate> generateNextTenRecurringDates(final CalendarData calendarData);
Collection<CalendarData> generateRecurringDates(final Collection<CalendarData> calendarsData); Collection<CalendarData> updateWithRecurringDates(final Collection<CalendarData> calendarsData);
CalendarData retrieveLoanCalendar(final Long loanId); CalendarData retrieveLoanCalendar(final Long loanId);
CalendarData retrieveCollctionCalendarByEntity(final Long entityId, final Integer entityTypeId); CalendarData retrieveCollctionCalendarByEntity(final Long entityId, final Integer entityTypeId);
LocalDate generateNextEligibleMeetingDateForCollection(CalendarData calendarData, MeetingData lastMeetingData);
} }
@@ -20,6 +20,7 @@ import org.mifosplatform.portfolio.calendar.data.CalendarData;
import org.mifosplatform.portfolio.calendar.domain.CalendarEntityType; import org.mifosplatform.portfolio.calendar.domain.CalendarEntityType;
import org.mifosplatform.portfolio.calendar.domain.CalendarType; import org.mifosplatform.portfolio.calendar.domain.CalendarType;
import org.mifosplatform.portfolio.calendar.exception.CalendarNotFoundException; import org.mifosplatform.portfolio.calendar.exception.CalendarNotFoundException;
import org.mifosplatform.portfolio.meeting.data.MeetingData;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.EmptyResultDataAccessException; import org.springframework.dao.EmptyResultDataAccessException;
import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.core.JdbcTemplate;
@@ -40,7 +41,7 @@ public class CalendarReadPlatformServiceImpl implements CalendarReadPlatformServ
private static final class CalendarDataMapper implements RowMapper<CalendarData> { private static final class CalendarDataMapper implements RowMapper<CalendarData> {
public String schema() { public String schema() {
return " select c.id as id, ci.entity_id as entityId, ci.entity_type_enum as entityTypeId, c.title as title, " return " select c.id as id, ci.id as calendarInstanceId, ci.entity_id as entityId, ci.entity_type_enum as entityTypeId, c.title as title, "
+ " c.description as description, c.location as location, c.start_date as startDate, c.end_date as endDate, " + " c.description as description, c.location as location, c.start_date as startDate, c.end_date as endDate, "
+ " c.duration as duration, c.calendar_type_enum as typeId, c.repeating as repeating, " + " c.duration as duration, c.calendar_type_enum as typeId, c.repeating as repeating, "
+ " c.recurrence as recurrence, c.remind_by_enum as remindById, c.first_reminder as firstReminder, c.second_reminder as secondReminder, " + " c.recurrence as recurrence, c.remind_by_enum as remindById, c.first_reminder as firstReminder, c.second_reminder as secondReminder, "
@@ -54,6 +55,7 @@ public class CalendarReadPlatformServiceImpl implements CalendarReadPlatformServ
public CalendarData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException { public CalendarData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException {
final Long id = rs.getLong("id"); final Long id = rs.getLong("id");
final Long calendarInstanceId = rs.getLong("calendarInstanceId");
final Long entityId = rs.getLong("entityId"); final Long entityId = rs.getLong("entityId");
final Integer entityTypeId = rs.getInt("entityTypeId"); final Integer entityTypeId = rs.getInt("entityTypeId");
final EnumOptionData entityType = CalendarEnumerations.calendarEntityType(entityTypeId); final EnumOptionData entityType = CalendarEnumerations.calendarEntityType(entityTypeId);
@@ -89,9 +91,10 @@ public class CalendarReadPlatformServiceImpl implements CalendarReadPlatformServ
final Long lastUpdatedByUserId = rs.getLong("updatingUserId"); final Long lastUpdatedByUserId = rs.getLong("updatingUserId");
final String lastUpdatedByUserName = rs.getString("updatingUserName"); final String lastUpdatedByUserName = rs.getString("updatingUserName");
return CalendarData.instance(id, entityId, entityType, title, description, location, startDate, endDate, duration, type, return CalendarData.instance(id, calendarInstanceId, entityId, entityType, title, description, location, startDate, endDate,
repeating, recurrence, frequency, interval, repeatsOnDay, remindBy, firstReminder, secondReminder, humanReadable, createdDate, duration, type, repeating, recurrence, frequency, interval, repeatsOnDay, remindBy, firstReminder, secondReminder,
lastUpdatedDate, createdByUserId, createdByUserName, lastUpdatedByUserId, lastUpdatedByUserName); humanReadable, createdDate, lastUpdatedDate, createdByUserId, createdByUserName, lastUpdatedByUserId,
lastUpdatedByUserName);
} }
} }
@@ -184,49 +187,139 @@ public class CalendarReadPlatformServiceImpl implements CalendarReadPlatformServ
} }
@Override @Override
public CalendarData generateRecurringDate(final CalendarData calendarData) { public Collection<LocalDate> generateRecurringDates(final CalendarData calendarData, final boolean withHistory, final LocalDate tillDate) {
return generateRecurringDate(calendarData, null); final LocalDate fromDate = null;
Collection<LocalDate> recurringDates = generateRecurringDate(calendarData, fromDate, tillDate, -1);
if (withHistory) {
final Collection<CalendarData> calendarHistorys = this.retrieveCalendarsFromHistory(calendarData.getId());
for (CalendarData calendarHistory : calendarHistorys) {
recurringDates.addAll(generateRecurringDate(calendarHistory, fromDate, tillDate, -1));
}
}
return recurringDates;
} }
@Override @Override
public CalendarData generateRecurringDate(final CalendarData calendarData, final LocalDate tillDate) { public Collection<LocalDate> generateNextTenRecurringDates(CalendarData calendarData) {
if (!calendarData.isRepeating()) { return calendarData; } final LocalDate tillDate = null;
return generateRecurringDate(calendarData, DateUtils.getLocalDateOfTenant(), tillDate, 10);
}
private Collection<LocalDate> generateRecurringDate(final CalendarData calendarData, final LocalDate fromDate, final LocalDate tillDate, final int maxCount) {
if (!calendarData.isRepeating()) { return null; }
final String rrule = calendarData.getRecurrence(); final String rrule = calendarData.getRecurrence();
final LocalDate currentDate = DateUtils.getLocalDateOfTenant(); /**
final LocalDate seedDate = calendarData.getStartDate(); * Start date or effective from date of calendar recurrence.
LocalDate endDate = calendarData.getEndDate(); */
LocalDate startDate = calendarData.getStartDate(); final LocalDate seedDate = this.getSeedDate(calendarData.getStartDate());
if (seedDate.isBefore(currentDate.minusYears(1))) { /**
startDate = currentDate.minusYears(1); * periodStartDate date onwards recurring dates will be generated.
*/
final LocalDate periodStartDate = this.getPeriodStartDate(seedDate, calendarData.getStartDate(), fromDate);
/**
* till periodEndDate recurring dates will be generated.
*/
final LocalDate periodEndDate = this.getPeriodEndDate(calendarData.getEndDate(), tillDate);
final Collection<LocalDate> recurringDates = CalendarUtils.getRecurringDates(rrule, seedDate, periodStartDate, periodEndDate,
maxCount);
return recurringDates;
}
private LocalDate getSeedDate(LocalDate date) {
return date;
}
private LocalDate getPeriodStartDate(final LocalDate seedDate, final LocalDate recurrenceStartDate, final LocalDate fromDate) {
LocalDate periodStartDate = null;
if(fromDate != null) {
periodStartDate = fromDate;
}else {
final LocalDate currentDate = DateUtils.getLocalDateOfTenant();
if (seedDate.isBefore(currentDate.minusYears(1))) {
periodStartDate = currentDate.minusYears(1);
}else{
periodStartDate = recurrenceStartDate;
}
} }
return periodStartDate;
}
private LocalDate getPeriodEndDate(LocalDate endDate, LocalDate tillDate) {
LocalDate periodEndDate = endDate;
final LocalDate currentDate = DateUtils.getLocalDateOfTenant();
if (tillDate != null) { if (tillDate != null) {
if (endDate != null) { if (endDate != null) {
if (endDate.isAfter(tillDate)) { if (endDate.isAfter(tillDate)) {
endDate = tillDate;// to retrieve meeting dates till // to retrieve meeting dates tillspecified date (tillDate)
// specified date (tillDate) periodEndDate = tillDate;
} }
} else { } else {
endDate = tillDate;// end date is null then fetch meeting dates // end date is null then fetch meeting dates tillDate
// tillDate periodEndDate = tillDate;
} }
} else if (endDate == null || endDate.isAfter(currentDate.plusYears(1))) { } else if (endDate == null || endDate.isAfter(currentDate.plusYears(1))) {
endDate = currentDate.plusYears(1); periodEndDate = currentDate.plusYears(1);
} }
return periodEndDate;
final Collection<LocalDate> recurringDates = CalendarUtils.getRecurringDates(rrule, seedDate, startDate, endDate, -1);
final Collection<LocalDate> nextTenRecurringDates = CalendarUtils.getRecurringDates(rrule, seedDate, endDate);
final LocalDate recentEligibleMeetingDate = CalendarUtils.getRecentEligibleMeetingDate(rrule, seedDate);
return CalendarData.withRecurringDates(calendarData, recurringDates, nextTenRecurringDates, recentEligibleMeetingDate);
} }
@Override @Override
public Collection<CalendarData> generateRecurringDates(final Collection<CalendarData> calendarsData) { public LocalDate generateNextEligibleMeetingDateForCollection(final CalendarData calendarData, final MeetingData lastMeetingData) {
final Collection<CalendarData> recuCalendarsData = new ArrayList<CalendarData>();
final LocalDate lastMeetingDate = (lastMeetingData == null) ? null : lastMeetingData.getMeetingDate();
// get applicable calendar based on meeting date
CalendarData applicableCalendarData = calendarData;
LocalDate nextEligibleMeetingDate = null;
/**
* The next available meeting date for collection should be taken from
* application calendar for that time period. e.g. If the previous
* calendar details has weekly meeting starting from 1st of Oct 2013 on
* every Tuesday, then meeting dates for collection are 1,8,15,22,29..
*
* If meeting schedule has changed from Tuesday to Friday with effective
* from 15th of Oct (calendar update has made on 2nd of Oct) , then
* application should allow to generate collection sheet on 8th of Oct
* which is still on Tuesday and next collection sheet date should be on
* 18th of Oct as per current calendar
*/
if (lastMeetingDate != null && !calendarData.isBetweenStartAndEndDate(lastMeetingDate)
&& !calendarData.isBetweenStartAndEndDate(DateUtils.getLocalDateOfTenant())) {
applicableCalendarData = this.retrieveApplicableCalendarFromHistory(calendarData.getId(), lastMeetingDate);
nextEligibleMeetingDate = CalendarUtils.getRecentEligibleMeetingDate(applicableCalendarData.getRecurrence(), lastMeetingDate);
}
/**
* If nextEligibleMeetingDate is on or after current calendar startdate
* then regenerate the nextEligible meeting date based on
*/
if (nextEligibleMeetingDate == null) {
final LocalDate seedDate = (lastMeetingDate != null) ? lastMeetingDate : calendarData.getStartDate();
nextEligibleMeetingDate = CalendarUtils.getRecentEligibleMeetingDate(applicableCalendarData.getRecurrence(), seedDate);
} else if (calendarData.isBetweenStartAndEndDate(nextEligibleMeetingDate)) {
nextEligibleMeetingDate = CalendarUtils.getRecentEligibleMeetingDate(applicableCalendarData.getRecurrence(),
calendarData.getStartDate());
}
return nextEligibleMeetingDate;
}
@Override
public Collection<CalendarData> updateWithRecurringDates(final Collection<CalendarData> calendarsData) {
final Collection<CalendarData> recuCalendarsData = new ArrayList<CalendarData>();
final boolean withHistory = true;
final LocalDate tillDate = null;
for (final CalendarData calendarData : calendarsData) { for (final CalendarData calendarData : calendarsData) {
recuCalendarsData.add(generateRecurringDate(calendarData)); final Collection<LocalDate> recurringDates = this.generateRecurringDates(calendarData, withHistory, tillDate);
final Collection<LocalDate> nextTenRecurringDates = this.generateNextTenRecurringDates(calendarData);
final LocalDate recentEligibleMeetingDate = null;
final CalendarData updatedCalendarData = CalendarData.withRecurringDates(calendarData, recurringDates, nextTenRecurringDates,
recentEligibleMeetingDate);
recuCalendarsData.add(updatedCalendarData);
} }
return recuCalendarsData; return recuCalendarsData;
@@ -279,4 +372,83 @@ public class CalendarReadPlatformServiceImpl implements CalendarReadPlatformServ
return conditionSql; return conditionSql;
} }
private CalendarData retrieveApplicableCalendarFromHistory(Long calendarId, LocalDate compareDate) {
try {
final CalendarDataFromHistoryMapper rm = new CalendarDataFromHistoryMapper();
final String sql = rm.schema() + " where c.calendar_id = ? and date(?) between c.start_date and c.end_date limit 1";
return this.jdbcTemplate.queryForObject(sql, rm, new Object[] { calendarId, compareDate.toDate() });
} catch (final EmptyResultDataAccessException e) {
return null;
}
}
private Collection<CalendarData> retrieveCalendarsFromHistory(Long calendarId) {
try {
final CalendarDataFromHistoryMapper rm = new CalendarDataFromHistoryMapper();
final String sql = rm.schema() + " where c.calendar_id = ? ";
final Collection<CalendarData> calendars = this.jdbcTemplate.query(sql, rm, new Object[] { calendarId });
return calendars;
} catch (final EmptyResultDataAccessException e) {
return null;
}
}
private static final class CalendarDataFromHistoryMapper implements RowMapper<CalendarData> {
public String schema() {
return " select c.calendar_id as id, c.title as title, c.description as description, c.location as location, c.start_date as startDate, "
+ " c.end_date as endDate, c.duration as duration, c.calendar_type_enum as typeId, c.repeating as repeating, "
+ " c.recurrence as recurrence, c.remind_by_enum as remindById, c.first_reminder as firstReminder, c.second_reminder as secondReminder "
+ " from m_calendar_history c ";
}
@Override
public CalendarData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException {
final Long id = rs.getLong("id");
final Long calendarInstanceId = null;
final Long entityId = null;
final EnumOptionData entityType = null;
final String title = rs.getString("title");
final String description = rs.getString("description");
final String location = rs.getString("location");
final LocalDate startDate = JdbcSupport.getLocalDate(rs, "startDate");
final LocalDate endDate = JdbcSupport.getLocalDate(rs, "endDate");
final Integer duration = rs.getInt("duration");
final Integer typeId = rs.getInt("typeId");
final EnumOptionData type = CalendarEnumerations.calendarType(typeId);
final boolean repeating = rs.getBoolean("repeating");
final String recurrence = rs.getString("recurrence");
final EnumOptionData frequency = CalendarEnumerations.calendarFrequencyType(CalendarUtils.getFrequency(recurrence));
final Integer interval = new Integer(CalendarUtils.getInterval(recurrence));
final EnumOptionData repeatsOnDay = CalendarEnumerations.calendarWeekDaysType(CalendarUtils.getRepeatsOnDay(recurrence));
final Integer remindById = rs.getInt("remindById");
EnumOptionData remindBy = null;
if (remindById != null && remindById != 0) {
remindBy = CalendarEnumerations.calendarRemindBy(remindById);
}
final Integer firstReminder = rs.getInt("firstReminder");
final Integer secondReminder = rs.getInt("secondReminder");
String humanReadable = null;
if (startDate != null && recurrence != null) {
humanReadable = CalendarUtils.getRRuleReadable(startDate, recurrence);
}
final LocalDate createdDate = null;
final LocalDate lastUpdatedDate = null;
final Long createdByUserId = null;
final String createdByUserName = null;
final Long lastUpdatedByUserId = null;
final String lastUpdatedByUserName = null;
return CalendarData.instance(id, calendarInstanceId, entityId, entityType, title, description, location, startDate, endDate,
duration, type, repeating, recurrence, frequency, interval, repeatsOnDay, remindBy, firstReminder, secondReminder,
humanReadable, createdDate, lastUpdatedDate, createdByUserId, createdByUserName, lastUpdatedByUserId,
lastUpdatedByUserName);
}
}
} }
@@ -7,6 +7,7 @@ package org.mifosplatform.portfolio.calendar.service;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.Date;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@@ -20,18 +21,22 @@ import org.mifosplatform.infrastructure.core.data.CommandProcessingResult;
import org.mifosplatform.infrastructure.core.data.CommandProcessingResultBuilder; import org.mifosplatform.infrastructure.core.data.CommandProcessingResultBuilder;
import org.mifosplatform.infrastructure.core.data.DataValidatorBuilder; import org.mifosplatform.infrastructure.core.data.DataValidatorBuilder;
import org.mifosplatform.infrastructure.core.exception.PlatformApiDataValidationException; import org.mifosplatform.infrastructure.core.exception.PlatformApiDataValidationException;
import org.mifosplatform.infrastructure.core.service.DateUtils;
import org.mifosplatform.portfolio.calendar.CalendarConstants.CALENDAR_SUPPORTED_PARAMETERS; import org.mifosplatform.portfolio.calendar.CalendarConstants.CALENDAR_SUPPORTED_PARAMETERS;
import org.mifosplatform.portfolio.calendar.domain.Calendar; import org.mifosplatform.portfolio.calendar.domain.Calendar;
import org.mifosplatform.portfolio.calendar.domain.CalendarEntityType; import org.mifosplatform.portfolio.calendar.domain.CalendarEntityType;
import org.mifosplatform.portfolio.calendar.domain.CalendarHistory;
import org.mifosplatform.portfolio.calendar.domain.CalendarHistoryRepository;
import org.mifosplatform.portfolio.calendar.domain.CalendarInstance; import org.mifosplatform.portfolio.calendar.domain.CalendarInstance;
import org.mifosplatform.portfolio.calendar.domain.CalendarInstanceRepository; import org.mifosplatform.portfolio.calendar.domain.CalendarInstanceRepository;
import org.mifosplatform.portfolio.calendar.domain.CalendarRepository; import org.mifosplatform.portfolio.calendar.domain.CalendarRepository;
import org.mifosplatform.portfolio.calendar.domain.CalendarType;
import org.mifosplatform.portfolio.calendar.exception.CalendarNotFoundException; import org.mifosplatform.portfolio.calendar.exception.CalendarNotFoundException;
import org.mifosplatform.portfolio.calendar.serialization.CalendarCommandFromApiJsonDeserializer; import org.mifosplatform.portfolio.calendar.serialization.CalendarCommandFromApiJsonDeserializer;
import org.mifosplatform.portfolio.client.domain.Client; import org.mifosplatform.portfolio.client.domain.Client;
import org.mifosplatform.portfolio.client.domain.ClientRepositoryWrapper; import org.mifosplatform.portfolio.client.domain.ClientRepositoryWrapper;
import org.mifosplatform.portfolio.group.domain.Group; import org.mifosplatform.portfolio.group.domain.Group;
import org.mifosplatform.portfolio.group.domain.GroupRepository; import org.mifosplatform.portfolio.group.domain.GroupRepositoryWrapper;
import org.mifosplatform.portfolio.loanaccount.domain.Loan; import org.mifosplatform.portfolio.loanaccount.domain.Loan;
import org.mifosplatform.portfolio.loanaccount.domain.LoanRepository; import org.mifosplatform.portfolio.loanaccount.domain.LoanRepository;
import org.mifosplatform.portfolio.loanaccount.service.LoanWritePlatformService; import org.mifosplatform.portfolio.loanaccount.service.LoanWritePlatformService;
@@ -43,21 +48,24 @@ import org.springframework.util.CollectionUtils;
public class CalendarWritePlatformServiceJpaRepositoryImpl implements CalendarWritePlatformService { public class CalendarWritePlatformServiceJpaRepositoryImpl implements CalendarWritePlatformService {
private final CalendarRepository calendarRepository; private final CalendarRepository calendarRepository;
private final CalendarHistoryRepository calendarHistoryRepository;
private final CalendarCommandFromApiJsonDeserializer fromApiJsonDeserializer; private final CalendarCommandFromApiJsonDeserializer fromApiJsonDeserializer;
private final CalendarInstanceRepository calendarInstanceRepository; private final CalendarInstanceRepository calendarInstanceRepository;
private final LoanWritePlatformService loanWritePlatformService; private final LoanWritePlatformService loanWritePlatformService;
private final ConfigurationDomainService configurationDomainService; private final ConfigurationDomainService configurationDomainService;
private final GroupRepository groupRepository; private final GroupRepositoryWrapper groupRepository;
private final LoanRepository loanRepository; private final LoanRepository loanRepository;
private final ClientRepositoryWrapper clientRepository; private final ClientRepositoryWrapper clientRepository;
@Autowired @Autowired
public CalendarWritePlatformServiceJpaRepositoryImpl(final CalendarRepository calendarRepository, public CalendarWritePlatformServiceJpaRepositoryImpl(final CalendarRepository calendarRepository,
final CalendarHistoryRepository calendarHistoryRepository,
final CalendarCommandFromApiJsonDeserializer fromApiJsonDeserializer, final CalendarCommandFromApiJsonDeserializer fromApiJsonDeserializer,
final CalendarInstanceRepository calendarInstanceRepository, final LoanWritePlatformService loanWritePlatformService, final CalendarInstanceRepository calendarInstanceRepository, final LoanWritePlatformService loanWritePlatformService,
final ConfigurationDomainService configurationDomainService, final GroupRepository groupRepository, final ConfigurationDomainService configurationDomainService, final GroupRepositoryWrapper groupRepository,
final LoanRepository loanRepository, final ClientRepositoryWrapper clientRepository) { final LoanRepository loanRepository, final ClientRepositoryWrapper clientRepository) {
this.calendarRepository = calendarRepository; this.calendarRepository = calendarRepository;
this.calendarHistoryRepository = calendarHistoryRepository;
this.fromApiJsonDeserializer = fromApiJsonDeserializer; this.fromApiJsonDeserializer = fromApiJsonDeserializer;
this.calendarInstanceRepository = calendarInstanceRepository; this.calendarInstanceRepository = calendarInstanceRepository;
this.loanWritePlatformService = loanWritePlatformService; this.loanWritePlatformService = loanWritePlatformService;
@@ -71,25 +79,58 @@ public class CalendarWritePlatformServiceJpaRepositoryImpl implements CalendarWr
public CommandProcessingResult createCalendar(final JsonCommand command) { public CommandProcessingResult createCalendar(final JsonCommand command) {
this.fromApiJsonDeserializer.validateForCreate(command.json()); this.fromApiJsonDeserializer.validateForCreate(command.json());
final Long entityId = command.getSupportedEntityId();
final LocalDate entityActivationDate = getEntityActivationDate(command);
final CalendarEntityType entityType = CalendarEntityType.valueOf(command.getSupportedEntityType().toUpperCase()); final CalendarEntityType entityType = CalendarEntityType.valueOf(command.getSupportedEntityType().toUpperCase());
LocalDate entityActivationDate = null;
Group centerOrGroup = null;
if (entityType.isCenter() || entityType.isGroup()) {
centerOrGroup = this.groupRepository.findOneWithNotFoundDetection(entityId);
entityActivationDate = centerOrGroup.getActivationLocalDate();
} else if (entityType.isLoan()) {
final Loan loan = this.loanRepository.findOne(entityId);
entityActivationDate = (loan.getApprovedOnDate() == null) ? loan.getSubmittedOnDate() : loan.getApprovedOnDate();
} else if (entityType.isLoan()) {
final Client client = this.clientRepository.findOneWithNotFoundDetection(entityId);
entityActivationDate = client.getActivationLocalDate();
}
final Calendar newCalendar = Calendar.fromJson(command); final Calendar newCalendar = Calendar.fromJson(command);
final List<ApiParameterError> dataValidationErrors = new ArrayList<ApiParameterError>(); final List<ApiParameterError> dataValidationErrors = new ArrayList<ApiParameterError>();
final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors) final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("calendar");
.resource("calendar"); if (entityActivationDate == null || newCalendar.getStartDateLocalDate().isBefore(entityActivationDate)) {
if(entityActivationDate == null || newCalendar.getStartDateLocalDate().isBefore(entityActivationDate)){
final DateTimeFormatter formatter = DateTimeFormat.forPattern(command.dateFormat()).withLocale(command.extractLocale()); final DateTimeFormatter formatter = DateTimeFormat.forPattern(command.dateFormat()).withLocale(command.extractLocale());
String dateAsString = ""; String dateAsString = "";
if(entityActivationDate != null) dateAsString = formatter.print(entityActivationDate); if (entityActivationDate != null) dateAsString = formatter.print(entityActivationDate);
final String errorMessage = "cannot.be.before." + entityType.name().toLowerCase() + ".activation.date"; final String errorMessage = "cannot.be.before." + entityType.name().toLowerCase() + ".activation.date";
baseDataValidator.reset().parameter(CALENDAR_SUPPORTED_PARAMETERS.START_DATE.getValue()).value(dateAsString) baseDataValidator.reset().parameter(CALENDAR_SUPPORTED_PARAMETERS.START_DATE.getValue()).value(dateAsString)
.failWithCodeNoParameterAddedToErrorCode(errorMessage); .failWithCodeNoParameterAddedToErrorCode(errorMessage);
if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
} }
if (entityType.isCenter() || entityType.isGroup()) {
Long centerOrGroupId = entityId;
Integer centerOrGroupEntityTypeId = entityType.getValue();
if (entityType.isGroup()) {
@SuppressWarnings("null")
final Group parent = centerOrGroup.getParent();
if (parent != null) {
centerOrGroupId = parent.getId();
centerOrGroupEntityTypeId = CalendarEntityType.CENTERS.getValue();
}
}
final CalendarInstance collectionCalendarInstance = this.calendarInstanceRepository
.findByEntityIdAndEntityTypeIdAndCalendarTypeId(centerOrGroupId, centerOrGroupEntityTypeId,
CalendarType.COLLECTION.getValue());
if (collectionCalendarInstance != null) {
final String errorMessage = "multiple.collection.calendar.not.supported";
baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode(errorMessage);
}
}
if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
this.calendarRepository.save(newCalendar); this.calendarRepository.save(newCalendar);
@@ -103,25 +144,6 @@ public class CalendarWritePlatformServiceJpaRepositoryImpl implements CalendarWr
} }
private LocalDate getEntityActivationDate(JsonCommand command) {
final Long entityId = command.getSupportedEntityId();
final CalendarEntityType entityType = CalendarEntityType.valueOf(command.getSupportedEntityType().toUpperCase());
LocalDate entityActivationDate = null;
if(entityType.isCenter() || entityType.isGroup()){
final Group centerOrGroup = this.groupRepository.findOne(entityId);
entityActivationDate = centerOrGroup.getActivationLocalDate();
} else if (entityType.isLoan()){
final Loan loan = this.loanRepository.findOne(entityId);
entityActivationDate = (loan.getApprovedOnDate() == null) ? loan.getSubmittedOnDate() : loan.getApprovedOnDate();
} else if (entityType.isLoan()){
final Client client = this.clientRepository.findOneWithNotFoundDetection(entityId);
entityActivationDate = client.getActivationLocalDate();
}
return entityActivationDate;
}
@Override @Override
public CommandProcessingResult updateCalendar(final JsonCommand command) { public CommandProcessingResult updateCalendar(final JsonCommand command) {
@@ -130,25 +152,30 @@ public class CalendarWritePlatformServiceJpaRepositoryImpl implements CalendarWr
final Long calendarId = command.entityId(); final Long calendarId = command.entityId();
final Calendar calendarForUpdate = this.calendarRepository.findOne(calendarId); final Calendar calendarForUpdate = this.calendarRepository.findOne(calendarId);
if (calendarForUpdate == null) { throw new CalendarNotFoundException(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); final Map<String, Object> changes = calendarForUpdate.update(command);
if (!changes.isEmpty()) { if (!changes.isEmpty()) {
//update calendar history table only if there is a change in calendar start date.
if(currentDate.isAfter(new LocalDate(oldStartDate))){
final Date endDate = calendarForUpdate.getStartDateLocalDate().minusDays(1).toDate();
calendarHistory.updateEndDate(endDate);
this.calendarHistoryRepository.save(calendarHistory);
}
this.calendarRepository.saveAndFlush(calendarForUpdate); this.calendarRepository.saveAndFlush(calendarForUpdate);
if (this.configurationDomainService.isRescheduleFutureRepaymentsEnabled()) { if (this.configurationDomainService.isRescheduleFutureRepaymentsEnabled() && calendarForUpdate.isRepeating()) {
// In this approach loans following this meeting calendar will //fetch all loan calendar instances associated with modifying calendar.
// be affected immediately.
// get all calendar instances following this meeting calendar
// and calendar entity type is loan
final Collection<CalendarInstance> loanCalendarInstances = this.calendarInstanceRepository.findByCalendarIdAndEntityTypeId( final Collection<CalendarInstance> loanCalendarInstances = this.calendarInstanceRepository.findByCalendarIdAndEntityTypeId(
calendarId, CalendarEntityType.LOANS.getValue()); calendarId, CalendarEntityType.LOANS.getValue());
if (!CollectionUtils.isEmpty(loanCalendarInstances)) { if (!CollectionUtils.isEmpty(loanCalendarInstances)) {
// update all loans which are following this meeting //update all loans associated with modifying calendar
// calendar
this.loanWritePlatformService.applyMeetingDateChanges(calendarForUpdate, loanCalendarInstances); this.loanWritePlatformService.applyMeetingDateChanges(calendarForUpdate, loanCalendarInstances);
//
} }
} }
} }
@@ -30,7 +30,6 @@ import org.mifosplatform.portfolio.calendar.domain.Calendar;
import org.mifosplatform.portfolio.calendar.domain.CalendarEntityType; import org.mifosplatform.portfolio.calendar.domain.CalendarEntityType;
import org.mifosplatform.portfolio.calendar.domain.CalendarRepositoryWrapper; import org.mifosplatform.portfolio.calendar.domain.CalendarRepositoryWrapper;
import org.mifosplatform.portfolio.calendar.exception.NotValidRecurringDateException; import org.mifosplatform.portfolio.calendar.exception.NotValidRecurringDateException;
import org.mifosplatform.portfolio.calendar.service.CalendarUtils;
import org.mifosplatform.portfolio.collectionsheet.data.JLGClientData; import org.mifosplatform.portfolio.collectionsheet.data.JLGClientData;
import org.mifosplatform.portfolio.collectionsheet.data.JLGCollectionSheetData; import org.mifosplatform.portfolio.collectionsheet.data.JLGCollectionSheetData;
import org.mifosplatform.portfolio.collectionsheet.data.JLGCollectionSheetFlatData; import org.mifosplatform.portfolio.collectionsheet.data.JLGCollectionSheetFlatData;
@@ -304,9 +303,11 @@ public class CollectionSheetReadPlatformServiceImpl implements CollectionSheetRe
final String transactionDateStr = df.format(transactionDate.toDate()); final String transactionDateStr = df.format(transactionDate.toDate());
final Calendar calendar = this.calendarRepositoryWrapper.findOneWithNotFoundDetection(calendarId); final Calendar calendar = this.calendarRepositoryWrapper.findOneWithNotFoundDetection(calendarId);
if (!CalendarUtils.isValidRedurringDate(calendar.getRecurrence(), calendar.getStartDateLocalDate(), transactionDate)) { throw new NotValidRecurringDateException( // check if transaction against calendar effective from date
"collectionsheet", "The date '" + transactionDate + "' is not a valid meeting date.", transactionDate); }
if (!calendar.isValidRecurringDate(transactionDate)) { throw new NotValidRecurringDateException("collectionsheet", "The date '"
+ transactionDate + "' is not a valid meeting date.", transactionDate); }
final AppUser currentUser = this.context.authenticatedUser(); final AppUser currentUser = this.context.authenticatedUser();
final String hierarchy = currentUser.getOffice().getHierarchy(); final String hierarchy = currentUser.getOffice().getHierarchy();
final String officeHierarchy = hierarchy + "%"; final String officeHierarchy = hierarchy + "%";
@@ -360,5 +361,4 @@ public class CollectionSheetReadPlatformServiceImpl implements CollectionSheetRe
return buildJLGCollectionSheet(transactionDate, collectionSheetFlatDatas); return buildJLGCollectionSheet(transactionDate, collectionSheetFlatDatas);
} }
} }
@@ -25,6 +25,7 @@ import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.UriInfo; import javax.ws.rs.core.UriInfo;
import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.StringUtils;
import org.joda.time.LocalDate;
import org.mifosplatform.commands.domain.CommandWrapper; import org.mifosplatform.commands.domain.CommandWrapper;
import org.mifosplatform.commands.service.CommandWrapperBuilder; import org.mifosplatform.commands.service.CommandWrapperBuilder;
import org.mifosplatform.commands.service.PortfolioCommandSourceWritePlatformService; import org.mifosplatform.commands.service.PortfolioCommandSourceWritePlatformService;
@@ -49,6 +50,8 @@ import org.mifosplatform.portfolio.group.data.CenterData;
import org.mifosplatform.portfolio.group.data.GroupGeneralData; import org.mifosplatform.portfolio.group.data.GroupGeneralData;
import org.mifosplatform.portfolio.group.service.CenterReadPlatformService; import org.mifosplatform.portfolio.group.service.CenterReadPlatformService;
import org.mifosplatform.portfolio.group.service.SearchParameters; import org.mifosplatform.portfolio.group.service.SearchParameters;
import org.mifosplatform.portfolio.meeting.data.MeetingData;
import org.mifosplatform.portfolio.meeting.service.MeetingReadPlatformService;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Scope; import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
@@ -71,6 +74,7 @@ public class CentersApiResource {
private final FromJsonHelper fromJsonHelper; private final FromJsonHelper fromJsonHelper;
private final AccountDetailsReadPlatformService accountDetailsReadPlatformService; private final AccountDetailsReadPlatformService accountDetailsReadPlatformService;
private final CalendarReadPlatformService calendarReadPlatformService; private final CalendarReadPlatformService calendarReadPlatformService;
private final MeetingReadPlatformService meetingReadPlatformService;
@Autowired @Autowired
public CentersApiResource(final PlatformSecurityContext context, final CenterReadPlatformService centerReadPlatformService, public CentersApiResource(final PlatformSecurityContext context, final CenterReadPlatformService centerReadPlatformService,
@@ -80,7 +84,7 @@ public class CentersApiResource {
final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService, final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService,
final CollectionSheetReadPlatformService collectionSheetReadPlatformService, final FromJsonHelper fromJsonHelper, final CollectionSheetReadPlatformService collectionSheetReadPlatformService, final FromJsonHelper fromJsonHelper,
final AccountDetailsReadPlatformService accountDetailsReadPlatformService, final AccountDetailsReadPlatformService accountDetailsReadPlatformService,
final CalendarReadPlatformService calendarReadPlatformService) { final CalendarReadPlatformService calendarReadPlatformService, final MeetingReadPlatformService meetingReadPlatformService) {
this.context = context; this.context = context;
this.centerReadPlatformService = centerReadPlatformService; this.centerReadPlatformService = centerReadPlatformService;
this.centerApiJsonSerializer = centerApiJsonSerializer; this.centerApiJsonSerializer = centerApiJsonSerializer;
@@ -92,6 +96,7 @@ public class CentersApiResource {
this.fromJsonHelper = fromJsonHelper; this.fromJsonHelper = fromJsonHelper;
this.accountDetailsReadPlatformService = accountDetailsReadPlatformService; this.accountDetailsReadPlatformService = accountDetailsReadPlatformService;
this.calendarReadPlatformService = calendarReadPlatformService; this.calendarReadPlatformService = calendarReadPlatformService;
this.meetingReadPlatformService = meetingReadPlatformService;
} }
@GET @GET
@@ -164,7 +169,13 @@ public class CentersApiResource {
collectionMeetingCalendar = this.calendarReadPlatformService.retrieveCollctionCalendarByEntity(centerId, collectionMeetingCalendar = this.calendarReadPlatformService.retrieveCollctionCalendarByEntity(centerId,
CalendarEntityType.CENTERS.getValue()); CalendarEntityType.CENTERS.getValue());
if (collectionMeetingCalendar != null) { if (collectionMeetingCalendar != null) {
collectionMeetingCalendar = this.calendarReadPlatformService.generateRecurringDate(collectionMeetingCalendar); final boolean withHistory = true;
final LocalDate tillDate = null;
final Collection<LocalDate> recurringDates = this.calendarReadPlatformService.generateRecurringDates(collectionMeetingCalendar, withHistory, tillDate);
final Collection<LocalDate> nextTenRecurringDates = this.calendarReadPlatformService.generateNextTenRecurringDates(collectionMeetingCalendar);
final MeetingData lastMeeting = this.meetingReadPlatformService.retrieveLastMeeting(collectionMeetingCalendar.getCalendarInstanceId());
final LocalDate recentEligibleMeetingDate = this.calendarReadPlatformService.generateNextEligibleMeetingDateForCollection(collectionMeetingCalendar, lastMeeting);
collectionMeetingCalendar = CalendarData.withRecurringDates(collectionMeetingCalendar, recurringDates, nextTenRecurringDates, recentEligibleMeetingDate);
} }
} }
@@ -26,6 +26,7 @@ import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.UriInfo; import javax.ws.rs.core.UriInfo;
import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.StringUtils;
import org.joda.time.LocalDate;
import org.mifosplatform.commands.domain.CommandWrapper; import org.mifosplatform.commands.domain.CommandWrapper;
import org.mifosplatform.commands.service.CommandWrapperBuilder; import org.mifosplatform.commands.service.CommandWrapperBuilder;
import org.mifosplatform.commands.service.PortfolioCommandSourceWritePlatformService; import org.mifosplatform.commands.service.PortfolioCommandSourceWritePlatformService;
@@ -55,6 +56,8 @@ import org.mifosplatform.portfolio.group.service.CenterReadPlatformService;
import org.mifosplatform.portfolio.group.service.GroupReadPlatformService; import org.mifosplatform.portfolio.group.service.GroupReadPlatformService;
import org.mifosplatform.portfolio.group.service.GroupRolesReadPlatformService; import org.mifosplatform.portfolio.group.service.GroupRolesReadPlatformService;
import org.mifosplatform.portfolio.group.service.SearchParameters; import org.mifosplatform.portfolio.group.service.SearchParameters;
import org.mifosplatform.portfolio.meeting.data.MeetingData;
import org.mifosplatform.portfolio.meeting.service.MeetingReadPlatformService;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Scope; import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
@@ -81,6 +84,7 @@ public class GroupsApiResource {
private final GroupRolesReadPlatformService groupRolesReadPlatformService; private final GroupRolesReadPlatformService groupRolesReadPlatformService;
private final AccountDetailsReadPlatformService accountDetailsReadPlatformService; private final AccountDetailsReadPlatformService accountDetailsReadPlatformService;
private final CalendarReadPlatformService calendarReadPlatformService; private final CalendarReadPlatformService calendarReadPlatformService;
private final MeetingReadPlatformService meetingReadPlatformService;
@Autowired @Autowired
public GroupsApiResource(final PlatformSecurityContext context, final GroupReadPlatformService groupReadPlatformService, public GroupsApiResource(final PlatformSecurityContext context, final GroupReadPlatformService groupReadPlatformService,
@@ -93,7 +97,7 @@ public class GroupsApiResource {
final CollectionSheetReadPlatformService collectionSheetReadPlatformService, final FromJsonHelper fromJsonHelper, final CollectionSheetReadPlatformService collectionSheetReadPlatformService, final FromJsonHelper fromJsonHelper,
final GroupRolesReadPlatformService groupRolesReadPlatformService, final GroupRolesReadPlatformService groupRolesReadPlatformService,
final AccountDetailsReadPlatformService accountDetailsReadPlatformService, final AccountDetailsReadPlatformService accountDetailsReadPlatformService,
final CalendarReadPlatformService calendarReadPlatformService) { final CalendarReadPlatformService calendarReadPlatformService, final MeetingReadPlatformService meetingReadPlatformService) {
this.context = context; this.context = context;
this.groupReadPlatformService = groupReadPlatformService; this.groupReadPlatformService = groupReadPlatformService;
@@ -109,6 +113,7 @@ public class GroupsApiResource {
this.groupRolesReadPlatformService = groupRolesReadPlatformService; this.groupRolesReadPlatformService = groupRolesReadPlatformService;
this.accountDetailsReadPlatformService = accountDetailsReadPlatformService; this.accountDetailsReadPlatformService = accountDetailsReadPlatformService;
this.calendarReadPlatformService = calendarReadPlatformService; this.calendarReadPlatformService = calendarReadPlatformService;
this.meetingReadPlatformService = meetingReadPlatformService;
} }
@GET @GET
@@ -216,7 +221,13 @@ public class GroupsApiResource {
CalendarEntityType.GROUPS.getValue()); CalendarEntityType.GROUPS.getValue());
} }
if (collectionMeetingCalendar != null) { if (collectionMeetingCalendar != null) {
collectionMeetingCalendar = this.calendarReadPlatformService.generateRecurringDate(collectionMeetingCalendar); final boolean withHistory = true;
final LocalDate tillDate = null;
final Collection<LocalDate> recurringDates = this.calendarReadPlatformService.generateRecurringDates(collectionMeetingCalendar, withHistory, tillDate);
final Collection<LocalDate> nextTenRecurringDates = this.calendarReadPlatformService.generateNextTenRecurringDates(collectionMeetingCalendar);
final MeetingData lastMeeting = this.meetingReadPlatformService.retrieveLastMeeting(collectionMeetingCalendar.getCalendarInstanceId());
final LocalDate recentEligibleMeetingDate = this.calendarReadPlatformService.generateNextEligibleMeetingDateForCollection(collectionMeetingCalendar, lastMeeting);
collectionMeetingCalendar = CalendarData.withRecurringDates(collectionMeetingCalendar, recurringDates, nextTenRecurringDates, recentEligibleMeetingDate);
} }
} }
@@ -207,7 +207,7 @@ public class LoanScheduleAssembler {
private void validateDisbursementDateWithMeetingDates(final LocalDate expectedDisbursementDate, final Calendar calendar) { private void validateDisbursementDateWithMeetingDates(final LocalDate expectedDisbursementDate, final Calendar calendar) {
// disbursement date should fall on a meeting date // disbursement date should fall on a meeting date
if (!CalendarUtils.isValidRedurringDate(calendar.getRecurrence(), calendar.getStartDateLocalDate(), expectedDisbursementDate)) { if (!calendar.isValidRecurringDate(expectedDisbursementDate)) {
final String errorMessage = "Expected disbursement date '" + expectedDisbursementDate + "' do not fall on a meeting date"; final String errorMessage = "Expected disbursement date '" + expectedDisbursementDate + "' do not fall on a meeting date";
throw new LoanApplicationDateException("disbursement.date.do.not.match.meeting.date", errorMessage, expectedDisbursementDate); throw new LoanApplicationDateException("disbursement.date.do.not.match.meeting.date", errorMessage, expectedDisbursementDate);
} }
@@ -229,7 +229,7 @@ public class LoanScheduleAssembler {
if (repaymentInterval % meetingInterval != 0) { if (repaymentInterval % meetingInterval != 0) {
// throw exception: Loan product frequency/interval // throw exception: Loan product frequency/interval
throw new MeetingFrequencyMismatchException("loanapplication.repayment.interval", throw new MeetingFrequencyMismatchException("loanapplication.repayment.interval",
"Loan repayment repaid every # must equal or multiple of meeting interval", repaymentInterval); "Loan repayment repaid every # must equal or multiple of meeting interval " + meetingInterval, meetingInterval, repaymentInterval);
} }
} }
} }
@@ -74,15 +74,12 @@ public final class LoanEventApiJsonValidator {
public void validateDisbursementDateWithMeetingDate(final LocalDate actualDisbursementDate, final CalendarInstance calendarInstance) { public void validateDisbursementDateWithMeetingDate(final LocalDate actualDisbursementDate, final CalendarInstance calendarInstance) {
if (null != calendarInstance) { if (null != calendarInstance) {
final Calendar calendar = calendarInstance.getCalendar(); final Calendar calendar = calendarInstance.getCalendar();
if (calendar != null && actualDisbursementDate != null) { if (!calendar.isValidRecurringDate(actualDisbursementDate)) {
// Disbursement date should fall on a meeting date // Disbursement date should fall on a meeting date
if (!CalendarUtils.isValidRedurringDate(calendar.getRecurrence(), calendar.getStartDateLocalDate(), actualDisbursementDate)) {
final String errorMessage = "Expected disbursement date '" + actualDisbursementDate.toString() final String errorMessage = "Expected disbursement date '" + actualDisbursementDate.toString()
+ "' does not fall on a meeting date."; + "' does not fall on a meeting date.";
throw new NotValidRecurringDateException("loan.actual.disbursement.date", errorMessage, throw new NotValidRecurringDateException("loan.actual.disbursement.date", errorMessage,
actualDisbursementDate.toString(), calendar.getTitle()); actualDisbursementDate.toString(), calendar.getTitle());
}
} }
} }
} }
@@ -1080,7 +1080,7 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService {
CalendarEntityType.GROUPS.getValue(), null)); CalendarEntityType.GROUPS.getValue(), null));
calendarsData calendarsData
.addAll(this.calendarReadPlatformService.retrieveCalendarsByEntity(groupId, CalendarEntityType.GROUPS.getValue(), null)); .addAll(this.calendarReadPlatformService.retrieveCalendarsByEntity(groupId, CalendarEntityType.GROUPS.getValue(), null));
calendarsData = this.calendarReadPlatformService.generateRecurringDates(calendarsData); calendarsData = this.calendarReadPlatformService.updateWithRecurringDates(calendarsData);
return calendarsData; return calendarsData;
} }
@@ -24,6 +24,7 @@ import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.UriInfo; import javax.ws.rs.core.UriInfo;
import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.StringUtils;
import org.joda.time.LocalDate;
import org.mifosplatform.commands.domain.CommandWrapper; import org.mifosplatform.commands.domain.CommandWrapper;
import org.mifosplatform.commands.service.CommandWrapperBuilder; import org.mifosplatform.commands.service.CommandWrapperBuilder;
import org.mifosplatform.commands.service.PortfolioCommandSourceWritePlatformService; import org.mifosplatform.commands.service.PortfolioCommandSourceWritePlatformService;
@@ -107,7 +108,11 @@ public class MeetingsApiResource {
if (calendarId != null) { if (calendarId != null) {
calendarData = this.calendarReadPlatformService.retrieveCalendar(calendarId, entityId, entityTypeId); calendarData = this.calendarReadPlatformService.retrieveCalendar(calendarId, entityId, entityTypeId);
calendarData = this.calendarReadPlatformService.generateRecurringDate(calendarData, DateUtils.getLocalDateOfTenant()); final boolean withHistory = true;
final Collection<LocalDate> recurringDates = this.calendarReadPlatformService.generateRecurringDates(calendarData, withHistory, DateUtils.getLocalDateOfTenant());
final Collection<LocalDate> nextTenRecurringDates = this.calendarReadPlatformService.generateNextTenRecurringDates(calendarData);
final LocalDate recentEligibleMeetingDate = null;
calendarData = CalendarData.withRecurringDates(calendarData, recurringDates, nextTenRecurringDates, recentEligibleMeetingDate);
} }
final MeetingData meetingData = MeetingData.template(clients, calendarData, final MeetingData meetingData = MeetingData.template(clients, calendarData,
@@ -66,4 +66,9 @@ public class MeetingData {
this.calendarData = calendarData; this.calendarData = calendarData;
this.attendanceTypeOptions = attendanceTypeOptions; this.attendanceTypeOptions = attendanceTypeOptions;
} }
public LocalDate getMeetingDate() {
return this.meetingDate;
}
} }
@@ -172,8 +172,9 @@ public class Meeting extends AbstractPersistable<Long> {
if (meetingDate != null) { if (meetingDate != null) {
meetingDateLocalDate = LocalDate.fromDateFields(meetingDate); meetingDateLocalDate = LocalDate.fromDateFields(meetingDate);
} }
if (meetingDateLocalDate == null if (meetingDateLocalDate == null
|| !CalendarUtils.isValidRedurringDate(calendar.getRecurrence(), calendar.getStartDateLocalDate(), meetingDateLocalDate)) { return false; } || !calendar.isValidRecurringDate(meetingDateLocalDate)) { return false; }
return true; return true;
} }
@@ -18,4 +18,6 @@ public interface MeetingReadPlatformService {
Collection<MeetingData> retrieveMeetingsByEntityByCalendarType(final Long entityId, final Integer entityTypeId, Collection<MeetingData> retrieveMeetingsByEntityByCalendarType(final Long entityId, final Integer entityTypeId,
final List<Integer> calendarTypeOptions); final List<Integer> calendarTypeOptions);
MeetingData retrieveLastMeeting(Long calendarInstanceId);
} }
@@ -36,7 +36,8 @@ public class MeetingReadPlatformServiceImpl implements MeetingReadPlatformServic
public String schema() { public String schema() {
return " select m.id as id, m.meeting_date as meetingDate from m_meeting m inner join m_calendar_instance ci on m.calendar_instance_id = ci.id "; return " select m.id as id, m.meeting_date as meetingDate from m_meeting m " +
"inner join m_calendar_instance ci on m.calendar_instance_id = ci.id ";
} }
@Override @Override
@@ -86,4 +87,17 @@ public class MeetingReadPlatformServiceImpl implements MeetingReadPlatformServic
return this.jdbcTemplate.query(sql, rm, new Object[] { entityId, entityTypeId }); return this.jdbcTemplate.query(sql, rm, new Object[] { entityId, entityTypeId });
} }
@Override
public MeetingData retrieveLastMeeting(Long calendarInstanceId) {
try {
final MeetingDataMapper rm = new MeetingDataMapper();
final String sql = rm.schema() + " where ci.id = ? order by m.meeting_date desc, m.id desc limit 1";
return this.jdbcTemplate.queryForObject(sql, rm, new Object[] { calendarInstanceId });
} catch (final EmptyResultDataAccessException e) {
return null;
}
}
} }