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 static final String CALENDAR_RESOURCE_NAME = "calendar";
public static enum CALENDAR_SUPPORTED_PARAMETERS {
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(
@@ -26,6 +26,7 @@ import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.UriInfo;
import org.joda.time.LocalDate;
import org.mifosplatform.commands.domain.CommandWrapper;
import org.mifosplatform.commands.service.CommandWrapperBuilder;
import org.mifosplatform.commands.service.PortfolioCommandSourceWritePlatformService;
@@ -94,7 +95,12 @@ public class CalendarsApiResource {
CalendarData calendarData = this.readPlatformService.retrieveCalendar(calendarId, entityId, entityTypeId);
// 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());
if (settings.isTemplate()) {
@@ -135,7 +141,7 @@ public class CalendarsApiResource {
CalendarEntityType.valueOf(entityType.toUpperCase()).getValue(), calendarTypeOptions));
// Add recurring dates
calendarsData = this.readPlatformService.generateRecurringDates(calendarsData);
calendarsData = this.readPlatformService.updateWithRecurringDates(calendarsData);
final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
return this.toApiJsonSerializer.serialize(settings, calendarsData, this.RESPONSE_DATA_PARAMETERS);
@@ -22,6 +22,7 @@ import org.mifosplatform.portfolio.calendar.service.CalendarEnumerations;
public class CalendarData {
private final Long id;
private final Long calendarInstanceId;
private final Long entityId;
private final EnumOptionData entityType;
private final String title;
@@ -58,7 +59,7 @@ public class CalendarData {
final List<EnumOptionData> frequencyOptions;
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 EnumOptionData type, final boolean repeating, final String recurrence, final EnumOptionData frequency,
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> 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,
nextTenRecurringDates, humanReadable, recentEligibleMeetingDate, createdDate, lastUpdatedDate, createdByUserId,
createdByUsername, lastUpdatedByUserId, lastUpdatedByUsername, entityTypeOptions, calendarTypeOptions, remindByOptions,
@@ -84,7 +85,7 @@ public class CalendarData {
public static CalendarData withRecurringDates(final CalendarData calendarData, final Collection<LocalDate> recurringDates,
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.type, calendarData.repeating, calendarData.recurrence, calendarData.frequency, calendarData.interval,
calendarData.repeatsOnDay, calendarData.remindBy, calendarData.firstReminder, calendarData.secondReminder, recurringDates,
@@ -94,9 +95,22 @@ public class CalendarData {
calendarData.calendarTypeOptions, calendarData.remindByOptions, calendarData.frequencyOptions,
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() {
final Long id = null;
final Long calendarInstanceId = null;
final Long entityId = null;
final EnumOptionData entityType = null;
final String title = null;
@@ -132,7 +146,7 @@ public class CalendarData {
final Long lastUpdatedByUserId = 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,
nextTenRecurringDates, humanReadable, recentEligibleMeetingDate, createdDate, lastUpdatedDate, createdByUserId,
createdByUsername, lastUpdatedByUserId, lastUpdatedByUsername, entityTypeOptions, calendarTypeOptions, remindByOptions,
@@ -143,7 +157,7 @@ public class CalendarData {
final List<EnumOptionData> calendarTypeOptions, final List<EnumOptionData> remindByOptions,
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.type, calendarData.repeating, calendarData.recurrence, calendarData.frequency, calendarData.interval,
calendarData.repeatsOnDay, calendarData.remindBy, calendarData.firstReminder, calendarData.secondReminder,
@@ -153,7 +167,7 @@ public class CalendarData {
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 boolean repeating, final String recurrence, final EnumOptionData frequency, final Integer interval,
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> repeatsOnDayOptions) {
this.id = id;
this.calendarInstanceId = calendarInstanceId;
this.entityId = entityId;
this.entityType = entityType;
this.title = title;
@@ -278,4 +293,31 @@ public class CalendarData {
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;
import static org.mifosplatform.portfolio.calendar.CalendarConstants.CALENDAR_RESOURCE_NAME;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.JoinColumn;
import javax.persistence.OneToMany;
import javax.persistence.Table;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;
@@ -19,7 +28,11 @@ import javax.persistence.TemporalType;
import org.apache.commons.lang.StringUtils;
import org.joda.time.LocalDate;
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.exception.PlatformApiDataValidationException;
import org.mifosplatform.infrastructure.core.service.DateUtils;
import org.mifosplatform.portfolio.calendar.CalendarConstants.CALENDAR_SUPPORTED_PARAMETERS;
import org.mifosplatform.portfolio.calendar.exception.CalendarDateException;
import org.mifosplatform.portfolio.calendar.exception.CalendarParameterUpdateNotSupportedException;
@@ -68,6 +81,10 @@ public class Calendar extends AbstractAuditableCustom<AppUser, Long> {
@Column(name = "second_reminder", nullable = true)
private Integer secondReminder;
@OneToMany(fetch = FetchType.EAGER)
@JoinColumn(name = "calendar_id")
private Set<CalendarHistory> calendarHistory = new HashSet<CalendarHistory>();
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,
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 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.description = StringUtils.defaultIfEmpty(description, 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 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("dateFormat", dateFormatAsInput);
actualChanges.put("locale", localeAsInput);
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;
}
//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);
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
// meetings or for all?.
@@ -319,9 +365,48 @@ public class Calendar extends AbstractAuditableCustom<AppUser, Long> {
}
return endDateLocalDate;
}
public Set<CalendarHistory> history() {
return this.calendarHistory;
}
public boolean isStartDateBefore(final LocalDate newStartDate) {
if (this.startDate != null && newStartDate != null && getStartDateLocalDate().isBefore(newStartDate)) { return true; }
public boolean isStartDateBefore(final LocalDate compareDate) {
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;
}
@@ -358,4 +443,18 @@ public class Calendar extends AbstractAuditableCustom<AppUser, Long> {
}
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() {
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.mifosplatform.portfolio.calendar.data.CalendarData;
import org.mifosplatform.portfolio.meeting.data.MeetingData;
public interface CalendarReadPlatformService {
@@ -24,14 +25,16 @@ public interface CalendarReadPlatformService {
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 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.CalendarType;
import org.mifosplatform.portfolio.calendar.exception.CalendarNotFoundException;
import org.mifosplatform.portfolio.meeting.data.MeetingData;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.EmptyResultDataAccessException;
import org.springframework.jdbc.core.JdbcTemplate;
@@ -40,7 +41,7 @@ public class CalendarReadPlatformServiceImpl implements CalendarReadPlatformServ
private static final class CalendarDataMapper implements RowMapper<CalendarData> {
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.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, "
@@ -54,6 +55,7 @@ public class CalendarReadPlatformServiceImpl implements CalendarReadPlatformServ
public CalendarData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException {
final Long id = rs.getLong("id");
final Long calendarInstanceId = rs.getLong("calendarInstanceId");
final Long entityId = rs.getLong("entityId");
final Integer entityTypeId = rs.getInt("entityTypeId");
final EnumOptionData entityType = CalendarEnumerations.calendarEntityType(entityTypeId);
@@ -89,9 +91,10 @@ public class CalendarReadPlatformServiceImpl implements CalendarReadPlatformServ
final Long lastUpdatedByUserId = rs.getLong("updatingUserId");
final String lastUpdatedByUserName = rs.getString("updatingUserName");
return CalendarData.instance(id, entityId, entityType, title, description, location, startDate, endDate, duration, type,
repeating, recurrence, frequency, interval, repeatsOnDay, remindBy, firstReminder, secondReminder, humanReadable, createdDate,
lastUpdatedDate, createdByUserId, createdByUserName, lastUpdatedByUserId, lastUpdatedByUserName);
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);
}
}
@@ -184,49 +187,139 @@ public class CalendarReadPlatformServiceImpl implements CalendarReadPlatformServ
}
@Override
public CalendarData generateRecurringDate(final CalendarData calendarData) {
return generateRecurringDate(calendarData, null);
public Collection<LocalDate> generateRecurringDates(final CalendarData calendarData, final boolean withHistory, final LocalDate tillDate) {
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
public CalendarData generateRecurringDate(final CalendarData calendarData, final LocalDate tillDate) {
if (!calendarData.isRepeating()) { return calendarData; }
public Collection<LocalDate> generateNextTenRecurringDates(CalendarData 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 LocalDate currentDate = DateUtils.getLocalDateOfTenant();
final LocalDate seedDate = calendarData.getStartDate();
LocalDate endDate = calendarData.getEndDate();
LocalDate startDate = calendarData.getStartDate();
if (seedDate.isBefore(currentDate.minusYears(1))) {
startDate = currentDate.minusYears(1);
/**
* Start date or effective from date of calendar recurrence.
*/
final LocalDate seedDate = this.getSeedDate(calendarData.getStartDate());
/**
* 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 (endDate != null) {
if (endDate.isAfter(tillDate)) {
endDate = tillDate;// to retrieve meeting dates till
// specified date (tillDate)
// to retrieve meeting dates tillspecified date (tillDate)
periodEndDate = tillDate;
}
} else {
endDate = tillDate;// end date is null then fetch meeting dates
// tillDate
// end date is null then fetch meeting dates tillDate
periodEndDate = tillDate;
}
} else if (endDate == null || endDate.isAfter(currentDate.plusYears(1))) {
endDate = currentDate.plusYears(1);
periodEndDate = currentDate.plusYears(1);
}
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);
return periodEndDate;
}
@Override
public Collection<CalendarData> generateRecurringDates(final Collection<CalendarData> calendarsData) {
final Collection<CalendarData> recuCalendarsData = new ArrayList<CalendarData>();
public LocalDate generateNextEligibleMeetingDateForCollection(final CalendarData calendarData, final MeetingData lastMeetingData) {
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) {
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;
@@ -279,4 +372,83 @@ public class CalendarReadPlatformServiceImpl implements CalendarReadPlatformServ
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.Collection;
import java.util.Date;
import java.util.List;
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.DataValidatorBuilder;
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.domain.Calendar;
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.CalendarInstanceRepository;
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.serialization.CalendarCommandFromApiJsonDeserializer;
import org.mifosplatform.portfolio.client.domain.Client;
import org.mifosplatform.portfolio.client.domain.ClientRepositoryWrapper;
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.LoanRepository;
import org.mifosplatform.portfolio.loanaccount.service.LoanWritePlatformService;
@@ -43,21 +48,24 @@ import org.springframework.util.CollectionUtils;
public class CalendarWritePlatformServiceJpaRepositoryImpl implements CalendarWritePlatformService {
private final CalendarRepository calendarRepository;
private final CalendarHistoryRepository calendarHistoryRepository;
private final CalendarCommandFromApiJsonDeserializer fromApiJsonDeserializer;
private final CalendarInstanceRepository calendarInstanceRepository;
private final LoanWritePlatformService loanWritePlatformService;
private final ConfigurationDomainService configurationDomainService;
private final GroupRepository groupRepository;
private final GroupRepositoryWrapper groupRepository;
private final LoanRepository loanRepository;
private final ClientRepositoryWrapper clientRepository;
@Autowired
public CalendarWritePlatformServiceJpaRepositoryImpl(final CalendarRepository calendarRepository,
final CalendarHistoryRepository calendarHistoryRepository,
final CalendarCommandFromApiJsonDeserializer fromApiJsonDeserializer,
final CalendarInstanceRepository calendarInstanceRepository, final LoanWritePlatformService loanWritePlatformService,
final ConfigurationDomainService configurationDomainService, final GroupRepository groupRepository,
final ConfigurationDomainService configurationDomainService, final GroupRepositoryWrapper groupRepository,
final LoanRepository loanRepository, final ClientRepositoryWrapper clientRepository) {
this.calendarRepository = calendarRepository;
this.calendarHistoryRepository = calendarHistoryRepository;
this.fromApiJsonDeserializer = fromApiJsonDeserializer;
this.calendarInstanceRepository = calendarInstanceRepository;
this.loanWritePlatformService = loanWritePlatformService;
@@ -71,25 +79,58 @@ public class CalendarWritePlatformServiceJpaRepositoryImpl implements CalendarWr
public CommandProcessingResult createCalendar(final JsonCommand command) {
this.fromApiJsonDeserializer.validateForCreate(command.json());
final LocalDate entityActivationDate = getEntityActivationDate(command);
final Long entityId = command.getSupportedEntityId();
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 List<ApiParameterError> dataValidationErrors = new ArrayList<ApiParameterError>();
final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors)
.resource("calendar");
if(entityActivationDate == null || newCalendar.getStartDateLocalDate().isBefore(entityActivationDate)){
final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("calendar");
if (entityActivationDate == null || newCalendar.getStartDateLocalDate().isBefore(entityActivationDate)) {
final DateTimeFormatter formatter = DateTimeFormat.forPattern(command.dateFormat()).withLocale(command.extractLocale());
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";
baseDataValidator.reset().parameter(CALENDAR_SUPPORTED_PARAMETERS.START_DATE.getValue()).value(dateAsString)
.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);
@@ -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
public CommandProcessingResult updateCalendar(final JsonCommand command) {
@@ -130,25 +152,30 @@ public class CalendarWritePlatformServiceJpaRepositoryImpl implements CalendarWr
final Long calendarId = command.entityId();
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);
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);
if (this.configurationDomainService.isRescheduleFutureRepaymentsEnabled()) {
// In this approach loans following this meeting calendar will
// be affected immediately.
// get all calendar instances following this meeting calendar
// and calendar entity type is loan
if (this.configurationDomainService.isRescheduleFutureRepaymentsEnabled() && calendarForUpdate.isRepeating()) {
//fetch all loan calendar instances associated with modifying calendar.
final Collection<CalendarInstance> loanCalendarInstances = this.calendarInstanceRepository.findByCalendarIdAndEntityTypeId(
calendarId, CalendarEntityType.LOANS.getValue());
if (!CollectionUtils.isEmpty(loanCalendarInstances)) {
// update all loans which are following this meeting
// calendar
//update all loans associated with modifying calendar
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.CalendarRepositoryWrapper;
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.JLGCollectionSheetData;
import org.mifosplatform.portfolio.collectionsheet.data.JLGCollectionSheetFlatData;
@@ -304,9 +303,11 @@ public class CollectionSheetReadPlatformServiceImpl implements CollectionSheetRe
final String transactionDateStr = df.format(transactionDate.toDate());
final Calendar calendar = this.calendarRepositoryWrapper.findOneWithNotFoundDetection(calendarId);
if (!CalendarUtils.isValidRedurringDate(calendar.getRecurrence(), calendar.getStartDateLocalDate(), transactionDate)) { throw new NotValidRecurringDateException(
"collectionsheet", "The date '" + transactionDate + "' is not a valid meeting date.", transactionDate); }
// check if transaction against calendar effective from date
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 String hierarchy = currentUser.getOffice().getHierarchy();
final String officeHierarchy = hierarchy + "%";
@@ -360,5 +361,4 @@ public class CollectionSheetReadPlatformServiceImpl implements CollectionSheetRe
return buildJLGCollectionSheet(transactionDate, collectionSheetFlatDatas);
}
}
@@ -25,6 +25,7 @@ import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.UriInfo;
import org.apache.commons.lang.StringUtils;
import org.joda.time.LocalDate;
import org.mifosplatform.commands.domain.CommandWrapper;
import org.mifosplatform.commands.service.CommandWrapperBuilder;
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.service.CenterReadPlatformService;
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.context.annotation.Scope;
import org.springframework.stereotype.Component;
@@ -71,6 +74,7 @@ public class CentersApiResource {
private final FromJsonHelper fromJsonHelper;
private final AccountDetailsReadPlatformService accountDetailsReadPlatformService;
private final CalendarReadPlatformService calendarReadPlatformService;
private final MeetingReadPlatformService meetingReadPlatformService;
@Autowired
public CentersApiResource(final PlatformSecurityContext context, final CenterReadPlatformService centerReadPlatformService,
@@ -80,7 +84,7 @@ public class CentersApiResource {
final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService,
final CollectionSheetReadPlatformService collectionSheetReadPlatformService, final FromJsonHelper fromJsonHelper,
final AccountDetailsReadPlatformService accountDetailsReadPlatformService,
final CalendarReadPlatformService calendarReadPlatformService) {
final CalendarReadPlatformService calendarReadPlatformService, final MeetingReadPlatformService meetingReadPlatformService) {
this.context = context;
this.centerReadPlatformService = centerReadPlatformService;
this.centerApiJsonSerializer = centerApiJsonSerializer;
@@ -92,6 +96,7 @@ public class CentersApiResource {
this.fromJsonHelper = fromJsonHelper;
this.accountDetailsReadPlatformService = accountDetailsReadPlatformService;
this.calendarReadPlatformService = calendarReadPlatformService;
this.meetingReadPlatformService = meetingReadPlatformService;
}
@GET
@@ -164,7 +169,13 @@ public class CentersApiResource {
collectionMeetingCalendar = this.calendarReadPlatformService.retrieveCollctionCalendarByEntity(centerId,
CalendarEntityType.CENTERS.getValue());
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 org.apache.commons.lang.StringUtils;
import org.joda.time.LocalDate;
import org.mifosplatform.commands.domain.CommandWrapper;
import org.mifosplatform.commands.service.CommandWrapperBuilder;
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.GroupRolesReadPlatformService;
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.context.annotation.Scope;
import org.springframework.stereotype.Component;
@@ -81,6 +84,7 @@ public class GroupsApiResource {
private final GroupRolesReadPlatformService groupRolesReadPlatformService;
private final AccountDetailsReadPlatformService accountDetailsReadPlatformService;
private final CalendarReadPlatformService calendarReadPlatformService;
private final MeetingReadPlatformService meetingReadPlatformService;
@Autowired
public GroupsApiResource(final PlatformSecurityContext context, final GroupReadPlatformService groupReadPlatformService,
@@ -93,7 +97,7 @@ public class GroupsApiResource {
final CollectionSheetReadPlatformService collectionSheetReadPlatformService, final FromJsonHelper fromJsonHelper,
final GroupRolesReadPlatformService groupRolesReadPlatformService,
final AccountDetailsReadPlatformService accountDetailsReadPlatformService,
final CalendarReadPlatformService calendarReadPlatformService) {
final CalendarReadPlatformService calendarReadPlatformService, final MeetingReadPlatformService meetingReadPlatformService) {
this.context = context;
this.groupReadPlatformService = groupReadPlatformService;
@@ -109,6 +113,7 @@ public class GroupsApiResource {
this.groupRolesReadPlatformService = groupRolesReadPlatformService;
this.accountDetailsReadPlatformService = accountDetailsReadPlatformService;
this.calendarReadPlatformService = calendarReadPlatformService;
this.meetingReadPlatformService = meetingReadPlatformService;
}
@GET
@@ -216,7 +221,13 @@ public class GroupsApiResource {
CalendarEntityType.GROUPS.getValue());
}
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) {
// 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";
throw new LoanApplicationDateException("disbursement.date.do.not.match.meeting.date", errorMessage, expectedDisbursementDate);
}
@@ -229,7 +229,7 @@ public class LoanScheduleAssembler {
if (repaymentInterval % meetingInterval != 0) {
// throw exception: Loan product frequency/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) {
if (null != calendarInstance) {
final Calendar calendar = calendarInstance.getCalendar();
if (calendar != null && actualDisbursementDate != null) {
if (!calendar.isValidRecurringDate(actualDisbursementDate)) {
// Disbursement date should fall on a meeting date
if (!CalendarUtils.isValidRedurringDate(calendar.getRecurrence(), calendar.getStartDateLocalDate(), actualDisbursementDate)) {
final String errorMessage = "Expected disbursement date '" + actualDisbursementDate.toString()
+ "' does not fall on a meeting date.";
throw new NotValidRecurringDateException("loan.actual.disbursement.date", errorMessage,
actualDisbursementDate.toString(), calendar.getTitle());
}
}
}
}
@@ -1080,7 +1080,7 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService {
CalendarEntityType.GROUPS.getValue(), null));
calendarsData
.addAll(this.calendarReadPlatformService.retrieveCalendarsByEntity(groupId, CalendarEntityType.GROUPS.getValue(), null));
calendarsData = this.calendarReadPlatformService.generateRecurringDates(calendarsData);
calendarsData = this.calendarReadPlatformService.updateWithRecurringDates(calendarsData);
return calendarsData;
}
@@ -24,6 +24,7 @@ import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.UriInfo;
import org.apache.commons.lang.StringUtils;
import org.joda.time.LocalDate;
import org.mifosplatform.commands.domain.CommandWrapper;
import org.mifosplatform.commands.service.CommandWrapperBuilder;
import org.mifosplatform.commands.service.PortfolioCommandSourceWritePlatformService;
@@ -107,7 +108,11 @@ public class MeetingsApiResource {
if (calendarId != null) {
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,
@@ -66,4 +66,9 @@ public class MeetingData {
this.calendarData = calendarData;
this.attendanceTypeOptions = attendanceTypeOptions;
}
public LocalDate getMeetingDate() {
return this.meetingDate;
}
}
@@ -172,8 +172,9 @@ public class Meeting extends AbstractPersistable<Long> {
if (meetingDate != null) {
meetingDateLocalDate = LocalDate.fromDateFields(meetingDate);
}
if (meetingDateLocalDate == null
|| !CalendarUtils.isValidRedurringDate(calendar.getRecurrence(), calendar.getStartDateLocalDate(), meetingDateLocalDate)) { return false; }
|| !calendar.isValidRecurringDate(meetingDateLocalDate)) { return false; }
return true;
}
@@ -18,4 +18,6 @@ public interface MeetingReadPlatformService {
Collection<MeetingData> retrieveMeetingsByEntityByCalendarType(final Long entityId, final Integer entityTypeId,
final List<Integer> calendarTypeOptions);
MeetingData retrieveLastMeeting(Long calendarInstanceId);
}
@@ -36,7 +36,8 @@ public class MeetingReadPlatformServiceImpl implements MeetingReadPlatformServic
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
@@ -86,4 +87,17 @@ public class MeetingReadPlatformServiceImpl implements MeetingReadPlatformServic
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;
}
}
}