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 420c41c09..1b0355cff 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,7 +365,7 @@ public class DataValidatorBuilder { return this; } - public DataValidatorBuilder isValidateRecurringRule(final String recurringRule) { + public DataValidatorBuilder isValidRecurringRule(final String recurringRule) { if (StringUtils.isNotBlank(recurringRule)) { try { RRule rRule = new RRule(recurringRule); 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 acbd95e0e..7ef9725f9 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 @@ -40,7 +40,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.mifosplatform.portfolio.calendar.service.CalendarHelper; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Scope; import org.springframework.stereotype.Component; @@ -54,9 +54,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", "duration", "type", "repeating", "recurrence", "remindBy", - "firstReminder", "secondReminder", "humanReadable", "createdDate", "lastUpdatedDate", "createdByUserId", "createdByUsername", "lastUpdatedByUserId", - "lastUpdatedByUsername", "recurringDates")); + "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; @@ -65,38 +65,37 @@ 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 CalendarWrapperService calendarWrapperService) { + final CalendarDropdownReadPlatformService dropdownReadPlatformService) { this.context = context; this.readPlatformService = readPlatformService; this.toApiJsonSerializer = toApiJsonSerializer; this.apiRequestParameterHelper = apiRequestParameterHelper; this.commandsSourceWritePlatformService = commandsSourceWritePlatformService; this.dropdownReadPlatformService = dropdownReadPlatformService; - this.calendarWrapperService = calendarWrapperService; } @GET @Path("{calendarId}") @Consumes({ MediaType.APPLICATION_JSON }) @Produces({ MediaType.APPLICATION_JSON }) - public String retrieveCalendar(@PathParam("calendarId") final Long calendarId, @PathParam("entityType") final String entityType, @PathParam("entityId") final Long entityId, @Context final UriInfo uriInfo) { + public String retrieveCalendar(@PathParam("calendarId") final Long calendarId, @PathParam("entityType") final String entityType, + @PathParam("entityId") final Long entityId, @Context final UriInfo uriInfo) { this.context.authenticatedUser().validateHasReadPermission(this.resourceNameForPermissions); final Integer entityTypeId = CalendarEntityType.valueOf(entityType.toUpperCase()).getValue(); CalendarData calendarData = this.readPlatformService.retrieveCalendar(calendarId, entityId, entityTypeId); - //Include recurring date details - calendarData = handleRecurringDate(calendarData); - + // Include recurring date details + calendarData = handleRecurringDate(calendarData); + final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters()); - if(settings.isTemplate()){ - calendarData = handleTemplate(calendarData); + if (settings.isTemplate()) { + calendarData = handleTemplate(calendarData); } return this.toApiJsonSerializer.serialize(settings, calendarData, this.RESPONSE_DATA_PARAMETERS); } @@ -108,11 +107,11 @@ public class CalendarsApiResource { @Context final UriInfo uriInfo) { this.context.authenticatedUser().validateHasReadPermission(this.resourceNameForPermissions); - + final Set associationParameters = ApiParameterHelper.extractAssociationsForResponseIfProvided(uriInfo.getQueryParameters()); - + Collection calendarsData = new ArrayList(); - + if (!associationParameters.isEmpty()) { if (associationParameters.contains("parentCalendars")) { calendarsData.addAll(this.readPlatformService.retrieveParentCalendarsByEntity(entityId, @@ -120,12 +119,12 @@ public class CalendarsApiResource { } } - calendarsData.addAll(this.readPlatformService.retrieveCalendarsByEntity(entityId, CalendarEntityType.valueOf(entityType.toUpperCase()) - .getValue())); + calendarsData.addAll(this.readPlatformService.retrieveCalendarsByEntity(entityId, + CalendarEntityType.valueOf(entityType.toUpperCase()).getValue())); - //Add recurring dates + // Add recurring dates calendarsData = handleRecurringDates(calendarsData); - + final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters()); return this.toApiJsonSerializer.serialize(settings, calendarsData, this.RESPONSE_DATA_PARAMETERS); } @@ -198,17 +197,18 @@ public class CalendarsApiResource { private CalendarData handleRecurringDate(final CalendarData calendarData){ if(!calendarData.isRepeating()) return calendarData; - final Collection recurringDates = this.calendarWrapperService.getNextRecurrencesList(calendarData); + + final Collection recurringDates = CalendarHelper.getRecurringDates(calendarData.getRecurrence(), calendarData.getStartDate(), calendarData.getEndDate()); return new CalendarData(calendarData, recurringDates); } - - private Collection handleRecurringDates(final Collection calendarsData){ + + 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/domain/Calendar.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/calendar/domain/Calendar.java index 130a487b0..28ece74a5 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 @@ -267,7 +267,7 @@ public class Calendar extends AbstractAuditableCustom { return this.secondReminder; } - private LocalDate getStartDateLocalDate() { + public LocalDate getStartDateLocalDate() { LocalDate startDateLocalDate = null; if (this.startDate != null) { startDateLocalDate = LocalDate.fromDateFields(this.startDate); @@ -275,7 +275,7 @@ public class Calendar extends AbstractAuditableCustom { return startDateLocalDate; } - private LocalDate getEndDateLocalDate() { + public LocalDate getEndDateLocalDate() { LocalDate endDateLocalDate = null; if (this.endDate != null) { endDateLocalDate = LocalDate.fromDateFields(this.endDate); 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 index ced0c1713..ee5c665ff 100644 --- 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 @@ -29,12 +29,20 @@ public class CalendarInstance extends AbstractPersistable { this.entityTypeId = entityTypeId; } + protected CalendarInstance(){ + + } + 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 void updateCalendar(final Calendar calendar){ + this.calendar = calendar; + } + public Calendar getCalendar() { return this.calendar; } 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 index 2a6f5d70a..fbc573150 100644 --- 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 @@ -1,5 +1,7 @@ package org.mifosplatform.portfolio.calendar.domain; +import java.util.Collection; + import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.JpaSpecificationExecutor; @@ -7,4 +9,6 @@ public interface CalendarInstanceRepository extends JpaRepository findByEntityIdAndEntityTypeId(Long entityId, Integer entityTypeId); + } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/calendar/exception/NotValidRecurringDateException.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/calendar/exception/NotValidRecurringDateException.java new file mode 100644 index 000000000..6600feefa --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/calendar/exception/NotValidRecurringDateException.java @@ -0,0 +1,18 @@ +/** + * 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.exception; + +import org.mifosplatform.infrastructure.core.exception.AbstractPlatformDomainRuleException; + +/** + * A {@link RuntimeException} thrown when Calendar resources are not found. + */ +public class NotValidRecurringDateException extends AbstractPlatformDomainRuleException { + + public NotValidRecurringDateException(String postFix, String defaultUserMessage, Object... defaultUserMessageArgs) { + super("error.msg.calendar." + postFix + ".not.valid.recurring.date", defaultUserMessage, defaultUserMessageArgs); + } +} \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/calendar/serialization/CalendarCommandFromApiJsonDeserializer.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/calendar/serialization/CalendarCommandFromApiJsonDeserializer.java index e26c4e78a..357e19f0c 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/calendar/serialization/CalendarCommandFromApiJsonDeserializer.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/calendar/serialization/CalendarCommandFromApiJsonDeserializer.java @@ -142,7 +142,7 @@ public class CalendarCommandFromApiJsonDeserializer extends AbstractFromApiJsonD if(repeating){ final String recurrence = this.fromApiJsonHelper.extractStringNamed(CALENDAR_SUPPORTED_PARAMETERS.RECURRENCE.getValue(), element); baseDataValidator.reset().parameter(CALENDAR_SUPPORTED_PARAMETERS.RECURRENCE.getValue()).value(recurrence).notNull() - .notExceedingLengthOf(100).isValidateRecurringRule(recurrence); + .notExceedingLengthOf(100).isValidRecurringRule(recurrence); } } @@ -235,7 +235,7 @@ public class CalendarCommandFromApiJsonDeserializer extends AbstractFromApiJsonD if(repeating){ final String recurrence = this.fromApiJsonHelper.extractStringNamed(CALENDAR_SUPPORTED_PARAMETERS.RECURRENCE.getValue(), element); baseDataValidator.reset().parameter(CALENDAR_SUPPORTED_PARAMETERS.RECURRENCE.getValue()).value(recurrence).notNull() - .notExceedingLengthOf(100).isValidateRecurringRule(recurrence); + .notExceedingLengthOf(100).isValidRecurringRule(recurrence); } } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/calendar/service/CalendarHelper.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/calendar/service/CalendarHelper.java new file mode 100644 index 000000000..e0b827590 --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/calendar/service/CalendarHelper.java @@ -0,0 +1,178 @@ +package org.mifosplatform.portfolio.calendar.service; + +import java.text.ParseException; +import java.util.ArrayList; +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.infrastructure.core.service.DateUtils; + +public class CalendarHelper { + + public static LocalDate getNextRecurringDate(final String recurringRule, final LocalDate seedDate, final LocalDate startDate) { + + final Recur recur = CalendarHelper.getICalRecur(recurringRule); + + if (recur == null) { return null; } + + final Date nextRecDate = recur.getNextDate(new Date(seedDate.toDate()), new Date(startDate.toDate())); + return nextRecDate == null ? null : new LocalDate(nextRecDate); + } + + public static Collection getRecurringDates(final String recurringRule, final LocalDate seedDate, final LocalDate endDate) { + + final LocalDate periodStartDate = DateUtils.getLocalDateOfTenant(); + final LocalDate periodEndDate = (endDate == null) ? DateUtils.getLocalDateOfTenant().plusYears(5) : endDate; + return getRecurringDates(recurringRule, seedDate, periodStartDate, periodEndDate); + } + + public static Collection getRecurringDates(final String recurringRule, final LocalDate seedDate, + final LocalDate periodStartDate, final LocalDate periodEndDate) { + final int maxCount = 15;// Default number of recurring dates + return getRecurringDates(recurringRule, seedDate, periodStartDate, periodEndDate, maxCount); + } + + public static Collection getRecurringDates(final String recurringRule, final LocalDate seedDate, + final LocalDate periodStartDate, final LocalDate periodEndDate, final int maxCount) { + + final Recur recur = CalendarHelper.getICalRecur(recurringRule); + + if (recur == null) { return null; } + + final Date seed = new Date(seedDate.toDate()); + final DateTime periodStart = new DateTime(periodStartDate.toDate()); + final DateTime periodEnd = new DateTime(periodEndDate.toDate()); + + final Value value = new Value(Value.DATE.getValue()); + final DateList recurringDates = recur.getDates(seed, periodStart, periodEnd, value, maxCount); + return convertToLocalDateList(recurringDates); + } + + private static Collection convertToLocalDateList(final DateList dates) { + + final 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; + } + + public static Recur getICalRecur(final String recurringRule) { + + // Construct RRule + try { + final RRule rrule = new RRule(recurringRule); + 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 (final ValidationException e) { + throw new PlatformDataIntegrityException("error.msg.invalid.recurring.rule", "The Recurring Rule value: " + recurringRule + + " is not valid.", "recurrence", recurringRule); + } catch (final 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 "; + final WeekDayList weekDayList = recur.getDayList(); + + for (@SuppressWarnings("rawtypes") + final Iterator iterator = weekDayList.iterator(); iterator.hasNext();) { + final 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"; + } + + final Date endDate = recur.getUntil(); + final LocalDate date = new LocalDate(endDate); + final DateTimeFormatter fmt = DateTimeFormat.forPattern("dd MMMM YY"); + final String formattedDate = date.toString(fmt); + if (endDate != null) { + humanReadable += ", until " + formattedDate; + } + + return humanReadable; + } + + public static boolean isValidRedurringDate(final String recurringRule, final LocalDate seedDate, final LocalDate date){ + + final Collection recurDate = getRecurringDates(recurringRule, seedDate, date, date.plusDays(1), 1); + return (recurDate == null || recurDate.isEmpty()) ? false : true; + } +} 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 de1018657..0c42285f3 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 @@ -67,7 +67,7 @@ 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 String humanReadable = CalendarHelper.getRRuleReadable(startDate, recurrence); final LocalDate createdDate = JdbcSupport.getLocalDate(rs, "createdDate"); final LocalDate lastUpdatedDate = JdbcSupport.getLocalDate(rs, "updatedDate"); 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 deleted file mode 100644 index 88844099f..000000000 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/calendar/service/CalendarWrapperService.java +++ /dev/null @@ -1,187 +0,0 @@ -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 - LocalDate periodStartDate = new LocalDate(Calendar.getInstance().getTime()); - return getNextRecurrencesList(calendarData, maxCount, periodStartDate); - } - - public Collection getNextRecurrencesList(final CalendarData calendarData, int maxCount, final LocalDate psDate) { - final Recur recur = getRecur(calendarData); - - if (recur == null) { return null; } - - final Date seed = new Date(calendarData.getStartDate().toDate()); - final DateTime periodStart = new DateTime(psDate.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/loanaccount/serialization/LoanApplicationCommandFromApiJsonHelper.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/serialization/LoanApplicationCommandFromApiJsonHelper.java index 577a68706..be516ef32 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/serialization/LoanApplicationCommandFromApiJsonHelper.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/serialization/LoanApplicationCommandFromApiJsonHelper.java @@ -483,6 +483,12 @@ public final class LoanApplicationCommandFromApiJsonHelper { .notExceedingLengthOf(500); } + final String calendarIdParameterName = "calendarId"; + if (fromApiJsonHelper.parameterExists(calendarIdParameterName, element)) { + final Long calendarId = fromApiJsonHelper.extractLongNamed(calendarIdParameterName, element); + baseDataValidator.reset().parameter(calendarIdParameterName).value(calendarId).ignoreIfNull().integerGreaterThanZero(); + } + // charges final String chargesParameterName = "charges"; if (element.isJsonObject() && fromApiJsonHelper.parameterExists(chargesParameterName, element)) { diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanApplicationWritePlatformServiceJpaRepositoryImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanApplicationWritePlatformServiceJpaRepositoryImpl.java index 2a9673433..6be29d72f 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanApplicationWritePlatformServiceJpaRepositoryImpl.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanApplicationWritePlatformServiceJpaRepositoryImpl.java @@ -11,6 +11,7 @@ import java.util.Map; import java.util.Set; import org.apache.commons.lang.StringUtils; +import org.joda.time.LocalDate; import org.mifosplatform.infrastructure.codes.domain.CodeValue; import org.mifosplatform.infrastructure.core.api.JsonCommand; import org.mifosplatform.infrastructure.core.api.JsonQuery; @@ -20,6 +21,14 @@ import org.mifosplatform.infrastructure.core.exception.PlatformDataIntegrityExce import org.mifosplatform.infrastructure.core.serialization.FromJsonHelper; import org.mifosplatform.infrastructure.security.service.PlatformSecurityContext; import org.mifosplatform.organisation.staff.domain.Staff; +import org.mifosplatform.portfolio.calendar.domain.Calendar; +import org.mifosplatform.portfolio.calendar.domain.CalendarEntityType; +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.exception.NotValidRecurringDateException; +import org.mifosplatform.portfolio.calendar.service.CalendarHelper; import org.mifosplatform.portfolio.client.domain.AccountNumberGenerator; import org.mifosplatform.portfolio.client.domain.AccountNumberGeneratorFactory; import org.mifosplatform.portfolio.client.domain.Client; @@ -80,6 +89,8 @@ public class LoanApplicationWritePlatformServiceJpaRepositoryImpl implements Loa private final AccountNumberGeneratorFactory accountIdentifierGeneratorFactory; private final LoanSummaryWrapper loanSummaryWrapper; private final LoanRepaymentScheduleTransactionProcessorFactory loanRepaymentScheduleTransactionProcessorFactory; + private final CalendarRepository calendarRepository; + private final CalendarInstanceRepository calendarInstanceRepository; @Autowired public LoanApplicationWritePlatformServiceJpaRepositoryImpl(final PlatformSecurityContext context, final FromJsonHelper fromJsonHelper, @@ -90,7 +101,8 @@ public class LoanApplicationWritePlatformServiceJpaRepositoryImpl implements Loa final NoteRepository noteRepository, final LoanScheduleCalculationPlatformService calculationPlatformService, final ClientRepository clientRepository, final LoanProductRepository loanProductRepository, final AccountNumberGeneratorFactory accountIdentifierGeneratorFactory, final LoanSummaryWrapper loanSummaryWrapper, - final LoanRepaymentScheduleTransactionProcessorFactory loanRepaymentScheduleTransactionProcessorFactory) { + final LoanRepaymentScheduleTransactionProcessorFactory loanRepaymentScheduleTransactionProcessorFactory, + final CalendarRepository calendarRepository, final CalendarInstanceRepository calendarInstanceRepository) { this.context = context; this.fromJsonHelper = fromJsonHelper; this.loanApplicationTransitionApiJsonValidator = loanApplicationTransitionApiJsonValidator; @@ -107,6 +119,8 @@ public class LoanApplicationWritePlatformServiceJpaRepositoryImpl implements Loa this.accountIdentifierGeneratorFactory = accountIdentifierGeneratorFactory; this.loanSummaryWrapper = loanSummaryWrapper; this.loanRepaymentScheduleTransactionProcessorFactory = loanRepaymentScheduleTransactionProcessorFactory; + this.calendarRepository = calendarRepository; + this.calendarInstanceRepository = calendarInstanceRepository; } private LoanLifecycleStateMachine defaultLoanLifecycleStateMachine() { @@ -122,6 +136,17 @@ public class LoanApplicationWritePlatformServiceJpaRepositoryImpl implements Loa this.fromApiJsonDeserializer.validateForCreate(command.json()); + + //validate dates against calendar recurring dates + //get calendar + final Long calendarId = command.longValueOfParameterNamed("calendarId"); + Calendar calendar = null; + if(calendarId != null && calendarId != 0){ + calendar = this.calendarRepository.findOne(calendarId); + if(calendar == null) { throw new CalendarNotFoundException(calendarId); } + validateCalendarDates(command, calendar); + } + final Loan newLoanApplication = loanAssembler.assembleFrom(command, currentUser); this.loanRepository.save(newLoanApplication); @@ -138,11 +163,11 @@ public class LoanApplicationWritePlatformServiceJpaRepositoryImpl implements Loa Note note = Note.loanNote(newLoanApplication, submittedOnNote); this.noteRepository.save(note); } - - //get calendar - final Long calendarId = command.longValueOfParameterNamed("calendarId"); - if(calendarId != null && calendarId != 0){ - //Save calendar instance + + //Save calendar instance + if(calendar != null){ + CalendarInstance calendarInstance = new CalendarInstance(calendar, newLoanApplication.getId(), CalendarEntityType.LOANS.getValue()); + this.calendarInstanceRepository.save(calendarInstance); } return new CommandProcessingResultBuilder() // @@ -164,6 +189,14 @@ public class LoanApplicationWritePlatformServiceJpaRepositoryImpl implements Loa this.fromApiJsonDeserializer.validateForModify(command.json()); + final Long calendarId = command.longValueOfParameterNamed("calendarId"); + Calendar calendar = null; + if(calendarId != null && calendarId != 0){ + calendar = this.calendarRepository.findOne(calendarId); + if(calendar == null) { throw new CalendarNotFoundException(calendarId); } + validateCalendarDates(command, calendar); + } + final Loan existingLoanApplication = retrieveLoanBy(loanId); if (!existingLoanApplication.isSubmittedAndPendingApproval()) { throw new LoanApplicationNotInSubmittedAndPendingApprovalStateCannotBeModified( @@ -254,6 +287,23 @@ public class LoanApplicationWritePlatformServiceJpaRepositoryImpl implements Loa this.noteRepository.save(note); } + List ciList = (List) this.calendarInstanceRepository.findByEntityIdAndEntityTypeId(loanId, CalendarEntityType.LOANS.getValue()); + if(calendar != null){ + + //For loans, allow to attach only one calendar instance per loan + if(ciList != null && !ciList.isEmpty()){ + CalendarInstance calendarInstance = ciList.get(0); + if(calendarInstance.getCalendar().getId() != calendar.getId()){ + calendarInstance.updateCalendar(calendar); + this.calendarInstanceRepository.saveAndFlush(calendarInstance); + } + } + + }else if(ciList != null && !ciList.isEmpty()){ + CalendarInstance calendarInstance = ciList.get(0); + this.calendarInstanceRepository.delete(calendarInstance); + } + return new CommandProcessingResultBuilder() // .withEntityId(loanId) // .withOfficeId(existingLoanApplication.getOfficeId()) // @@ -454,4 +504,32 @@ public class LoanApplicationWritePlatformServiceJpaRepositoryImpl implements Loa loan.setHelpers(defaultLoanLifecycleStateMachine(), this.loanSummaryWrapper, this.loanRepaymentScheduleTransactionProcessorFactory); return loan; } + + private void validateCalendarDates(final JsonCommand command, final Calendar calendar) { + + // Validate disbursement date and first repayment dates matches calendar + // date + final LocalDate edDate = command.localDateValueOfParameterNamed("expectedDisbursementDate"); + if (edDate != null) { + + if (!CalendarHelper.isValidRedurringDate(calendar.getRecurrence(), calendar.getStartDateLocalDate(), edDate)) { + final String errorMessage = "The expected disbursement date :" + (edDate.toString()) + " does not fall on a meeting date."; + throw new NotValidRecurringDateException("loan.expected.disbursement.date", errorMessage, edDate.toString(), + calendar.getTitle()); + } + + } + + final LocalDate repStartingDate = command.localDateValueOfParameterNamed("repaymentsStartingFromDate"); + if (repStartingDate != null) { + + if (!CalendarHelper.isValidRedurringDate(calendar.getRecurrence(), calendar.getStartDateLocalDate(), repStartingDate)) { + final String errorMessage = "The First repayment date :" + (repStartingDate.toString()) + " do not fall a meeting date."; + throw new NotValidRecurringDateException("loan.first.repayment.date", errorMessage, repStartingDate.toString(), + calendar.getTitle()); + } + + } + + } } \ No newline at end of file