diff --git a/mifosng-db/patches/23-calendar_and_calendar_instance_table_scripts.sql b/mifosng-db/patches/23-calendar_and_calendar_instance_table_scripts.sql new file mode 100644 index 000000000..791c84bdf --- /dev/null +++ b/mifosng-db/patches/23-calendar_and_calendar_instance_table_scripts.sql @@ -0,0 +1,32 @@ +DROP TABLE IF EXISTS `m_calendar`; +CREATE TABLE IF NOT EXISTS `m_calendar` ( + `id` bigint(20) NOT NULL AUTO_INCREMENT, + `title` varchar(50) NOT NULL, + `description` varchar(100) DEFAULT NULL, + `location` varchar(50) DEFAULT NULL, + `start_date` date NOT NULL, + `end_date` date DEFAULT NULL, + `duration` smallint(6) DEFAULT NULL, + `calendar_type_enum` smallint(5) NOT NULL, + `repeating` tinyint(1) NOT NULL DEFAULT '0', + `recurrence` varchar(100) DEFAULT NULL, + `remind_by_enum` smallint(5) DEFAULT NULL, + `first_reminder` smallint(11) DEFAULT NULL, + `second_reminder` smallint(11) DEFAULT NULL, + `createdby_id` bigint(20) DEFAULT NULL, + `lastmodifiedby_id` bigint(20) DEFAULT NULL, + `created_date` datetime DEFAULT NULL, + `lastmodified_date` datetime DEFAULT NULL, + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=latin1; + +DROP TABLE IF EXISTS `m_calendar_instance`; +CREATE TABLE IF NOT EXISTS `m_calendar_instance` ( + `id` bigint(20) NOT NULL AUTO_INCREMENT, + `calendar_id` bigint(20) NOT NULL, + `entity_id` bigint(20) NOT NULL, + `entity_type_enum` smallint(5) NOT NULL, + PRIMARY KEY (`id`), + KEY `FK_m_calendar_m_calendar_instance` (`calendar_id`), + CONSTRAINT `FK_m_calendar_m_calendar_instance` FOREIGN KEY (`calendar_id`) REFERENCES `m_calendar` (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=latin1; \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/core/data/DataValidatorBuilder.java b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/core/data/DataValidatorBuilder.java index 83e291e8a..420c41c09 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/core/data/DataValidatorBuilder.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/core/data/DataValidatorBuilder.java @@ -365,21 +365,23 @@ public class DataValidatorBuilder { return this; } - public DataValidatorBuilder isValidateRecurringRule(final String recurringRule){ - if(StringUtils.isNotBlank(recurringRule)){ - try { - RRule rRule = new RRule(recurringRule); - rRule.validate(); - } catch (ValidationException e) { - ApiParameterError error = ApiParameterError.parameterError("validation.msg.invalid.recurring.rule", "The Recurring Rule value: " + recurringRule + " is not valid.", parameter, recurringRule ); - dataValidationErrors.add(error); + public DataValidatorBuilder isValidateRecurringRule(final String recurringRule) { + if (StringUtils.isNotBlank(recurringRule)) { + try { + RRule rRule = new RRule(recurringRule); + rRule.validate(); + } catch (ValidationException e) { + ApiParameterError error = ApiParameterError.parameterError("validation.msg.invalid.recurring.rule", + "The Recurring Rule value: " + recurringRule + " is not valid.", parameter, recurringRule); + dataValidationErrors.add(error); return this; - } catch (ParseException e) { - ApiParameterError error = ApiParameterError.parameterError("validation.msg.invalid.recurring.rule", "The Recurring Rule value: " + recurringRule + " is not valid.", parameter, recurringRule ); - dataValidationErrors.add(error); + } catch (ParseException e) { + ApiParameterError error = ApiParameterError.parameterError("validation.msg.recurring.rule.parsing.error", + "Error in pasring the Recurring Rule value: " + recurringRule + ".", parameter, recurringRule); + dataValidationErrors.add(error); return this; - } - } - return this; + } + } + return this; } } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/calendar/api/CalendarsApiResource.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/calendar/api/CalendarsApiResource.java index 50df7cbe4..e95e180be 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/calendar/api/CalendarsApiResource.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/calendar/api/CalendarsApiResource.java @@ -5,6 +5,7 @@ */ package org.mifosplatform.portfolio.calendar.api; +import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.HashSet; @@ -23,6 +24,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; @@ -37,6 +39,7 @@ import org.mifosplatform.portfolio.calendar.domain.Calendar; import org.mifosplatform.portfolio.calendar.domain.CalendarEntityType; import org.mifosplatform.portfolio.calendar.service.CalendarDropdownReadPlatformService; import org.mifosplatform.portfolio.calendar.service.CalendarReadPlatformService; +import org.mifosplatform.portfolio.calendar.service.CalendarWrapperService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Scope; import org.springframework.stereotype.Component; @@ -50,8 +53,9 @@ public class CalendarsApiResource { * The set of parameters that are supported in response for {@link Calendar} */ private final Set RESPONSE_DATA_PARAMETERS = new HashSet(Arrays.asList("id", "entityId", "entityType", "title", - "description", "location", "startDate", "endDate", "createdDate", "duration", "type", "repeating", "recurrence", "remindBy", - "firstReminder", "secondReminder")); + "description", "location", "startDate", "endDate", "duration", "type", "repeating", "recurrence", "remindBy", + "firstReminder", "secondReminder", "humanReadable", "createdDate", "lastUpdatedDate", "createdByUserId", "createdByUsername", "lastUpdatedByUserId", + "lastUpdatedByUsername", "recurringDates")); private final String resourceNameForPermissions = "CALENDAR"; private final PlatformSecurityContext context; @@ -60,18 +64,20 @@ public class CalendarsApiResource { private final ApiRequestParameterHelper apiRequestParameterHelper; private final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService; private final CalendarDropdownReadPlatformService dropdownReadPlatformService; + private final CalendarWrapperService calendarWrapperService; @Autowired public CalendarsApiResource(final PlatformSecurityContext context, final CalendarReadPlatformService readPlatformService, final DefaultToApiJsonSerializer toApiJsonSerializer, final ApiRequestParameterHelper apiRequestParameterHelper, final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService, - final CalendarDropdownReadPlatformService dropdownReadPlatformService) { + final CalendarDropdownReadPlatformService dropdownReadPlatformService, final CalendarWrapperService calendarWrapperService) { this.context = context; this.readPlatformService = readPlatformService; this.toApiJsonSerializer = toApiJsonSerializer; this.apiRequestParameterHelper = apiRequestParameterHelper; this.commandsSourceWritePlatformService = commandsSourceWritePlatformService; this.dropdownReadPlatformService = dropdownReadPlatformService; + this.calendarWrapperService = calendarWrapperService; } @GET @@ -82,13 +88,16 @@ public class CalendarsApiResource { this.context.authenticatedUser().validateHasReadPermission(this.resourceNameForPermissions); final Integer entityTypeId = CalendarEntityType.valueOf(entityType.toUpperCase()).getValue(); - CalendarData calendar = this.readPlatformService.retrieveCalendar(calendarId, entityId, entityTypeId); + CalendarData calendarData = this.readPlatformService.retrieveCalendar(calendarId, entityId, entityTypeId); + //Include recurring date details + calendarData = handleRecurringDate(calendarData); + final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters()); if(settings.isTemplate()){ - calendar = handleTemplate(calendar); + calendarData = handleTemplate(calendarData); } - return this.toApiJsonSerializer.serialize(settings, calendar, this.RESPONSE_DATA_PARAMETERS); + return this.toApiJsonSerializer.serialize(settings, calendarData, this.RESPONSE_DATA_PARAMETERS); } @GET @@ -99,11 +108,13 @@ public class CalendarsApiResource { this.context.authenticatedUser().validateHasReadPermission(this.resourceNameForPermissions); - final Collection calendars = this.readPlatformService.retrieveCalendarsByEntity(entityId, + Collection calendarsData = this.readPlatformService.retrieveCalendarsByEntity(entityId, CalendarEntityType.valueOf(entityType.toUpperCase()).getValue()); - + //Add recurring dates + calendarsData = handleRecurringDates(calendarsData); + final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters()); - return this.toApiJsonSerializer.serialize(settings, calendars, this.RESPONSE_DATA_PARAMETERS); + return this.toApiJsonSerializer.serialize(settings, calendarsData, this.RESPONSE_DATA_PARAMETERS); } @GET @@ -172,4 +183,19 @@ public class CalendarsApiResource { return new CalendarData(calendarData, entityTypeOptions, calendarTypeOptions, remindByOptions); } + private CalendarData handleRecurringDate(final CalendarData calendarData){ + if(!calendarData.isRepeating()) return calendarData; + final Collection recurringDates = this.calendarWrapperService.getNextRecurrencesList(calendarData); + return new CalendarData(calendarData, recurringDates); + } + + private Collection handleRecurringDates(final Collection calendarsData){ + Collection recuCalendarsData = new ArrayList(); + + for (CalendarData calendarData : calendarsData) { + recuCalendarsData.add(handleRecurringDate(calendarData)); + } + + return recuCalendarsData; + } } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/calendar/data/CalendarData.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/calendar/data/CalendarData.java index 61a70bef2..a76659129 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/calendar/data/CalendarData.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/calendar/data/CalendarData.java @@ -5,6 +5,7 @@ */ package org.mifosplatform.portfolio.calendar.data; +import java.util.Collection; import java.util.List; import org.joda.time.LocalDate; @@ -26,7 +27,6 @@ public class CalendarData { private final String location; private final LocalDate startDate; private final LocalDate endDate; - private final LocalDate createdDate; private final Integer duration; private final EnumOptionData type; private final boolean repeating; @@ -34,6 +34,15 @@ public class CalendarData { private final EnumOptionData remindBy; private final Integer firstReminder; private final Integer secondReminder; + private final Collection recurringDates; + private final String humanReadable; + + private final LocalDate createdDate; + private final LocalDate lastUpdatedDate; + private final Long createdByUserId; + private final String createdByUsername; + private final Long lastUpdatedByUserId; + private final String lastUpdatedByUsername; // template related final List entityTypeOptions; @@ -41,9 +50,10 @@ public class CalendarData { final List remindByOptions; public CalendarData(final Long id, final Long entityId, final EnumOptionData entityType, final String title, final String description, - final String location, final LocalDate startDate, final LocalDate endDate, final LocalDate createdDate, final Integer duration, - final EnumOptionData type, final boolean repeating, final String recurrence, final EnumOptionData remindBy, - final Integer firstReminder, final Integer secondReminder) { + final String location, final LocalDate startDate, final LocalDate endDate, final Integer duration, final EnumOptionData type, + final boolean repeating, final String recurrence, final EnumOptionData remindBy, final Integer firstReminder, + final Integer secondReminder, final String humanReadable, final LocalDate createdDate, final LocalDate lastUpdatedDate, + final Long createdByUserId, final String createdByUsername, final Long lastUpdatedByUserId, final String lastUpdatedByUsername) { this.id = id; this.entityId = entityId; @@ -53,7 +63,6 @@ public class CalendarData { this.location = location; this.startDate = startDate; this.endDate = endDate; - this.createdDate = createdDate; this.duration = duration; this.type = type; this.repeating = repeating; @@ -64,6 +73,15 @@ public class CalendarData { this.entityTypeOptions = null; this.calendarTypeOptions = null; this.remindByOptions = null; + this.recurringDates = null; + this.humanReadable = humanReadable; + + this.createdDate = createdDate; + this.lastUpdatedDate = lastUpdatedDate; + this.createdByUserId = createdByUserId; + this.createdByUsername = createdByUsername; + this.lastUpdatedByUserId = lastUpdatedByUserId; + this.lastUpdatedByUsername = lastUpdatedByUsername; } public CalendarData(final CalendarData calendarData, final List entityTypeOptions, @@ -76,7 +94,6 @@ public class CalendarData { this.location = calendarData.location; this.startDate = calendarData.startDate; this.endDate = calendarData.endDate; - this.createdDate = calendarData.createdDate; this.duration = calendarData.duration; this.type = calendarData.type; this.repeating = calendarData.repeating; @@ -87,6 +104,44 @@ public class CalendarData { this.entityTypeOptions = entityTypeOptions; this.calendarTypeOptions = calendarTypeOptions; this.remindByOptions = remindByOptions; + this.recurringDates = calendarData.recurringDates; + this.humanReadable = calendarData.humanReadable; + this.createdDate = calendarData.createdDate; + this.lastUpdatedDate = calendarData.lastUpdatedDate; + this.createdByUserId = calendarData.createdByUserId; + this.createdByUsername = calendarData.createdByUsername; + this.lastUpdatedByUserId = calendarData.lastUpdatedByUserId; + this.lastUpdatedByUsername = calendarData.lastUpdatedByUsername; + } + + public CalendarData(final CalendarData calendarData, final Collection recurringDates) { + this.id = calendarData.id; + this.entityId = calendarData.entityId; + this.entityType = calendarData.entityType; + this.title = calendarData.title; + this.description = calendarData.description; + this.location = calendarData.location; + this.startDate = calendarData.startDate; + this.endDate = calendarData.endDate; + this.duration = calendarData.duration; + this.type = calendarData.type; + this.repeating = calendarData.repeating; + this.recurrence = calendarData.recurrence; + this.remindBy = calendarData.remindBy; + this.firstReminder = calendarData.firstReminder; + this.secondReminder = calendarData.secondReminder; + this.entityTypeOptions = null; + this.calendarTypeOptions = null; + this.remindByOptions = null; + this.recurringDates = recurringDates; + this.humanReadable = calendarData.humanReadable; + this.createdDate = calendarData.createdDate; + this.lastUpdatedDate = calendarData.lastUpdatedDate; + this.createdByUserId = calendarData.createdByUserId; + this.createdByUsername = calendarData.createdByUsername; + this.lastUpdatedByUserId = calendarData.lastUpdatedByUserId; + this.lastUpdatedByUsername = calendarData.lastUpdatedByUsername; + } public static CalendarData sensibleDefaultsForNewCalendarCreation() { @@ -98,7 +153,6 @@ public class CalendarData { final String location = null; final LocalDate startDate = null; final LocalDate endDate = null; - final LocalDate createdDate = null; final Integer duration = new Integer(0); final EnumOptionData type = CalendarEnumerations.calendarType(CalendarType.COLLECTION); final boolean repeating = false; @@ -106,8 +160,90 @@ public class CalendarData { final EnumOptionData remindBy = CalendarEnumerations.calendarRemindBy(CalendarRemindBy.EMAIL); final Integer firstReminder = new Integer(0); final Integer secondReminder = new Integer(0); + final String humanReadable = null; - return new CalendarData(id, entityId, entityType, title, description, location, startDate, endDate, createdDate, duration, type, - repeating, recurrence, remindBy, firstReminder, secondReminder); + return new CalendarData(id, entityId, entityType, title, description, location, startDate, endDate, duration, type, repeating, + recurrence, remindBy, firstReminder, secondReminder, humanReadable, null, null, null, null, null, null); } + + public Long getId() { + return this.id; + } + + public Long getEntityId() { + return this.entityId; + } + + public EnumOptionData getEntityType() { + return this.entityType; + } + + public String getTitle() { + return this.title; + } + + public String getDescription() { + return this.description; + } + + public String getLocation() { + return this.location; + } + + public LocalDate getStartDate() { + return this.startDate; + } + + public LocalDate getEndDate() { + return this.endDate; + } + + public LocalDate getCreatedDate() { + return this.createdDate; + } + + public Integer getDuration() { + return this.duration; + } + + public EnumOptionData getType() { + return this.type; + } + + public boolean isRepeating() { + return this.repeating; + } + + public String getRecurrence() { + return this.recurrence; + } + + public EnumOptionData getRemindBy() { + return this.remindBy; + } + + public Integer getFirstReminder() { + return this.firstReminder; + } + + public Integer getSecondReminder() { + return this.secondReminder; + } + + public List getEntityTypeOptions() { + return this.entityTypeOptions; + } + + public List getCalendarTypeOptions() { + return this.calendarTypeOptions; + } + + public List getRemindByOptions() { + return this.remindByOptions; + } + + public String getHumanReadable() { + return this.humanReadable; + } + } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/calendar/domain/Calendar.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/calendar/domain/Calendar.java index 607392a5f..130a487b0 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/calendar/domain/Calendar.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/calendar/domain/Calendar.java @@ -19,18 +19,13 @@ 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.domain.AbstractAuditableCustom; import org.mifosplatform.portfolio.calendar.CalendarConstants.CALENDAR_SUPPORTED_PARAMETERS; -import org.springframework.data.jpa.domain.AbstractPersistable; +import org.mifosplatform.useradministration.domain.AppUser; @Entity @Table(name = "m_calendar") -public class Calendar extends AbstractPersistable { - - @Column(name = "entity_id", nullable = false) - private Long entityId; - - @Column(name = "entity_type_enum", nullable = false) - private Integer entityTypeId; +public class Calendar extends AbstractAuditableCustom { @Column(name = "title", length = 50, nullable = false) private String title; @@ -49,10 +44,6 @@ public class Calendar extends AbstractPersistable { @Temporal(TemporalType.DATE) private Date endDate; - @Column(name = "created_date", nullable = false) - @Temporal(TemporalType.TIMESTAMP) - private Date createdDate; - @Column(name = "duration", nullable = true) private Integer duration; @@ -78,12 +69,9 @@ public class Calendar extends AbstractPersistable { } - public Calendar(final Long entityId, final Integer entityTypeId, final String title, final String description, final String location, - final LocalDate startDate, final LocalDate endDate, final LocalDate createdDate, final Integer duration, final Integer typeId, - final boolean repeating, final String recurrence, final Integer remindById, final Integer firstReminder, - final Integer secondReminder) { - this.entityId = entityId; - this.entityTypeId = entityTypeId; + 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) { this.title = StringUtils.defaultIfEmpty(title, null); this.description = StringUtils.defaultIfEmpty(description, null); this.location = StringUtils.defaultIfEmpty(location, null); @@ -100,12 +88,6 @@ public class Calendar extends AbstractPersistable { this.endDate = null; } - if (null != createdDate) { - this.createdDate = createdDate.toDateMidnight().toDate(); - } else { - this.createdDate = null; - } - this.duration = duration; this.typeId = typeId; this.repeating = repeating; @@ -117,14 +99,14 @@ public class Calendar extends AbstractPersistable { public static Calendar fromJson(final JsonCommand command) { - final Long entityId = command.getSupportedEntityId(); - final Integer entityTypeId = CalendarEntityType.valueOf(command.getSupportedEntityType().toUpperCase()).getValue(); + // final Long entityId = command.getSupportedEntityId(); + // final Integer entityTypeId = + // CalendarEntityType.valueOf(command.getSupportedEntityType().toUpperCase()).getValue(); final String title = command.stringValueOfParameterNamed(CALENDAR_SUPPORTED_PARAMETERS.TITLE.getValue()); final String description = command.stringValueOfParameterNamed(CALENDAR_SUPPORTED_PARAMETERS.DESCRIPTION.getValue()); final String location = command.stringValueOfParameterNamed(CALENDAR_SUPPORTED_PARAMETERS.LOCATION.getValue()); final LocalDate startDate = command.localDateValueOfParameterNamed(CALENDAR_SUPPORTED_PARAMETERS.START_DATE.getValue()); final LocalDate endDate = command.localDateValueOfParameterNamed(CALENDAR_SUPPORTED_PARAMETERS.END_DATE.getValue()); - final LocalDate createdDate = command.localDateValueOfParameterNamed(CALENDAR_SUPPORTED_PARAMETERS.CREATED_DATE.getValue()); final Integer duration = command.integerValueSansLocaleOfParameterNamed(CALENDAR_SUPPORTED_PARAMETERS.DURATION.getValue()); final Integer typeId = command.integerValueSansLocaleOfParameterNamed(CALENDAR_SUPPORTED_PARAMETERS.TYPE_ID.getValue()); final boolean repeating = command.booleanPrimitiveValueOfParameterNamed(CALENDAR_SUPPORTED_PARAMETERS.REPEATING.getValue()); @@ -135,8 +117,8 @@ public class Calendar extends AbstractPersistable { final Integer secondReminder = command.integerValueSansLocaleOfParameterNamed(CALENDAR_SUPPORTED_PARAMETERS.SECOND_REMINDER .getValue()); - return new Calendar(entityId, entityTypeId, title, description, location, startDate, endDate, createdDate, duration, typeId, - repeating, recurrence, remindById, firstReminder, secondReminder); + return new Calendar(title, description, location, startDate, endDate, duration, typeId, repeating, recurrence, + remindById, firstReminder, secondReminder); } public Map update(final JsonCommand command) { @@ -237,14 +219,6 @@ public class Calendar extends AbstractPersistable { return actualChanges; } - public Long getEntityId() { - return this.entityId; - } - - public Integer getEntityTypeId() { - return this.entityTypeId; - } - public String getTitle() { return this.title; } @@ -265,10 +239,6 @@ public class Calendar extends AbstractPersistable { return this.endDate; } - public Date getCreatedDate() { - return this.createdDate; - } - public Integer getDuration() { return this.duration; } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/calendar/domain/CalendarInstance.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/calendar/domain/CalendarInstance.java new file mode 100644 index 000000000..ced0c1713 --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/calendar/domain/CalendarInstance.java @@ -0,0 +1,52 @@ +package org.mifosplatform.portfolio.calendar.domain; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; +import javax.persistence.Table; + +import org.mifosplatform.infrastructure.core.api.JsonCommand; +import org.springframework.data.jpa.domain.AbstractPersistable; + +@Entity +@Table(name = "m_calendar_instance") +public class CalendarInstance extends AbstractPersistable { + + @ManyToOne + @JoinColumn(name = "calendar_id", nullable = false) + private Calendar calendar; + + @Column(name = "entity_id", nullable = false) + private Long entityId; + + @Column(name = "entity_type_enum", nullable = false) + private Integer entityTypeId; + + public CalendarInstance(Calendar calendar, Long entityId, Integer entityTypeId) { + this.calendar = calendar; + this.entityId = entityId; + this.entityTypeId = entityTypeId; + } + + public static CalendarInstance fromJson(final Calendar calendar, final JsonCommand command) { + final Long entityId = command.getSupportedEntityId(); + final Integer entityTypeId = CalendarEntityType.valueOf(command.getSupportedEntityType().toUpperCase()).getValue(); + return new CalendarInstance(calendar, entityId, entityTypeId); + } + + public Calendar getCalendar() { + return this.calendar; + } + + + public Long getEntityId() { + return this.entityId; + } + + + public Integer getEntityTypeId() { + return this.entityTypeId; + } + +} diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/calendar/domain/CalendarInstanceRepository.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/calendar/domain/CalendarInstanceRepository.java new file mode 100644 index 000000000..2a6f5d70a --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/calendar/domain/CalendarInstanceRepository.java @@ -0,0 +1,10 @@ +package org.mifosplatform.portfolio.calendar.domain; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.JpaSpecificationExecutor; + +public interface CalendarInstanceRepository extends JpaRepository, JpaSpecificationExecutor { + + CalendarInstance findByCalendarAndEntityIdAndEntityTypeId(Long calendarId, Long entityId, Integer entityTypeId); + +} diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/calendar/service/CalendarReadPlatformServiceImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/calendar/service/CalendarReadPlatformServiceImpl.java index 09dad3069..10ad87c2f 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/calendar/service/CalendarReadPlatformServiceImpl.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/calendar/service/CalendarReadPlatformServiceImpl.java @@ -34,11 +34,14 @@ public class CalendarReadPlatformServiceImpl implements CalendarReadPlatformServ private static final class CalendarDataMapper implements RowMapper { public String schema() { - return " select c.id as id, c.entity_id as entityId, c.entity_type_enum as entityTypeId, c.title as title, " + return " select c.id as id, 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.created_date as createdDate, 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 c "; + + " 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.created_date as createdDate, c.lastmodified_date as updatedDate, creatingUser.id as creatingUserId, creatingUser.username as creatingUserName, " + + " updatingUser.id as updatingUserId, updatingUser.username as updatingUserName " + + " from m_calendar c join m_calendar_instance ci on ci.calendar_id=c.id, m_appuser as creatingUser, m_appuser as updatingUser" + + " where c.createdby_id=creatingUser.id and c.lastmodifiedby_id=updatingUser.id "; } @Override @@ -53,7 +56,6 @@ public class CalendarReadPlatformServiceImpl implements CalendarReadPlatformServ final String location = rs.getString("location"); final LocalDate startDate = JdbcSupport.getLocalDate(rs, "startDate"); final LocalDate endDate = JdbcSupport.getLocalDate(rs, "endDate"); - final LocalDate createdDate = JdbcSupport.getLocalDate(rs, "createdDate"); final Integer duration = rs.getInt("duration"); final Integer typeId = rs.getInt("typeId"); final EnumOptionData type = CalendarEnumerations.calendarType(typeId); @@ -64,9 +66,16 @@ public class CalendarReadPlatformServiceImpl implements CalendarReadPlatformServ if(remindById != null) remindBy = CalendarEnumerations.calendarRemindBy(remindById); final Integer firstReminder = rs.getInt("firstReminder"); final Integer secondReminder = rs.getInt("secondReminder"); + final String humanReadable = CalendarWrapperService.getRRuleReadable(startDate, recurrence); + + final LocalDate createdDate = JdbcSupport.getLocalDate(rs, "createdDate"); + final LocalDate lastUpdatedDate = JdbcSupport.getLocalDate(rs, "updatedDate"); + final Long createdByUserId = rs.getLong("creatingUserId"); + final String createdByUserName = rs.getString("creatingUserName"); + final Long lastUpdatedByUserId = rs.getLong("updatingUserId"); + final String lastUpdatedByUserName = rs.getString("updatingUserName"); - return new CalendarData(id, entityId, entityType, title, description, location, startDate, endDate, createdDate, duration, - type, repeating, recurrence, remindBy, firstReminder, secondReminder); + return new CalendarData(id, entityId, entityType, title, description, location, startDate, endDate, duration, type, repeating, recurrence, remindBy, firstReminder, secondReminder, humanReadable, createdDate, lastUpdatedDate, createdByUserId, createdByUserName, lastUpdatedByUserId, lastUpdatedByUserName); } } @@ -76,7 +85,7 @@ public class CalendarReadPlatformServiceImpl implements CalendarReadPlatformServ try { final CalendarDataMapper rm = new CalendarDataMapper(); - final String sql = rm.schema() + "where c.id = ? and c.entity_id = ? and c.entity_type_enum = ?"; + final String sql = rm.schema() + " and c.id = ? and ci.entity_id = ? and ci.entity_type_enum = ? "; return this.jdbcTemplate.queryForObject(sql, rm, new Object[] { calendarId, entityId, entityTypeId }); } catch (final EmptyResultDataAccessException e) { @@ -88,7 +97,7 @@ public class CalendarReadPlatformServiceImpl implements CalendarReadPlatformServ public Collection retrieveCalendarsByEntity(final Long entityId, final Integer entityTypeId) { final CalendarDataMapper rm = new CalendarDataMapper(); - final String sql = rm.schema() + "where c.entity_id = ? and c.entity_type_enum = ?"; + final String sql = rm.schema() + " and ci.entity_id = ? and ci.entity_type_enum = ? order by c.start_date "; return this.jdbcTemplate.query(sql, rm, new Object[] { entityId, entityTypeId }); } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/calendar/service/CalendarWrapperService.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/calendar/service/CalendarWrapperService.java new file mode 100644 index 000000000..2fe574877 --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/calendar/service/CalendarWrapperService.java @@ -0,0 +1,186 @@ +package org.mifosplatform.portfolio.calendar.service; + +import java.text.ParseException; +import java.util.ArrayList; +import java.util.Calendar; +import java.util.Collection; +import java.util.Iterator; + +import net.fortuna.ical4j.model.Date; +import net.fortuna.ical4j.model.DateList; +import net.fortuna.ical4j.model.DateTime; +import net.fortuna.ical4j.model.Recur; +import net.fortuna.ical4j.model.ValidationException; +import net.fortuna.ical4j.model.WeekDay; +import net.fortuna.ical4j.model.WeekDayList; +import net.fortuna.ical4j.model.parameter.Value; +import net.fortuna.ical4j.model.property.RRule; + +import org.joda.time.LocalDate; +import org.joda.time.format.DateTimeFormat; +import org.joda.time.format.DateTimeFormatter; +import org.mifosplatform.infrastructure.core.exception.PlatformDataIntegrityException; +import org.mifosplatform.portfolio.calendar.data.CalendarData; +import org.springframework.stereotype.Service; + +@Service +public class CalendarWrapperService { + + public CalendarWrapperService() { + + } + + public LocalDate getNextRecurrence(final CalendarData calendar) { + + final Recur recur = getRecur(calendar); + + if (recur == null) { return null; } + + final Date nextRecDate = recur.getNextDate(new Date(calendar.getStartDate().toDate()), new Date(calendar.getStartDate().toDate())); + final LocalDate lNextRecDate = new LocalDate(nextRecDate); + return lNextRecDate; + } + + public Collection getNextRecurrencesList(final CalendarData calendarData) { + int maxCount = 10;//Default number of recurring dates + return getNextRecurrencesList(calendarData, maxCount); + } + + public Collection getNextRecurrencesList(final CalendarData calendarData, int maxCount) { + final Recur recur = getRecur(calendarData); + + if (recur == null) { return null; } + + final Date seed = new Date(calendarData.getStartDate().toDate()); + final DateTime periodStart = new DateTime(calendarData.getStartDate().toDate()); + + Calendar endDate = Calendar.getInstance(); + endDate.setTime(calendarData.getStartDate().toDate()); + + if(calendarData.getEndDate() != null){ + endDate.setTime(calendarData.getEndDate().toDate()); + }else{ + endDate.add(Calendar.YEAR, 5); + } + + final DateTime periodEnd = new DateTime(endDate.getTime()); + + final Value value = new Value(Value.DATE.getValue()); + + return getRecurringDates(recur.getDates(seed, periodStart, periodEnd, value, maxCount)); + } + + private Collection getRecurringDates(final DateList dates) { + + Collection recurringDates = new ArrayList(); + + for (@SuppressWarnings("rawtypes") + final Iterator iterator = dates.iterator(); iterator.hasNext();) { + final Date date = (Date) iterator.next(); + recurringDates.add(new LocalDate(date)); + } + + return recurringDates; + } + + private Recur getRecur(final CalendarData calendar) { + if (!calendar.isRepeating()) { + // throw exception or return null + // throw new CalendarNotReccuring(); + return null; + } + + // Construct RRule + try { + final RRule rrule = new RRule(calendar.getRecurrence()); + rrule.validate(); + + final Recur recur = rrule.getRecur(); + + return recur; + } catch (final ParseException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } catch (final ValidationException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + + return null; + } + + public static String getRRuleReadable(final LocalDate startDate, final String recurringRule){ + + String humanReadable = ""; + + RRule rrule; + Recur recur = null; + try { + rrule = new RRule(recurringRule); + rrule.validate(); + recur = rrule.getRecur(); + } catch (ValidationException e) { + throw new PlatformDataIntegrityException("error.msg.invalid.recurring.rule", + "The Recurring Rule value: " + recurringRule + " is not valid.", "recurrence", recurringRule); + } catch (ParseException e) { + throw new PlatformDataIntegrityException("error.msg.recurring.rule.parsing.error", + "Error in pasring the Recurring Rule value: " + recurringRule , "recurrence", recurringRule); + } + + if(recur == null) return humanReadable; + + + if(recur.getFrequency().equals(Recur.DAILY)){ + if(recur.getInterval() == 1){ + humanReadable = "Daily"; + }else{ + humanReadable = "Every " + recur.getInterval() + " days"; + } + }else if(recur.getFrequency().equals(Recur.WEEKLY)){ + if(recur.getInterval() == 1 || recur.getInterval() == -1){ + humanReadable = "Weekly"; + }else{ + humanReadable = "Every " + recur.getInterval() + " weeks"; + } + + humanReadable += " on "; + WeekDayList weekDayList = recur.getDayList(); + + for (@SuppressWarnings("rawtypes") + Iterator iterator = weekDayList.iterator(); iterator.hasNext();) { + WeekDay weekDay = (WeekDay) iterator.next(); + humanReadable += weekDay.getDay(); + } + + }else if(recur.getFrequency().equals(Recur.MONTHLY)){ + if(recur.getInterval() == 1){ + humanReadable = "Monthly on day " + startDate.getDayOfMonth(); + }else{ + humanReadable = "Every " + recur.getInterval() + " months on day " + startDate.getDayOfMonth(); + } + }else if(recur.getFrequency().equals(Recur.YEARLY)){ + if(recur.getInterval() == 1){ + humanReadable = "Annually on " + startDate.toString("MMM") + " " + startDate.getDayOfMonth(); + }else{ + humanReadable = "Every " + recur.getInterval() + " years on " + startDate.toString("MMM") + " " + startDate.getDayOfMonth(); + } + } + + if(recur.getCount() > 0){ + if(recur.getCount() == 1){ + humanReadable = "Once"; + } + humanReadable += ", " + recur.getCount() + " times"; + } + + Date endDate = recur.getUntil(); + LocalDate date = new LocalDate(endDate); + DateTimeFormatter fmt = DateTimeFormat.forPattern("dd MMMM YY"); + String formattedDate = date.toString( fmt ); + if(endDate != null){ + humanReadable += ", until " + formattedDate; + } + + return humanReadable; + } +} diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/calendar/service/CalendarWritePlatformService.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/calendar/service/CalendarWritePlatformService.java index d35aa5419..8ec202f00 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/calendar/service/CalendarWritePlatformService.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/calendar/service/CalendarWritePlatformService.java @@ -10,4 +10,8 @@ public interface CalendarWritePlatformService { CommandProcessingResult updateCalendar(JsonCommand command); CommandProcessingResult deleteCalendar(Long calendarId); + + CommandProcessingResult createCalendarInstance(Long calendarId, Long entityId, Integer entityTypeId); + + CommandProcessingResult updateCalendarInstance(Long calendarId, Long entityId, Integer entityTypeId); } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/calendar/service/CalendarWritePlatformServiceJpaRepositoryImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/calendar/service/CalendarWritePlatformServiceJpaRepositoryImpl.java index 09c4c5bb0..3752ea4a0 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/calendar/service/CalendarWritePlatformServiceJpaRepositoryImpl.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/calendar/service/CalendarWritePlatformServiceJpaRepositoryImpl.java @@ -11,6 +11,8 @@ import org.mifosplatform.infrastructure.core.api.JsonCommand; import org.mifosplatform.infrastructure.core.data.CommandProcessingResult; import org.mifosplatform.infrastructure.core.data.CommandProcessingResultBuilder; import org.mifosplatform.portfolio.calendar.domain.Calendar; +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.exception.CalendarNotFoundException; import org.mifosplatform.portfolio.calendar.serialization.CalendarCommandFromApiJsonDeserializer; @@ -22,12 +24,15 @@ public class CalendarWritePlatformServiceJpaRepositoryImpl implements CalendarWr private final CalendarRepository calendarRepository; private final CalendarCommandFromApiJsonDeserializer fromApiJsonDeserializer; + private final CalendarInstanceRepository calendarInstanceRepository; @Autowired public CalendarWritePlatformServiceJpaRepositoryImpl(final CalendarRepository calendarRepository, - final CalendarCommandFromApiJsonDeserializer fromApiJsonDeserializer) { + final CalendarCommandFromApiJsonDeserializer fromApiJsonDeserializer, + final CalendarInstanceRepository calendarInstanceRepository) { this.calendarRepository = calendarRepository; this.fromApiJsonDeserializer = fromApiJsonDeserializer; + this.calendarInstanceRepository = calendarInstanceRepository; } @Override @@ -37,6 +42,10 @@ public class CalendarWritePlatformServiceJpaRepositoryImpl implements CalendarWr final Calendar newCalendar = Calendar.fromJson(command); this.calendarRepository.save(newCalendar); + + final CalendarInstance newCalendarInstance = CalendarInstance.fromJson(newCalendar, command); + this.calendarInstanceRepository.save(newCalendarInstance); + return new CommandProcessingResultBuilder() // .withCommandId(command.commandId()) // .withEntityId(newCalendar.getId()) // @@ -78,4 +87,33 @@ public class CalendarWritePlatformServiceJpaRepositoryImpl implements CalendarWr .build(); } + @Override + public CommandProcessingResult createCalendarInstance(final Long calendarId, final Long entityId, final Integer entityTypeId) { + + final Calendar calendarForUpdate = this.calendarRepository.findOne(calendarId); + if (calendarForUpdate == null) { throw new CalendarNotFoundException(calendarId); } + + final CalendarInstance newCalendarInstance = new CalendarInstance(calendarForUpdate, entityId, entityTypeId); + this.calendarInstanceRepository.save(newCalendarInstance); + + return new CommandProcessingResultBuilder() // + .withCommandId(null) // + .withEntityId(calendarForUpdate.getId()) // + .build(); + } + + @Override + public CommandProcessingResult updateCalendarInstance(Long calendarId, Long entityId, Integer entityTypeId) { + final Calendar calendarForUpdate = this.calendarRepository.findOne(calendarId); + if (calendarForUpdate == null) { throw new CalendarNotFoundException(calendarId); } + + final CalendarInstance calendarInstanceForUpdate = this.calendarInstanceRepository.findByCalendarAndEntityIdAndEntityTypeId(calendarId, entityId, entityTypeId); + this.calendarInstanceRepository.saveAndFlush(calendarInstanceForUpdate); + return new CommandProcessingResultBuilder() // + .withCommandId(null) // + .withEntityId(calendarForUpdate.getId()) // + .build(); + } + + }