/** * 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.service; import java.sql.ResultSet; import java.sql.SQLException; import java.util.ArrayList; import java.util.Collection; import java.util.List; import org.joda.time.LocalDate; import org.mifosplatform.infrastructure.core.data.EnumOptionData; import org.mifosplatform.infrastructure.core.domain.JdbcSupport; import org.mifosplatform.infrastructure.core.service.DateUtils; import org.mifosplatform.infrastructure.core.service.RoutingDataSource; import org.mifosplatform.portfolio.calendar.data.CalendarData; import org.mifosplatform.portfolio.calendar.domain.CalendarEntityType; import org.mifosplatform.portfolio.calendar.domain.CalendarType; import org.mifosplatform.portfolio.calendar.exception.CalendarNotFoundException; import org.mifosplatform.portfolio.meeting.data.MeetingData; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.dao.EmptyResultDataAccessException; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.core.RowMapper; import org.springframework.stereotype.Service; import org.springframework.util.CollectionUtils; @Service public class CalendarReadPlatformServiceImpl implements CalendarReadPlatformService { private final JdbcTemplate jdbcTemplate; @Autowired public CalendarReadPlatformServiceImpl(final RoutingDataSource dataSource) { this.jdbcTemplate = new JdbcTemplate(dataSource); } private static final class CalendarDataMapper implements RowMapper<CalendarData> { public String schema() { return " select c.id as id, ci.id as calendarInstanceId, ci.entity_id as entityId, ci.entity_type_enum as entityTypeId, c.title as title, " + " c.description as description, c.location as location, c.start_date as startDate, c.end_date as endDate, " + " c.duration as duration, c.calendar_type_enum as typeId, c.repeating as repeating, " + " c.recurrence as recurrence, c.remind_by_enum as remindById, c.first_reminder as firstReminder, c.second_reminder as secondReminder, " + " 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 public CalendarData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException { final Long id = rs.getLong("id"); final Long calendarInstanceId = rs.getLong("calendarInstanceId"); final Long entityId = rs.getLong("entityId"); final Integer entityTypeId = rs.getInt("entityTypeId"); final EnumOptionData entityType = CalendarEnumerations.calendarEntityType(entityTypeId); final String title = rs.getString("title"); final String description = rs.getString("description"); final String location = rs.getString("location"); final LocalDate startDate = JdbcSupport.getLocalDate(rs, "startDate"); final LocalDate endDate = JdbcSupport.getLocalDate(rs, "endDate"); final Integer duration = rs.getInt("duration"); final Integer typeId = rs.getInt("typeId"); final EnumOptionData type = CalendarEnumerations.calendarType(typeId); final boolean repeating = rs.getBoolean("repeating"); final String recurrence = rs.getString("recurrence"); final EnumOptionData frequency = CalendarEnumerations.calendarFrequencyType(CalendarUtils.getFrequency(recurrence)); final Integer interval = new Integer(CalendarUtils.getInterval(recurrence)); final EnumOptionData repeatsOnDay = CalendarEnumerations.calendarWeekDaysType(CalendarUtils.getRepeatsOnDay(recurrence)); final Integer remindById = rs.getInt("remindById"); EnumOptionData remindBy = null; if (remindById != null && remindById != 0) { remindBy = CalendarEnumerations.calendarRemindBy(remindById); } final Integer firstReminder = rs.getInt("firstReminder"); final Integer secondReminder = rs.getInt("secondReminder"); String humanReadable = null; if (startDate != null && recurrence != null) { humanReadable = CalendarUtils.getRRuleReadable(startDate, recurrence); } final LocalDate createdDate = 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 CalendarData.instance(id, calendarInstanceId, entityId, entityType, title, description, location, startDate, endDate, duration, type, repeating, recurrence, frequency, interval, repeatsOnDay, remindBy, firstReminder, secondReminder, humanReadable, createdDate, lastUpdatedDate, createdByUserId, createdByUserName, lastUpdatedByUserId, lastUpdatedByUserName); } } @Override public CalendarData retrieveCalendar(final Long calendarId, final Long entityId, final Integer entityTypeId) { try { final CalendarDataMapper rm = new CalendarDataMapper(); 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) { throw new CalendarNotFoundException(calendarId); } } @Override public Collection<CalendarData> retrieveCalendarsByEntity(final Long entityId, final Integer entityTypeId, final List<Integer> calendarTypeOptions) { final CalendarDataMapper rm = new CalendarDataMapper(); Collection<CalendarData> result = null; String sql = ""; if (calendarTypeOptions == null || calendarTypeOptions.isEmpty()) { sql = rm.schema() + " and ci.entity_id = ? and ci.entity_type_enum = ? order by c.start_date "; result = this.jdbcTemplate.query(sql, rm, new Object[] { entityId, entityTypeId }); } else if (!calendarTypeOptions.isEmpty()) { final String sqlCalendarTypeOptions = CalendarUtils.getSqlCalendarTypeOptionsInString(calendarTypeOptions); sql = rm.schema() + " and ci.entity_id = ? and ci.entity_type_enum = ? and c.calendar_type_enum in ( " + sqlCalendarTypeOptions + " ) order by c.start_date "; result = this.jdbcTemplate.query(sql, rm, new Object[] { entityId, entityTypeId }); } return result; } @Override public CalendarData retrieveCollctionCalendarByEntity(final Long entityId, final Integer entityTypeId) { final CalendarDataMapper rm = new CalendarDataMapper(); final String sql = rm.schema() + " and ci.entity_id = ? and ci.entity_type_enum = ? and calendar_type_enum = ? order by c.start_date "; final List<CalendarData> result = this.jdbcTemplate.query(sql, rm, new Object[] { entityId, entityTypeId, CalendarType.COLLECTION.getValue() }); if (!result.isEmpty() && result.size() > 0) { return result.get(0); } return null; } @Override public Collection<CalendarData> retrieveParentCalendarsByEntity(final Long entityId, final Integer entityTypeId, final List<Integer> calendarTypeOptions) { final CalendarDataMapper rm = new CalendarDataMapper(); Collection<CalendarData> result = null; String sql = ""; final CalendarEntityType ceType = CalendarEntityType.fromInt(entityTypeId); final String parentHeirarchyCondition = getParentHierarchyCondition(ceType); // FIXME :AA center is the parent entity of group, change this code to // support more parent entity types. if (calendarTypeOptions == null || calendarTypeOptions.isEmpty()) { sql = rm.schema() + " " + parentHeirarchyCondition + " and ci.entity_type_enum = ? order by c.start_date "; result = this.jdbcTemplate.query(sql, rm, new Object[] { entityId, CalendarEntityType.CENTERS.getValue() }); } else { final String sqlCalendarTypeOptions = CalendarUtils.getSqlCalendarTypeOptionsInString(calendarTypeOptions); sql = rm.schema() + " " + parentHeirarchyCondition + " and ci.entity_type_enum = ? and c.calendar_type_enum in (" + sqlCalendarTypeOptions + ") order by c.start_date "; result = this.jdbcTemplate.query(sql, rm, new Object[] { entityId, CalendarEntityType.CENTERS.getValue() }); } return result; } @Override public Collection<CalendarData> retrieveAllCalendars() { final CalendarDataMapper rm = new CalendarDataMapper(); final String sql = rm.schema(); return this.jdbcTemplate.query(sql, rm); } @Override public CalendarData retrieveNewCalendarDetails() { return CalendarData.sensibleDefaultsForNewCalendarCreation(); } @Override public Collection<LocalDate> generateRecurringDates(final CalendarData calendarData, final boolean withHistory, final LocalDate tillDate) { final LocalDate fromDate = null; Collection<LocalDate> recurringDates = generateRecurringDate(calendarData, fromDate, tillDate, -1); if (withHistory) { final Collection<CalendarData> calendarHistorys = this.retrieveCalendarsFromHistory(calendarData.getId()); for (CalendarData calendarHistory : calendarHistorys) { recurringDates.addAll(generateRecurringDate(calendarHistory, fromDate, tillDate, -1)); } } return recurringDates; } @Override public Collection<LocalDate> generateNextTenRecurringDates(CalendarData calendarData) { final LocalDate tillDate = null; return generateRecurringDate(calendarData, DateUtils.getLocalDateOfTenant(), tillDate, 10); } private Collection<LocalDate> generateRecurringDate(final CalendarData calendarData, final LocalDate fromDate, final LocalDate tillDate, final int maxCount) { if (!calendarData.isRepeating()) { return null; } final String rrule = calendarData.getRecurrence(); /** * Start date or effective from date of calendar recurrence. */ final LocalDate seedDate = this.getSeedDate(calendarData.getStartDate()); /** * periodStartDate date onwards recurring dates will be generated. */ final LocalDate periodStartDate = this.getPeriodStartDate(seedDate, calendarData.getStartDate(), fromDate); /** * till periodEndDate recurring dates will be generated. */ final LocalDate periodEndDate = this.getPeriodEndDate(calendarData.getEndDate(), tillDate); final Collection<LocalDate> recurringDates = CalendarUtils.getRecurringDates(rrule, seedDate, periodStartDate, periodEndDate, maxCount); return recurringDates; } private LocalDate getSeedDate(LocalDate date) { return date; } private LocalDate getPeriodStartDate(final LocalDate seedDate, final LocalDate recurrenceStartDate, final LocalDate fromDate) { LocalDate periodStartDate = null; if (fromDate != null) { periodStartDate = fromDate; } else { final LocalDate currentDate = DateUtils.getLocalDateOfTenant(); if (seedDate.isBefore(currentDate.minusYears(1))) { periodStartDate = currentDate.minusYears(1); } else { periodStartDate = recurrenceStartDate; } } return periodStartDate; } private LocalDate getPeriodEndDate(LocalDate endDate, LocalDate tillDate) { LocalDate periodEndDate = endDate; final LocalDate currentDate = DateUtils.getLocalDateOfTenant(); if (tillDate != null) { if (endDate != null) { if (endDate.isAfter(tillDate)) { // to retrieve meeting dates tillspecified date (tillDate) periodEndDate = tillDate; } } else { // end date is null then fetch meeting dates tillDate periodEndDate = tillDate; } } else if (endDate == null || endDate.isAfter(currentDate.plusYears(1))) { periodEndDate = currentDate.plusYears(1); } return periodEndDate; } @Override public LocalDate generateNextEligibleMeetingDateForCollection(final CalendarData calendarData, final MeetingData lastMeetingData) { final LocalDate lastMeetingDate = (lastMeetingData == null) ? null : lastMeetingData.getMeetingDate(); // get applicable calendar based on meeting date CalendarData applicableCalendarData = calendarData; LocalDate nextEligibleMeetingDate = null; /** * The next available meeting date for collection should be taken from * application calendar for that time period. e.g. If the previous * calendar details has weekly meeting starting from 1st of Oct 2013 on * every Tuesday, then meeting dates for collection are 1,8,15,22,29.. * * If meeting schedule has changed from Tuesday to Friday with effective * from 15th of Oct (calendar update has made on 2nd of Oct) , then * application should allow to generate collection sheet on 8th of Oct * which is still on Tuesday and next collection sheet date should be on * 18th of Oct as per current calendar */ if (lastMeetingDate != null && !calendarData.isBetweenStartAndEndDate(lastMeetingDate) && !calendarData.isBetweenStartAndEndDate(DateUtils.getLocalDateOfTenant())) { applicableCalendarData = this.retrieveApplicableCalendarFromHistory(calendarData.getId(), lastMeetingDate); nextEligibleMeetingDate = CalendarUtils.getRecentEligibleMeetingDate(applicableCalendarData.getRecurrence(), lastMeetingDate); } /** * If nextEligibleMeetingDate is on or after current calendar startdate * then regenerate the nextEligible meeting date based on */ if (nextEligibleMeetingDate == null) { final LocalDate seedDate = (lastMeetingDate != null) ? lastMeetingDate : calendarData.getStartDate(); nextEligibleMeetingDate = CalendarUtils.getRecentEligibleMeetingDate(applicableCalendarData.getRecurrence(), seedDate); } else if (calendarData.isBetweenStartAndEndDate(nextEligibleMeetingDate)) { nextEligibleMeetingDate = CalendarUtils.getRecentEligibleMeetingDate(applicableCalendarData.getRecurrence(), calendarData.getStartDate()); } return nextEligibleMeetingDate; } @Override public Collection<CalendarData> updateWithRecurringDates(final Collection<CalendarData> calendarsData) { final Collection<CalendarData> recuCalendarsData = new ArrayList<>(); final boolean withHistory = true; final LocalDate tillDate = null; for (final CalendarData calendarData : calendarsData) { final Collection<LocalDate> recurringDates = this.generateRecurringDates(calendarData, withHistory, tillDate); final Collection<LocalDate> nextTenRecurringDates = this.generateNextTenRecurringDates(calendarData); final LocalDate recentEligibleMeetingDate = null; final CalendarData updatedCalendarData = CalendarData.withRecurringDates(calendarData, recurringDates, nextTenRecurringDates, recentEligibleMeetingDate); recuCalendarsData.add(updatedCalendarData); } return recuCalendarsData; } @Override public CalendarData retrieveLoanCalendar(final Long loanId) { final CalendarDataMapper rm = new CalendarDataMapper(); final String sql = rm.schema() + " and ci.entity_id = ? and ci.entity_type_enum = ? order by c.start_date "; CalendarData calendarData = null; final Collection<CalendarData> calendars = this.jdbcTemplate.query(sql, rm, new Object[] { loanId, CalendarEntityType.LOANS.getValue() }); if (!CollectionUtils.isEmpty(calendars)) { for (final CalendarData calendar : calendars) { calendarData = calendar; break;// Loans are associated with only one calendar } } return calendarData; } public static String getParentHierarchyCondition(final CalendarEntityType calendarEntityType) { String conditionSql = ""; switch (calendarEntityType) { case CLIENTS: // TODO : AA : do we need to propagate to top level parent in // hierarchy? conditionSql = " and ci.entity_id in (select gc.group_id from m_client c join m_group_client gc " + " on c.id=gc.client_id where c.id = ? ) "; break; case GROUPS: // TODO : AA: add parent hierarchy for groups conditionSql = " and ci.entity_id in (select g.parent_id from m_group g where g.id = ? ) "; break; case LOANS: // TODO : AA: do we need parent hierarchy calendars for loans? conditionSql = " and ci.entity_id = ? "; break; default: break; } return conditionSql; } private CalendarData retrieveApplicableCalendarFromHistory(Long calendarId, LocalDate compareDate) { try { final CalendarDataFromHistoryMapper rm = new CalendarDataFromHistoryMapper(); final String sql = rm.schema() + " where c.calendar_id = ? and date(?) between c.start_date and c.end_date limit 1"; return this.jdbcTemplate.queryForObject(sql, rm, new Object[] { calendarId, compareDate.toDate() }); } catch (final EmptyResultDataAccessException e) { return null; } } private Collection<CalendarData> retrieveCalendarsFromHistory(Long calendarId) { try { final CalendarDataFromHistoryMapper rm = new CalendarDataFromHistoryMapper(); final String sql = rm.schema() + " where c.calendar_id = ? "; final Collection<CalendarData> calendars = this.jdbcTemplate.query(sql, rm, new Object[] { calendarId }); return calendars; } catch (final EmptyResultDataAccessException e) { return null; } } private static final class CalendarDataFromHistoryMapper implements RowMapper<CalendarData> { public String schema() { return " select c.calendar_id as id, c.title as title, c.description as description, c.location as location, c.start_date as startDate, " + " c.end_date as endDate, c.duration as duration, c.calendar_type_enum as typeId, c.repeating as repeating, " + " c.recurrence as recurrence, c.remind_by_enum as remindById, c.first_reminder as firstReminder, c.second_reminder as secondReminder " + " from m_calendar_history c "; } @Override public CalendarData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException { final Long id = rs.getLong("id"); final Long calendarInstanceId = null; final Long entityId = null; final EnumOptionData entityType = null; final String title = rs.getString("title"); final String description = rs.getString("description"); final String location = rs.getString("location"); final LocalDate startDate = JdbcSupport.getLocalDate(rs, "startDate"); final LocalDate endDate = JdbcSupport.getLocalDate(rs, "endDate"); final Integer duration = rs.getInt("duration"); final Integer typeId = rs.getInt("typeId"); final EnumOptionData type = CalendarEnumerations.calendarType(typeId); final boolean repeating = rs.getBoolean("repeating"); final String recurrence = rs.getString("recurrence"); final EnumOptionData frequency = CalendarEnumerations.calendarFrequencyType(CalendarUtils.getFrequency(recurrence)); final Integer interval = new Integer(CalendarUtils.getInterval(recurrence)); final EnumOptionData repeatsOnDay = CalendarEnumerations.calendarWeekDaysType(CalendarUtils.getRepeatsOnDay(recurrence)); final Integer remindById = rs.getInt("remindById"); EnumOptionData remindBy = null; if (remindById != null && remindById != 0) { remindBy = CalendarEnumerations.calendarRemindBy(remindById); } final Integer firstReminder = rs.getInt("firstReminder"); final Integer secondReminder = rs.getInt("secondReminder"); String humanReadable = null; if (startDate != null && recurrence != null) { humanReadable = CalendarUtils.getRRuleReadable(startDate, recurrence); } final LocalDate createdDate = null; final LocalDate lastUpdatedDate = null; final Long createdByUserId = null; final String createdByUserName = null; final Long lastUpdatedByUserId = null; final String lastUpdatedByUserName = null; return CalendarData.instance(id, calendarInstanceId, entityId, entityType, title, description, location, startDate, endDate, duration, type, repeating, recurrence, frequency, interval, repeatsOnDay, remindBy, firstReminder, secondReminder, humanReadable, createdDate, lastUpdatedDate, createdByUserId, createdByUserName, lastUpdatedByUserId, lastUpdatedByUserName); } } }