MIFOSX-256: Added calendar meeting date validation for expected disbursment date and first repayment date

This commit is contained in:
ashok-conflux
2013-03-20 21:03:38 +05:30
parent 7ae32b346d
commit eec727db5b
12 changed files with 329 additions and 224 deletions
@@ -365,7 +365,7 @@ public class DataValidatorBuilder {
return this; return this;
} }
public DataValidatorBuilder isValidateRecurringRule(final String recurringRule) { public DataValidatorBuilder isValidRecurringRule(final String recurringRule) {
if (StringUtils.isNotBlank(recurringRule)) { if (StringUtils.isNotBlank(recurringRule)) {
try { try {
RRule rRule = new RRule(recurringRule); RRule rRule = new RRule(recurringRule);
@@ -40,7 +40,7 @@ import org.mifosplatform.portfolio.calendar.domain.Calendar;
import org.mifosplatform.portfolio.calendar.domain.CalendarEntityType; import org.mifosplatform.portfolio.calendar.domain.CalendarEntityType;
import org.mifosplatform.portfolio.calendar.service.CalendarDropdownReadPlatformService; import org.mifosplatform.portfolio.calendar.service.CalendarDropdownReadPlatformService;
import org.mifosplatform.portfolio.calendar.service.CalendarReadPlatformService; 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.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Scope; import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
@@ -54,9 +54,9 @@ public class CalendarsApiResource {
* The set of parameters that are supported in response for {@link Calendar} * The set of parameters that are supported in response for {@link Calendar}
*/ */
private final Set<String> RESPONSE_DATA_PARAMETERS = new HashSet<String>(Arrays.asList("id", "entityId", "entityType", "title", private final Set<String> RESPONSE_DATA_PARAMETERS = new HashSet<String>(Arrays.asList("id", "entityId", "entityType", "title",
"description", "location", "startDate", "endDate", "duration", "type", "repeating", "recurrence", "remindBy", "description", "location", "startDate", "endDate", "duration", "type", "repeating", "recurrence", "remindBy", "firstReminder",
"firstReminder", "secondReminder", "humanReadable", "createdDate", "lastUpdatedDate", "createdByUserId", "createdByUsername", "lastUpdatedByUserId", "secondReminder", "humanReadable", "createdDate", "lastUpdatedDate", "createdByUserId", "createdByUsername",
"lastUpdatedByUsername", "recurringDates")); "lastUpdatedByUserId", "lastUpdatedByUsername", "recurringDates"));
private final String resourceNameForPermissions = "CALENDAR"; private final String resourceNameForPermissions = "CALENDAR";
private final PlatformSecurityContext context; private final PlatformSecurityContext context;
@@ -65,38 +65,37 @@ public class CalendarsApiResource {
private final ApiRequestParameterHelper apiRequestParameterHelper; private final ApiRequestParameterHelper apiRequestParameterHelper;
private final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService; private final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService;
private final CalendarDropdownReadPlatformService dropdownReadPlatformService; private final CalendarDropdownReadPlatformService dropdownReadPlatformService;
private final CalendarWrapperService calendarWrapperService;
@Autowired @Autowired
public CalendarsApiResource(final PlatformSecurityContext context, final CalendarReadPlatformService readPlatformService, public CalendarsApiResource(final PlatformSecurityContext context, final CalendarReadPlatformService readPlatformService,
final DefaultToApiJsonSerializer<CalendarData> toApiJsonSerializer, final ApiRequestParameterHelper apiRequestParameterHelper, final DefaultToApiJsonSerializer<CalendarData> toApiJsonSerializer, final ApiRequestParameterHelper apiRequestParameterHelper,
final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService, final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService,
final CalendarDropdownReadPlatformService dropdownReadPlatformService, final CalendarWrapperService calendarWrapperService) { final CalendarDropdownReadPlatformService dropdownReadPlatformService) {
this.context = context; this.context = context;
this.readPlatformService = readPlatformService; this.readPlatformService = readPlatformService;
this.toApiJsonSerializer = toApiJsonSerializer; this.toApiJsonSerializer = toApiJsonSerializer;
this.apiRequestParameterHelper = apiRequestParameterHelper; this.apiRequestParameterHelper = apiRequestParameterHelper;
this.commandsSourceWritePlatformService = commandsSourceWritePlatformService; this.commandsSourceWritePlatformService = commandsSourceWritePlatformService;
this.dropdownReadPlatformService = dropdownReadPlatformService; this.dropdownReadPlatformService = dropdownReadPlatformService;
this.calendarWrapperService = calendarWrapperService;
} }
@GET @GET
@Path("{calendarId}") @Path("{calendarId}")
@Consumes({ MediaType.APPLICATION_JSON }) @Consumes({ MediaType.APPLICATION_JSON })
@Produces({ 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); this.context.authenticatedUser().validateHasReadPermission(this.resourceNameForPermissions);
final Integer entityTypeId = CalendarEntityType.valueOf(entityType.toUpperCase()).getValue(); final Integer entityTypeId = CalendarEntityType.valueOf(entityType.toUpperCase()).getValue();
CalendarData calendarData = this.readPlatformService.retrieveCalendar(calendarId, entityId, entityTypeId); CalendarData calendarData = this.readPlatformService.retrieveCalendar(calendarId, entityId, entityTypeId);
//Include recurring date details // Include recurring date details
calendarData = handleRecurringDate(calendarData); calendarData = handleRecurringDate(calendarData);
final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters()); final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
if(settings.isTemplate()){ if (settings.isTemplate()) {
calendarData = handleTemplate(calendarData); calendarData = handleTemplate(calendarData);
} }
return this.toApiJsonSerializer.serialize(settings, calendarData, this.RESPONSE_DATA_PARAMETERS); return this.toApiJsonSerializer.serialize(settings, calendarData, this.RESPONSE_DATA_PARAMETERS);
} }
@@ -108,11 +107,11 @@ public class CalendarsApiResource {
@Context final UriInfo uriInfo) { @Context final UriInfo uriInfo) {
this.context.authenticatedUser().validateHasReadPermission(this.resourceNameForPermissions); this.context.authenticatedUser().validateHasReadPermission(this.resourceNameForPermissions);
final Set<String> associationParameters = ApiParameterHelper.extractAssociationsForResponseIfProvided(uriInfo.getQueryParameters()); final Set<String> associationParameters = ApiParameterHelper.extractAssociationsForResponseIfProvided(uriInfo.getQueryParameters());
Collection<CalendarData> calendarsData = new ArrayList<CalendarData>(); Collection<CalendarData> calendarsData = new ArrayList<CalendarData>();
if (!associationParameters.isEmpty()) { if (!associationParameters.isEmpty()) {
if (associationParameters.contains("parentCalendars")) { if (associationParameters.contains("parentCalendars")) {
calendarsData.addAll(this.readPlatformService.retrieveParentCalendarsByEntity(entityId, calendarsData.addAll(this.readPlatformService.retrieveParentCalendarsByEntity(entityId,
@@ -120,12 +119,12 @@ public class CalendarsApiResource {
} }
} }
calendarsData.addAll(this.readPlatformService.retrieveCalendarsByEntity(entityId, CalendarEntityType.valueOf(entityType.toUpperCase()) calendarsData.addAll(this.readPlatformService.retrieveCalendarsByEntity(entityId,
.getValue())); CalendarEntityType.valueOf(entityType.toUpperCase()).getValue()));
//Add recurring dates // Add recurring dates
calendarsData = handleRecurringDates(calendarsData); calendarsData = handleRecurringDates(calendarsData);
final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters()); final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
return this.toApiJsonSerializer.serialize(settings, calendarsData, this.RESPONSE_DATA_PARAMETERS); return this.toApiJsonSerializer.serialize(settings, calendarsData, this.RESPONSE_DATA_PARAMETERS);
} }
@@ -198,17 +197,18 @@ public class CalendarsApiResource {
private CalendarData handleRecurringDate(final CalendarData calendarData){ private CalendarData handleRecurringDate(final CalendarData calendarData){
if(!calendarData.isRepeating()) return calendarData; if(!calendarData.isRepeating()) return calendarData;
final Collection<LocalDate> recurringDates = this.calendarWrapperService.getNextRecurrencesList(calendarData);
final Collection<LocalDate> recurringDates = CalendarHelper.getRecurringDates(calendarData.getRecurrence(), calendarData.getStartDate(), calendarData.getEndDate());
return new CalendarData(calendarData, recurringDates); return new CalendarData(calendarData, recurringDates);
} }
private Collection<CalendarData> handleRecurringDates(final Collection<CalendarData> calendarsData){ private Collection<CalendarData> handleRecurringDates(final Collection<CalendarData> calendarsData) {
Collection<CalendarData> recuCalendarsData = new ArrayList<CalendarData>(); Collection<CalendarData> recuCalendarsData = new ArrayList<CalendarData>();
for (CalendarData calendarData : calendarsData) { for (CalendarData calendarData : calendarsData) {
recuCalendarsData.add(handleRecurringDate(calendarData)); recuCalendarsData.add(handleRecurringDate(calendarData));
} }
return recuCalendarsData; return recuCalendarsData;
} }
} }
@@ -267,7 +267,7 @@ public class Calendar extends AbstractAuditableCustom<AppUser, Long> {
return this.secondReminder; return this.secondReminder;
} }
private LocalDate getStartDateLocalDate() { public LocalDate getStartDateLocalDate() {
LocalDate startDateLocalDate = null; LocalDate startDateLocalDate = null;
if (this.startDate != null) { if (this.startDate != null) {
startDateLocalDate = LocalDate.fromDateFields(this.startDate); startDateLocalDate = LocalDate.fromDateFields(this.startDate);
@@ -275,7 +275,7 @@ public class Calendar extends AbstractAuditableCustom<AppUser, Long> {
return startDateLocalDate; return startDateLocalDate;
} }
private LocalDate getEndDateLocalDate() { public LocalDate getEndDateLocalDate() {
LocalDate endDateLocalDate = null; LocalDate endDateLocalDate = null;
if (this.endDate != null) { if (this.endDate != null) {
endDateLocalDate = LocalDate.fromDateFields(this.endDate); endDateLocalDate = LocalDate.fromDateFields(this.endDate);
@@ -29,12 +29,20 @@ public class CalendarInstance extends AbstractPersistable<Long> {
this.entityTypeId = entityTypeId; this.entityTypeId = entityTypeId;
} }
protected CalendarInstance(){
}
public static CalendarInstance fromJson(final Calendar calendar, final JsonCommand command) { public static CalendarInstance fromJson(final Calendar calendar, final JsonCommand command) {
final Long entityId = command.getSupportedEntityId(); final Long entityId = command.getSupportedEntityId();
final Integer entityTypeId = CalendarEntityType.valueOf(command.getSupportedEntityType().toUpperCase()).getValue(); final Integer entityTypeId = CalendarEntityType.valueOf(command.getSupportedEntityType().toUpperCase()).getValue();
return new CalendarInstance(calendar, entityId, entityTypeId); return new CalendarInstance(calendar, entityId, entityTypeId);
} }
public void updateCalendar(final Calendar calendar){
this.calendar = calendar;
}
public Calendar getCalendar() { public Calendar getCalendar() {
return this.calendar; return this.calendar;
} }
@@ -1,5 +1,7 @@
package org.mifosplatform.portfolio.calendar.domain; package org.mifosplatform.portfolio.calendar.domain;
import java.util.Collection;
import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor; import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
@@ -7,4 +9,6 @@ public interface CalendarInstanceRepository extends JpaRepository<CalendarInstan
CalendarInstance findByCalendarAndEntityIdAndEntityTypeId(Long calendarId, Long entityId, Integer entityTypeId); CalendarInstance findByCalendarAndEntityIdAndEntityTypeId(Long calendarId, Long entityId, Integer entityTypeId);
Collection<CalendarInstance> findByEntityIdAndEntityTypeId(Long entityId, Integer entityTypeId);
} }
@@ -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);
}
}
@@ -142,7 +142,7 @@ public class CalendarCommandFromApiJsonDeserializer extends AbstractFromApiJsonD
if(repeating){ if(repeating){
final String recurrence = this.fromApiJsonHelper.extractStringNamed(CALENDAR_SUPPORTED_PARAMETERS.RECURRENCE.getValue(), element); final String recurrence = this.fromApiJsonHelper.extractStringNamed(CALENDAR_SUPPORTED_PARAMETERS.RECURRENCE.getValue(), element);
baseDataValidator.reset().parameter(CALENDAR_SUPPORTED_PARAMETERS.RECURRENCE.getValue()).value(recurrence).notNull() 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){ if(repeating){
final String recurrence = this.fromApiJsonHelper.extractStringNamed(CALENDAR_SUPPORTED_PARAMETERS.RECURRENCE.getValue(), element); final String recurrence = this.fromApiJsonHelper.extractStringNamed(CALENDAR_SUPPORTED_PARAMETERS.RECURRENCE.getValue(), element);
baseDataValidator.reset().parameter(CALENDAR_SUPPORTED_PARAMETERS.RECURRENCE.getValue()).value(recurrence).notNull() baseDataValidator.reset().parameter(CALENDAR_SUPPORTED_PARAMETERS.RECURRENCE.getValue()).value(recurrence).notNull()
.notExceedingLengthOf(100).isValidateRecurringRule(recurrence); .notExceedingLengthOf(100).isValidRecurringRule(recurrence);
} }
} }
@@ -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<LocalDate> 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<LocalDate> 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<LocalDate> 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<LocalDate> convertToLocalDateList(final DateList dates) {
final Collection<LocalDate> recurringDates = new ArrayList<LocalDate>();
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<LocalDate> recurDate = getRecurringDates(recurringRule, seedDate, date, date.plusDays(1), 1);
return (recurDate == null || recurDate.isEmpty()) ? false : true;
}
}
@@ -67,7 +67,7 @@ public class CalendarReadPlatformServiceImpl implements CalendarReadPlatformServ
if(remindById != null) remindBy = CalendarEnumerations.calendarRemindBy(remindById); if(remindById != null) remindBy = CalendarEnumerations.calendarRemindBy(remindById);
final Integer firstReminder = rs.getInt("firstReminder"); final Integer firstReminder = rs.getInt("firstReminder");
final Integer secondReminder = rs.getInt("secondReminder"); 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 createdDate = JdbcSupport.getLocalDate(rs, "createdDate");
final LocalDate lastUpdatedDate = JdbcSupport.getLocalDate(rs, "updatedDate"); final LocalDate lastUpdatedDate = JdbcSupport.getLocalDate(rs, "updatedDate");
@@ -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<LocalDate> 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<LocalDate> 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<LocalDate> getRecurringDates(final DateList dates) {
Collection<LocalDate> recurringDates = new ArrayList<LocalDate>();
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;
}
}
@@ -483,6 +483,12 @@ public final class LoanApplicationCommandFromApiJsonHelper {
.notExceedingLengthOf(500); .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 // charges
final String chargesParameterName = "charges"; final String chargesParameterName = "charges";
if (element.isJsonObject() && fromApiJsonHelper.parameterExists(chargesParameterName, element)) { if (element.isJsonObject() && fromApiJsonHelper.parameterExists(chargesParameterName, element)) {
@@ -11,6 +11,7 @@ import java.util.Map;
import java.util.Set; import java.util.Set;
import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.StringUtils;
import org.joda.time.LocalDate;
import org.mifosplatform.infrastructure.codes.domain.CodeValue; import org.mifosplatform.infrastructure.codes.domain.CodeValue;
import org.mifosplatform.infrastructure.core.api.JsonCommand; import org.mifosplatform.infrastructure.core.api.JsonCommand;
import org.mifosplatform.infrastructure.core.api.JsonQuery; 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.core.serialization.FromJsonHelper;
import org.mifosplatform.infrastructure.security.service.PlatformSecurityContext; import org.mifosplatform.infrastructure.security.service.PlatformSecurityContext;
import org.mifosplatform.organisation.staff.domain.Staff; 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.AccountNumberGenerator;
import org.mifosplatform.portfolio.client.domain.AccountNumberGeneratorFactory; import org.mifosplatform.portfolio.client.domain.AccountNumberGeneratorFactory;
import org.mifosplatform.portfolio.client.domain.Client; import org.mifosplatform.portfolio.client.domain.Client;
@@ -80,6 +89,8 @@ public class LoanApplicationWritePlatformServiceJpaRepositoryImpl implements Loa
private final AccountNumberGeneratorFactory accountIdentifierGeneratorFactory; private final AccountNumberGeneratorFactory accountIdentifierGeneratorFactory;
private final LoanSummaryWrapper loanSummaryWrapper; private final LoanSummaryWrapper loanSummaryWrapper;
private final LoanRepaymentScheduleTransactionProcessorFactory loanRepaymentScheduleTransactionProcessorFactory; private final LoanRepaymentScheduleTransactionProcessorFactory loanRepaymentScheduleTransactionProcessorFactory;
private final CalendarRepository calendarRepository;
private final CalendarInstanceRepository calendarInstanceRepository;
@Autowired @Autowired
public LoanApplicationWritePlatformServiceJpaRepositoryImpl(final PlatformSecurityContext context, final FromJsonHelper fromJsonHelper, public LoanApplicationWritePlatformServiceJpaRepositoryImpl(final PlatformSecurityContext context, final FromJsonHelper fromJsonHelper,
@@ -90,7 +101,8 @@ public class LoanApplicationWritePlatformServiceJpaRepositoryImpl implements Loa
final NoteRepository noteRepository, final LoanScheduleCalculationPlatformService calculationPlatformService, final NoteRepository noteRepository, final LoanScheduleCalculationPlatformService calculationPlatformService,
final ClientRepository clientRepository, final LoanProductRepository loanProductRepository, final ClientRepository clientRepository, final LoanProductRepository loanProductRepository,
final AccountNumberGeneratorFactory accountIdentifierGeneratorFactory, final LoanSummaryWrapper loanSummaryWrapper, final AccountNumberGeneratorFactory accountIdentifierGeneratorFactory, final LoanSummaryWrapper loanSummaryWrapper,
final LoanRepaymentScheduleTransactionProcessorFactory loanRepaymentScheduleTransactionProcessorFactory) { final LoanRepaymentScheduleTransactionProcessorFactory loanRepaymentScheduleTransactionProcessorFactory,
final CalendarRepository calendarRepository, final CalendarInstanceRepository calendarInstanceRepository) {
this.context = context; this.context = context;
this.fromJsonHelper = fromJsonHelper; this.fromJsonHelper = fromJsonHelper;
this.loanApplicationTransitionApiJsonValidator = loanApplicationTransitionApiJsonValidator; this.loanApplicationTransitionApiJsonValidator = loanApplicationTransitionApiJsonValidator;
@@ -107,6 +119,8 @@ public class LoanApplicationWritePlatformServiceJpaRepositoryImpl implements Loa
this.accountIdentifierGeneratorFactory = accountIdentifierGeneratorFactory; this.accountIdentifierGeneratorFactory = accountIdentifierGeneratorFactory;
this.loanSummaryWrapper = loanSummaryWrapper; this.loanSummaryWrapper = loanSummaryWrapper;
this.loanRepaymentScheduleTransactionProcessorFactory = loanRepaymentScheduleTransactionProcessorFactory; this.loanRepaymentScheduleTransactionProcessorFactory = loanRepaymentScheduleTransactionProcessorFactory;
this.calendarRepository = calendarRepository;
this.calendarInstanceRepository = calendarInstanceRepository;
} }
private LoanLifecycleStateMachine defaultLoanLifecycleStateMachine() { private LoanLifecycleStateMachine defaultLoanLifecycleStateMachine() {
@@ -122,6 +136,17 @@ public class LoanApplicationWritePlatformServiceJpaRepositoryImpl implements Loa
this.fromApiJsonDeserializer.validateForCreate(command.json()); 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); final Loan newLoanApplication = loanAssembler.assembleFrom(command, currentUser);
this.loanRepository.save(newLoanApplication); this.loanRepository.save(newLoanApplication);
@@ -138,11 +163,11 @@ public class LoanApplicationWritePlatformServiceJpaRepositoryImpl implements Loa
Note note = Note.loanNote(newLoanApplication, submittedOnNote); Note note = Note.loanNote(newLoanApplication, submittedOnNote);
this.noteRepository.save(note); this.noteRepository.save(note);
} }
//get calendar //Save calendar instance
final Long calendarId = command.longValueOfParameterNamed("calendarId"); if(calendar != null){
if(calendarId != null && calendarId != 0){ CalendarInstance calendarInstance = new CalendarInstance(calendar, newLoanApplication.getId(), CalendarEntityType.LOANS.getValue());
//Save calendar instance this.calendarInstanceRepository.save(calendarInstance);
} }
return new CommandProcessingResultBuilder() // return new CommandProcessingResultBuilder() //
@@ -164,6 +189,14 @@ public class LoanApplicationWritePlatformServiceJpaRepositoryImpl implements Loa
this.fromApiJsonDeserializer.validateForModify(command.json()); 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); final Loan existingLoanApplication = retrieveLoanBy(loanId);
if (!existingLoanApplication.isSubmittedAndPendingApproval()) { throw new LoanApplicationNotInSubmittedAndPendingApprovalStateCannotBeModified( if (!existingLoanApplication.isSubmittedAndPendingApproval()) { throw new LoanApplicationNotInSubmittedAndPendingApprovalStateCannotBeModified(
@@ -254,6 +287,23 @@ public class LoanApplicationWritePlatformServiceJpaRepositoryImpl implements Loa
this.noteRepository.save(note); this.noteRepository.save(note);
} }
List<CalendarInstance> ciList = (List<CalendarInstance>) 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() // return new CommandProcessingResultBuilder() //
.withEntityId(loanId) // .withEntityId(loanId) //
.withOfficeId(existingLoanApplication.getOfficeId()) // .withOfficeId(existingLoanApplication.getOfficeId()) //
@@ -454,4 +504,32 @@ public class LoanApplicationWritePlatformServiceJpaRepositoryImpl implements Loa
loan.setHelpers(defaultLoanLifecycleStateMachine(), this.loanSummaryWrapper, this.loanRepaymentScheduleTransactionProcessorFactory); loan.setHelpers(defaultLoanLifecycleStateMachine(), this.loanSummaryWrapper, this.loanRepaymentScheduleTransactionProcessorFactory);
return loan; 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());
}
}
}
} }