MIFOSX-496: Capture client attendance details

This commit is contained in:
ashok-conflux
2013-08-14 19:21:20 +05:30
parent eee431d3b0
commit 28bbd361c2
60 changed files with 2056 additions and 146 deletions
@@ -0,0 +1,30 @@
CREATE TABLE `m_meeting` (
`id` BIGINT(20) NOT NULL AUTO_INCREMENT,
`calendar_instance_id` BIGINT(20) NOT NULL,
`meeting_date` DATE NOT NULL,
PRIMARY KEY (`id`),
UNIQUE INDEX `unique_calendar_instance_id_meeting_date` (`calendar_instance_id`, `meeting_date`),
CONSTRAINT `FK_m_calendar_instance_m_meeting` FOREIGN KEY (`calendar_instance_id`) REFERENCES `m_calendar_instance` (`id`)
)
COLLATE='latin1_swedish_ci'
ENGINE=InnoDB;
CREATE TABLE `m_client_attendance` (
`id` BIGINT(20) NOT NULL AUTO_INCREMENT,
`client_id` BIGINT(20) NOT NULL DEFAULT '0',
`meeting_id` BIGINT(20) NOT NULL,
`attendance_type_enum` SMALLINT(5) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE INDEX `unique_client_meeting_attendance` (`client_id`, `meeting_id`),
INDEX `FK_m_meeting_m_client_attendance` (`meeting_id`),
CONSTRAINT `FK_m_client_m_client_attendance` FOREIGN KEY (`client_id`) REFERENCES `m_client` (`id`),
CONSTRAINT `FK_m_meeting_m_client_attendance` FOREIGN KEY (`meeting_id`) REFERENCES `m_meeting` (`id`)
)
COLLATE='utf8_general_ci'
ENGINE=InnoDB;
INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ('portfolio', 'CREATE_MEETING', 'MEETING', 'CREATE', 0);
INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ('portfolio', 'UPDATE_MEETING', 'MEETING', 'UPDATE', 0);
INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ('portfolio', 'DELETE_MEETING', 'MEETING', 'DELETE', 0);
INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ('portfolio', 'SAVEORUPDATEATTENDANCE_MEETING', 'MEETING', 'SAVEORUPDATEATTENDANCE', 0);
@@ -571,4 +571,12 @@ public class CommandWrapper {
public boolean isAccountTransferResource() {
return this.entityName.equalsIgnoreCase("ACCOUNTTRANSFER");
}
public boolean isMeetingResource() {
return this.entityName.equalsIgnoreCase("MEETING");
}
public boolean isSaveOrUpdateAttendance(){
return this.actionName.equalsIgnoreCase("SAVEORUPDATEATTENDANCE");
}
}
@@ -1210,4 +1210,39 @@ public class CommandWrapperBuilder {
this.href = "/updateJobDetail/" + jobId + "/updateJobDetail";
return this;
}
public CommandWrapperBuilder createMeeting(final String supportedEntityType, final Long supportedEntityId) {
this.actionName = "CREATE";
this.entityName = "MEETING";
this.supportedEntityType = supportedEntityType;
this.supportedEntityId = supportedEntityId;
this.href = "/" + supportedEntityType + "/" + supportedEntityId + "/meetings";
return this;
}
public CommandWrapperBuilder updateMeeting(final String supportedEntityType, final Long supportedEntityId, final Long meetingId) {
this.actionName = "UPDATE";
this.entityName = "MEETING";
this.entityId = meetingId;
this.href = "/" + supportedEntityType + "/" + supportedEntityId + "/meetings/" + meetingId;
return this;
}
public CommandWrapperBuilder deleteMeeting(final String supportedEntityType, final Long supportedEntityId, final Long meetingId) {
this.actionName = "DELETE";
this.entityName = "MEETING";
this.entityId = meetingId;
this.href = "/" + supportedEntityType + "/" + supportedEntityId + "/meetings/" + meetingId;
return this;
}
public CommandWrapperBuilder saveOrUpdateAttendance(final Long entityId, final String supportedEntityType, final Long supportedEntityId) {
this.actionName = "SAVEORUPDATEATTENDANCE";
this.entityName = "MEETING";
this.entityId = entityId;
this.supportedEntityType = supportedEntityType;
this.supportedEntityId = supportedEntityId;
this.href = "/" + supportedEntityType + "/" + supportedEntityId + "/meetings/" + entityId + "?command=saveOrUpdateAttendance";
return this;
}
}
@@ -522,6 +522,18 @@ public class SynchronousCommandProcessingService implements CommandProcessingSer
} else {
throw new UnsupportedCommandException(wrapper.commandName());
}
} else if (wrapper.isMeetingResource()) {
if (wrapper.isCreate()) {
handler = this.applicationContext.getBean("createMeetingCommandHandler", NewCommandSourceHandler.class);
} else if (wrapper.isUpdate()) {
handler = this.applicationContext.getBean("updateMeetingCommandHandler", NewCommandSourceHandler.class);
} else if (wrapper.isDelete()) {
handler = this.applicationContext.getBean("deleteMeetingCommandHandler", NewCommandSourceHandler.class);
} else if (wrapper.isSaveOrUpdateAttendance()) {
handler = this.applicationContext.getBean("updateMeetingAttendanceCommandHandler", NewCommandSourceHandler.class);
} else {
throw new UnsupportedCommandException(wrapper.commandName());
}
} else {
throw new UnsupportedCommandException(wrapper.commandName());
}
@@ -9,7 +9,7 @@ package org.mifosplatform.organisation.workingdays.service;
import org.joda.time.LocalDate;
import org.mifosplatform.organisation.workingdays.domain.RepaymentRescheduleType;
import org.mifosplatform.organisation.workingdays.domain.WorkingDays;
import org.mifosplatform.portfolio.calendar.service.CalendarHelper;
import org.mifosplatform.portfolio.calendar.service.CalendarUtils;
public class WorkingDaysUtil {
@@ -38,6 +38,6 @@ public class WorkingDaysUtil {
}
public static boolean isWorkingDay(WorkingDays workingDays, final LocalDate date){
return CalendarHelper.isValidRedurringDate(workingDays.getRecurrence(), date, date);
return CalendarUtils.isValidRedurringDate(workingDays.getRecurrence(), date, date);
}
}
@@ -20,6 +20,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.CalendarUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
@@ -105,7 +106,7 @@ public class CalendarsApiResource {
Collection<CalendarData> calendarsData = new ArrayList<CalendarData>();
List<Integer> calendarTypeOptions = createIntegerListFromQueryParameter(calendarType);
List<Integer> calendarTypeOptions = CalendarUtils.createIntegerListFromQueryParameter(calendarType);
if (!associationParameters.isEmpty()) {
@@ -191,37 +192,4 @@ public class CalendarsApiResource {
return new CalendarData(calendarData, entityTypeOptions, calendarTypeOptions, remindByOptions);
}
public List<Integer> createIntegerListFromQueryParameter(String calendarTypeQuery) {
List<Integer> calendarTypeOptions = new ArrayList<Integer>();
// adding all calendar Types if query parameter is "all"
if(calendarTypeQuery.equalsIgnoreCase("all")){
calendarTypeOptions.add(1);
calendarTypeOptions.add(2);
calendarTypeOptions.add(3);
calendarTypeOptions.add(4);
return calendarTypeOptions;
}
// creating a list of calendar type options from the comma separated query parameter.
List<String> calendarTypeOptionsInQuery = new ArrayList<String>();
StringTokenizer st = new StringTokenizer(calendarTypeQuery, ",");
while (st.hasMoreElements()) {
calendarTypeOptionsInQuery.add(st.nextElement().toString());
}
for(String calType : calendarTypeOptionsInQuery){
if(calType.equalsIgnoreCase("collection")) {
calendarTypeOptions.add(1);
} else if(calType.equalsIgnoreCase("training")) {
calendarTypeOptions.add(2);
} else if(calType.equalsIgnoreCase("audit")) {
calendarTypeOptions.add(3);
} else if(calType.equalsIgnoreCase("general")) {
calendarTypeOptions.add(4);
}
}
return calendarTypeOptions;
}
}
@@ -23,7 +23,7 @@ import org.mifosplatform.infrastructure.core.domain.AbstractAuditableCustom;
import org.mifosplatform.portfolio.calendar.CalendarConstants.CALENDAR_SUPPORTED_PARAMETERS;
import org.mifosplatform.portfolio.calendar.exception.CalendarDateException;
import org.mifosplatform.portfolio.calendar.exception.CalendarParameterUpdateNotSupportedException;
import org.mifosplatform.portfolio.calendar.service.CalendarHelper;
import org.mifosplatform.portfolio.calendar.service.CalendarUtils;
import org.mifosplatform.useradministration.domain.AppUser;
@Entity
@@ -213,13 +213,13 @@ public class Calendar extends AbstractAuditableCustom<AppUser, Long> {
//FIXME: AA - Is this restriction required only for collection type meetings or for all?.
//Do not allow to change meeting frequency
if(!CalendarHelper.isFrequencySame(this.recurrence, newValue)){
if(!CalendarUtils.isFrequencySame(this.recurrence, newValue)){
final String defaultUserMessage = "Update of meeting frequency is not supported";
throw new CalendarParameterUpdateNotSupportedException("meeting.frequency", defaultUserMessage);
}
//Do not allow to change meeting interval
if(!CalendarHelper.isIntervalSame(this.recurrence, newValue)){
if(!CalendarUtils.isIntervalSame(this.recurrence, newValue)){
final String defaultUserMessage = "Update of meeting interval is not supported";
throw new CalendarParameterUpdateNotSupportedException("meeting.interval", defaultUserMessage);
}
@@ -66,4 +66,20 @@ public enum CalendarEntityType {
public String toString() {
return name().toString();
}
public static boolean isGroup(final Integer value) {
return CalendarEntityType.GROUPS.value.equals(value) ? true : false;
}
public static boolean isGroup(final String name) {
return CalendarEntityType.GROUPS.name().equalsIgnoreCase(name) ? true : false;
}
public static boolean isCenter(final Integer value) {
return CalendarEntityType.CENTERS.value.equals(value) ? true : false;
}
public static boolean isCenter(final String name) {
return CalendarEntityType.CENTERS.name().equalsIgnoreCase(name) ? true : false;
}
}
@@ -14,7 +14,7 @@ import org.springframework.data.repository.query.Param;
public interface CalendarInstanceRepository extends JpaRepository<CalendarInstance, Long>, JpaSpecificationExecutor<CalendarInstance> {
CalendarInstance findByCalendarAndEntityIdAndEntityTypeId(Long calendarId, Long entityId, Integer entityTypeId);
CalendarInstance findByCalendarIdAndEntityIdAndEntityTypeId(Long calendarId, Long entityId, Integer entityTypeId);
Collection<CalendarInstance> findByEntityIdAndEntityTypeId(Long entityId, Integer entityTypeId);
@@ -0,0 +1,51 @@
/**
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/.
*/
package org.mifosplatform.portfolio.calendar.domain;
import org.mifosplatform.portfolio.calendar.exception.CalendarInstanceNotFoundException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
/**
* <p>
* Wrapper for {@link CalendarInstanceRepository} that is responsible for checking
* if {@link CalendarInstance} is returned when using <code>findOne</code>
* repository method and throwing an appropriate not found exception.
* </p>
*
* <p>
* This is to avoid need for checking and throwing in multiple areas of code
* base where {@link CalendarInstanceRepository} is required.
* </p>
*/
@Service
public class CalendarInstanceRepositoryWrapper {
private final CalendarInstanceRepository repository;
@Autowired
public CalendarInstanceRepositoryWrapper(final CalendarInstanceRepository repository) {
this.repository = repository;
}
public CalendarInstance findOneWithNotFoundDetection(final Long CalendarInstanceId) {
final CalendarInstance calendatInstance = this.repository.findOne(CalendarInstanceId);
if (calendatInstance == null) { throw new CalendarInstanceNotFoundException(CalendarInstanceId); }
return calendatInstance;
}
public void save(final CalendarInstance calendatInstance) {
this.repository.save(calendatInstance);
}
public void delete(final CalendarInstance calendatInstance) {
this.repository.delete(calendatInstance);
}
public void saveAndFlush(final CalendarInstance calendatInstance) {
this.repository.saveAndFlush(calendatInstance);
}
}
@@ -0,0 +1,22 @@
/**
* 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.AbstractPlatformResourceNotFoundException;
/**
* A {@link RuntimeException} thrown when CalendarInstance resources are not found.
*/
public class CalendarInstanceNotFoundException extends AbstractPlatformResourceNotFoundException {
public CalendarInstanceNotFoundException(final Long id) {
super("error.msg.calendar.instance.id.invalid", "Calendar Instance with identifier " + id + " does not exist", id);
}
public CalendarInstanceNotFoundException(final String postFix, final String defaultUserMessage, final Object... defaultUserMessageArgs) {
super("error.msg.calendar.instance." + postFix, defaultUserMessage, defaultUserMessageArgs);
}
}
@@ -5,6 +5,7 @@
*/
package org.mifosplatform.portfolio.calendar.service;
import org.joda.time.LocalDate;
import org.mifosplatform.portfolio.calendar.data.CalendarData;
import java.util.Collection;
@@ -23,9 +24,13 @@ public interface CalendarReadPlatformService {
CalendarData retrieveNewCalendarDetails();
CalendarData generateRecurringDate(final CalendarData calendarData);
CalendarData generateRecurringDate(final CalendarData calendarData, final LocalDate tillDate);
Collection<CalendarData> generateRecurringDates(final Collection<CalendarData> calendarsData);
CalendarData retrieveLoanCalendar(final Long loanId);
CalendarData retrieveCollctionCalendarByEntity(final Long entityId, final Integer entityTypeId);
}
@@ -18,6 +18,7 @@ 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.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.EmptyResultDataAccessException;
@@ -71,7 +72,7 @@ public class CalendarReadPlatformServiceImpl implements CalendarReadPlatformServ
if (remindById != null && remindById != 0) remindBy = CalendarEnumerations.calendarRemindBy(remindById);
final Integer firstReminder = rs.getInt("firstReminder");
final Integer secondReminder = rs.getInt("secondReminder");
final String humanReadable = CalendarHelper.getRRuleReadable(startDate, recurrence);
final String humanReadable = CalendarUtils.getRRuleReadable(startDate, recurrence);
final LocalDate createdDate = JdbcSupport.getLocalDate(rs, "createdDate");
final LocalDate lastUpdatedDate = JdbcSupport.getLocalDate(rs, "updatedDate");
@@ -113,13 +114,27 @@ public class CalendarReadPlatformServiceImpl implements CalendarReadPlatformServ
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()) {
String sqlCalendarTypeOptions = getSqlCalendarTypeOptionsInString(calendarTypeOptions);
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();
String sql = rm.schema() + " and ci.entity_id = ? and ci.entity_type_enum = ? and calendar_type_enum = ? order by c.start_date ";
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,
@@ -137,7 +152,7 @@ public class CalendarReadPlatformServiceImpl implements CalendarReadPlatformServ
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 {
String sqlCalendarTypeOptions = getSqlCalendarTypeOptionsInString(calendarTypeOptions);
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() });
@@ -162,6 +177,11 @@ public class CalendarReadPlatformServiceImpl implements CalendarReadPlatformServ
@Override
public CalendarData generateRecurringDate(final CalendarData calendarData) {
return generateRecurringDate(calendarData, null);
}
@Override
public CalendarData generateRecurringDate(CalendarData calendarData, LocalDate tillDate) {
if (!calendarData.isRepeating()) return calendarData;
final String rrule = calendarData.getRecurrence();
final LocalDate currentDate = DateUtils.getLocalDateOfTenant();
@@ -172,13 +192,22 @@ public class CalendarReadPlatformServiceImpl implements CalendarReadPlatformServ
startDate = currentDate.minusYears(1);
}
if (endDate == null || endDate.isAfter(currentDate.plusYears(1))) {
if(tillDate != null){
if(endDate != null){
if(endDate.isAfter(tillDate)){
endDate = tillDate;//to retrieve meeting dates till specified date (tillDate)
}
}else{
endDate = tillDate;//end date is null then fetch meeting dates tillDate
}
}else if (endDate == null || endDate.isAfter(currentDate.plusYears(1))) {
endDate = currentDate.plusYears(1);
}
final Collection<LocalDate> recurringDates = CalendarHelper.getRecurringDates(rrule, seedDate, startDate, endDate, -1);
final Collection<LocalDate> nextTenRecurringDates = CalendarHelper.getRecurringDates(rrule, seedDate, endDate);
final Collection<LocalDate> recurringDates = CalendarUtils.getRecurringDates(rrule, seedDate, startDate, endDate, -1);
final Collection<LocalDate> nextTenRecurringDates = CalendarUtils.getRecurringDates(rrule, seedDate, endDate);
return new CalendarData(calendarData, recurringDates, nextTenRecurringDates);
}
@Override
@@ -238,21 +267,5 @@ public class CalendarReadPlatformServiceImpl implements CalendarReadPlatformServ
return conditionSql;
}
/**
* function returns a comma separated list of calendar_type_enum values ex.
* 1,2,3,4
*
* @param calendarTypeOptions
* @return
*/
public String getSqlCalendarTypeOptionsInString(final List<Integer> calendarTypeOptions) {
String sqlCalendarTypeOptions = "";
int size = calendarTypeOptions.size();
for (int i = 0; i < size - 1; i++) {
sqlCalendarTypeOptions += calendarTypeOptions.get(i).toString() + ",";
}
sqlCalendarTypeOptions += calendarTypeOptions.get(size - 1).toString();
return sqlCalendarTypeOptions;
}
}
@@ -11,6 +11,8 @@ import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.StringTokenizer;
import net.fortuna.ical4j.model.Date;
import net.fortuna.ical4j.model.DateList;
@@ -32,10 +34,10 @@ import org.mifosplatform.organisation.workingdays.service.WorkingDaysUtil;
import org.mifosplatform.portfolio.calendar.domain.Calendar;
import org.mifosplatform.portfolio.loanproduct.domain.PeriodFrequencyType;
public class CalendarHelper {
public class CalendarUtils {
public static LocalDate getNextRecurringDate(final String recurringRule, final LocalDate seedDate, final LocalDate startDate) {
final Recur recur = CalendarHelper.getICalRecur(recurringRule);
final Recur recur = CalendarUtils.getICalRecur(recurringRule);
if (recur == null) { return null; }
return getNextRecurringDate(recur, seedDate, startDate);
}
@@ -82,7 +84,7 @@ public class CalendarHelper {
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);
final Recur recur = CalendarUtils.getICalRecur(recurringRule);
return getRecurringDates(recur, seedDate, periodStartDate,
periodEndDate, maxCount);
@@ -210,7 +212,7 @@ public class CalendarHelper {
public static boolean isValidRedurringDate(final String recurringRule, final LocalDate seedDate, final LocalDate date){
final Recur recur = CalendarHelper.getICalRecur(recurringRule);
final Recur recur = CalendarUtils.getICalRecur(recurringRule);
if (recur == null) { return false; }
return isValidRecurringDate(recur, seedDate, date);
@@ -248,7 +250,7 @@ public class CalendarHelper {
}
public static PeriodFrequencyType getMeetingPeriodFrequencyType(final String recurringRule){
final Recur recur = CalendarHelper.getICalRecur(recurringRule);
final Recur recur = CalendarUtils.getICalRecur(recurringRule);
PeriodFrequencyType meetingFrequencyType = PeriodFrequencyType.INVALID;
if(recur.getFrequency().equals(Recur.DAILY)){
meetingFrequencyType = PeriodFrequencyType.DAYS;
@@ -277,12 +279,12 @@ public class CalendarHelper {
}
public static int getInterval(final String recurringRule){
final Recur recur = CalendarHelper.getICalRecur(recurringRule);
final Recur recur = CalendarUtils.getICalRecur(recurringRule);
return recur.getInterval();
}
public static LocalDate getFirstRepaymentMeetingDate(final Calendar calendar, final LocalDate disbursementDate, final Integer loanRepaymentInterval, final String frequency){
final Recur recur = CalendarHelper.getICalRecur(calendar.getRecurrence());
final Recur recur = CalendarUtils.getICalRecur(calendar.getRecurrence());
if (recur == null) { return null; }
LocalDate startDate = disbursementDate;
final LocalDate seedDate = calendar.getStartDateLocalDate();
@@ -310,7 +312,7 @@ public class CalendarHelper {
}
public static LocalDate getNewRepaymentMeetingDate(final String recurringRule, final LocalDate seedDate, final LocalDate oldRepaymentDate, final Integer loanRepaymentInterval, final String frequency, final WorkingDays workingDays){
final Recur recur = CalendarHelper.getICalRecur(recurringRule);
final Recur recur = CalendarUtils.getICalRecur(recurringRule);
if (recur == null) { return null; }
if(isValidRecurringDate(recur, seedDate, oldRepaymentDate)){
return oldRepaymentDate;
@@ -320,7 +322,7 @@ public class CalendarHelper {
public static LocalDate getNextRepaymentMeetingDate(final String recurringRule, final LocalDate seedDate, final LocalDate repaymentDate, final Integer loanRepaymentInterval, final String frequency, final WorkingDays workingDays){
final Recur recur = CalendarHelper.getICalRecur(recurringRule);
final Recur recur = CalendarUtils.getICalRecur(recurringRule);
if (recur == null) { return null; }
LocalDate tmpDate = repaymentDate;
if(isValidRecurringDate(recur, seedDate, repaymentDate)){
@@ -364,4 +366,53 @@ public class CalendarHelper {
if (oldRecur == null || oldRecur.getFrequency() == null || newRecur == null || newRecur.getFrequency() == null) { return false; }
return (oldRecur.getInterval() == newRecur.getInterval());
}
public static List<Integer> createIntegerListFromQueryParameter(String calendarTypeQuery) {
List<Integer> calendarTypeOptions = new ArrayList<Integer>();
// adding all calendar Types if query parameter is "all"
if(calendarTypeQuery.equalsIgnoreCase("all")){
calendarTypeOptions.add(1);
calendarTypeOptions.add(2);
calendarTypeOptions.add(3);
calendarTypeOptions.add(4);
return calendarTypeOptions;
}
// creating a list of calendar type options from the comma separated query parameter.
List<String> calendarTypeOptionsInQuery = new ArrayList<String>();
StringTokenizer st = new StringTokenizer(calendarTypeQuery, ",");
while (st.hasMoreElements()) {
calendarTypeOptionsInQuery.add(st.nextElement().toString());
}
for(String calType : calendarTypeOptionsInQuery){
if(calType.equalsIgnoreCase("collection")) {
calendarTypeOptions.add(1);
} else if(calType.equalsIgnoreCase("training")) {
calendarTypeOptions.add(2);
} else if(calType.equalsIgnoreCase("audit")) {
calendarTypeOptions.add(3);
} else if(calType.equalsIgnoreCase("general")) {
calendarTypeOptions.add(4);
}
}
return calendarTypeOptions;
}
/**
* function returns a comma separated list of calendar_type_enum values ex.
* 1,2,3,4
*
* @param calendarTypeOptions
* @return
*/
public static String getSqlCalendarTypeOptionsInString(final List<Integer> calendarTypeOptions) {
String sqlCalendarTypeOptions = "";
int size = calendarTypeOptions.size();
for (int i = 0; i < size - 1; i++) {
sqlCalendarTypeOptions += calendarTypeOptions.get(i).toString() + ",";
}
sqlCalendarTypeOptions += calendarTypeOptions.get(size - 1).toString();
return sqlCalendarTypeOptions;
}
}
@@ -128,7 +128,7 @@ public class CalendarWritePlatformServiceJpaRepositoryImpl implements CalendarWr
final Calendar calendarForUpdate = this.calendarRepository.findOne(calendarId);
if (calendarForUpdate == null) { throw new CalendarNotFoundException(calendarId); }
final CalendarInstance calendarInstanceForUpdate = this.calendarInstanceRepository.findByCalendarAndEntityIdAndEntityTypeId(calendarId, entityId, entityTypeId);
final CalendarInstance calendarInstanceForUpdate = this.calendarInstanceRepository.findByCalendarIdAndEntityIdAndEntityTypeId(calendarId, entityId, entityTypeId);
this.calendarInstanceRepository.saveAndFlush(calendarInstanceForUpdate);
return new CommandProcessingResultBuilder() //
.withCommandId(null) //
@@ -468,4 +468,13 @@ public final class Client extends AbstractPersistable<Long> {
public boolean isActivatedAfter(final LocalDate submittedOn) {
return getActivationLocalDate().isAfter(submittedOn);
}
public boolean isChildOfGroup(final Long groupId) {
if (groupId != null && this.groups != null && !this.groups.isEmpty()) {
for (Group group : this.groups) {
if (group.getId().equals(groupId)) return true;
}
}
return false;
}
}
@@ -9,5 +9,5 @@ import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
public interface ClientRepository extends JpaRepository<Client, Long>, JpaSpecificationExecutor<Client> {
// no added behaviour
// no added behaviour
}
@@ -28,6 +28,8 @@ public interface ClientReadPlatformService {
Collection<ClientData> retrieveClientMembersOfGroup(Long groupId);
Collection<ClientData> retrieveActiveClientMembersOfGroup(Long groupId);
Collection<ClientData> retrieveActiveClientMembersOfCenter(final Long centerId);
ClientData retrieveAllClosureReasons(String clientClosureReason);
}
@@ -298,6 +298,20 @@ public class ClientReadPlatformServiceImpl implements ClientReadPlatformService
}
}
@Override
public Collection<ClientData> retrieveActiveClientMembersOfCenter(final Long centerId) {
final AppUser currentUser = context.authenticatedUser();
final String hierarchy = currentUser.getOffice().getHierarchy();
final String hierarchySearchString = hierarchy + "%";
final String sql = "select " + this.membersOfGroupMapper.schema()
+ " left join m_group g on pgc.group_id=g.id where o.hierarchy like ? and g.parent_id = ? and c.status_enum = ? group by c.id";
return this.jdbcTemplate.query(sql, this.membersOfGroupMapper,
new Object[] { hierarchySearchString, centerId, ClientStatus.ACTIVE.getValue() });
}
private static final class ClientMapper implements RowMapper<ClientData> {
private final String schema;
@@ -216,9 +216,10 @@ public class CollectionSheetReadPlatformServiceImpl implements CollectionSheetRe
+ "JOIN m_client cl ON cl.id = gc.client_id "
+ "LEFT JOIN m_loan ln ON cl.id = ln.client_id AND ln.group_id is not null AND ( ln.loan_status_id = 300 OR ( ln.loan_status_id =200 AND ln.expected_disbursedon_date <= :dueDate )) "
+ "LEFT JOIN m_product_loan pl ON pl.id = ln.product_id "
+ "JOIN m_currency rc on rc.`code` = ln.currency_code "
+ "LEFT JOIN m_currency rc on rc.`code` = ln.currency_code "
+ "LEFT JOIN m_loan_repayment_schedule ls ON ls.loan_id = ln.id AND ls.completed_derived = 0 AND ls.duedate <= :dueDate "
+ "LEFT JOIN m_loan_charge lc ON lc.loan_id = ln.id AND lc.is_paid_derived = 0 AND ( lc.due_for_collection_as_of_date <= :dueDate OR lc.charge_time_enum = 1) ";
// + "WHERE gp.hierarchy like :hierarchy " +
// "GROUP BY gp.id ,cl.id , ln.id " +
// "ORDER BY gp.id , cl.id , ln.id ";
@@ -6,6 +6,7 @@
package org.mifosplatform.portfolio.group.api;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.Set;
@@ -38,9 +39,13 @@ import org.mifosplatform.infrastructure.core.service.Page;
import org.mifosplatform.infrastructure.security.service.PlatformSecurityContext;
import org.mifosplatform.portfolio.accountdetails.data.AccountSummaryCollectionData;
import org.mifosplatform.portfolio.accountdetails.service.AccountDetailsReadPlatformService;
import org.mifosplatform.portfolio.calendar.data.CalendarData;
import org.mifosplatform.portfolio.calendar.domain.CalendarEntityType;
import org.mifosplatform.portfolio.calendar.service.CalendarReadPlatformService;
import org.mifosplatform.portfolio.collectionsheet.data.JLGCollectionSheetData;
import org.mifosplatform.portfolio.collectionsheet.service.CollectionSheetReadPlatformService;
import org.mifosplatform.portfolio.group.data.CenterData;
import org.mifosplatform.portfolio.group.data.GroupGeneralData;
import org.mifosplatform.portfolio.group.service.CenterReadPlatformService;
import org.mifosplatform.portfolio.group.service.SearchParameters;
import org.springframework.beans.factory.annotation.Autowired;
@@ -64,6 +69,7 @@ public class CentersApiResource {
private final CollectionSheetReadPlatformService collectionSheetReadPlatformService;
private final FromJsonHelper fromJsonHelper;
private final AccountDetailsReadPlatformService accountDetailsReadPlatformService;
private final CalendarReadPlatformService calendarReadPlatformService;
@Autowired
public CentersApiResource(final PlatformSecurityContext context, final CenterReadPlatformService centerReadPlatformService,
@@ -72,7 +78,7 @@ public class CentersApiResource {
final ApiRequestParameterHelper apiRequestParameterHelper,
final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService,
final CollectionSheetReadPlatformService collectionSheetReadPlatformService, final FromJsonHelper fromJsonHelper,
final AccountDetailsReadPlatformService accountDetailsReadPlatformService) {
final AccountDetailsReadPlatformService accountDetailsReadPlatformService, final CalendarReadPlatformService calendarReadPlatformService) {
this.context = context;
this.centerReadPlatformService = centerReadPlatformService;
this.centerApiJsonSerializer = centerApiJsonSerializer;
@@ -83,6 +89,7 @@ public class CentersApiResource {
this.collectionSheetReadPlatformService = collectionSheetReadPlatformService;
this.fromJsonHelper = fromJsonHelper;
this.accountDetailsReadPlatformService = accountDetailsReadPlatformService;
this.calendarReadPlatformService = calendarReadPlatformService;
}
@GET
@@ -126,7 +133,8 @@ public class CentersApiResource {
this.context.authenticatedUser().validateHasReadPermission(GroupingTypesApiConstants.CENTER_RESOURCE_NAME);
final Set<String> associationParameters = ApiParameterHelper.extractAssociationsForResponseIfProvided(uriInfo.getQueryParameters());
CalendarData collectionMeetingCalendar = null;
Collection<GroupGeneralData> groups = null;
CenterData center = this.centerReadPlatformService.retrieveOne(centerId);
final boolean template = ApiParameterHelper.template(uriInfo.getQueryParameters());
@@ -137,9 +145,16 @@ public class CentersApiResource {
if (!associationParameters.isEmpty()) {
if (associationParameters.contains("groupMembers")) {
center = CenterData.setGroups(center, this.centerReadPlatformService.retrieveAssociatedGroups(centerId));
groups = this.centerReadPlatformService.retrieveAssociatedGroups(centerId);
}
if (associationParameters.contains("collectionMeetingCalendar")) {
collectionMeetingCalendar = this.calendarReadPlatformService.retrieveCollctionCalendarByEntity(centerId, CalendarEntityType.CENTERS.getValue());
}
center = CenterData.withAssociations(center, groups, collectionMeetingCalendar);
}
final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
return this.centerApiJsonSerializer.serialize(settings, center, GroupingTypesApiConstants.CENTER_RESPONSE_DATA_PARAMETERS);
@@ -52,6 +52,7 @@ public class GroupingTypesApiConstants {
public static final String officeOptionsParamName = "officeOptions";
public static final String staffOptionsParamName = "staffOptions";
public static final String clientOptionsParamName = "clientOptions";
public static final String collectionMeetingCalendar = "collectionMeetingCalendar";
public static final Set<String> CENTER_REQUEST_DATA_PARAMETERS = new HashSet<String>(Arrays.asList(localeParamName,
dateFormatParamName, idParamName, nameParamName, externalIdParamName, officeIdParamName, staffIdParamName, activeParamName,
@@ -59,7 +60,7 @@ public class GroupingTypesApiConstants {
public static final Set<String> GROUP_REQUEST_DATA_PARAMETERS = new HashSet<String>(Arrays.asList(localeParamName, dateFormatParamName,
idParamName, nameParamName, externalIdParamName, centerIdParamName, officeIdParamName, staffIdParamName, activeParamName,
activationDateParamName, clientMembersParamName));
activationDateParamName, clientMembersParamName, collectionMeetingCalendar));
public static final Set<String> GROUP_ROLES_REQUEST_DATA_PARAMETERS = new HashSet<String>(Arrays.asList(roleParamName, clientIdParamName));
@@ -8,6 +8,7 @@ package org.mifosplatform.portfolio.group.api;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import javax.ws.rs.Consumes;
@@ -40,6 +41,10 @@ import org.mifosplatform.infrastructure.core.service.Page;
import org.mifosplatform.infrastructure.security.service.PlatformSecurityContext;
import org.mifosplatform.portfolio.accountdetails.data.AccountSummaryCollectionData;
import org.mifosplatform.portfolio.accountdetails.service.AccountDetailsReadPlatformService;
import org.mifosplatform.portfolio.calendar.data.CalendarData;
import org.mifosplatform.portfolio.calendar.domain.CalendarEntityType;
import org.mifosplatform.portfolio.calendar.service.CalendarReadPlatformService;
import org.mifosplatform.portfolio.calendar.service.CalendarUtils;
import org.mifosplatform.portfolio.client.data.ClientData;
import org.mifosplatform.portfolio.client.service.ClientReadPlatformService;
import org.mifosplatform.portfolio.collectionsheet.data.JLGCollectionSheetData;
@@ -75,6 +80,7 @@ public class GroupsApiResource {
private final FromJsonHelper fromJsonHelper;
private final GroupRolesReadPlatformService groupRolesReadPlatformService;
private final AccountDetailsReadPlatformService accountDetailsReadPlatformService;
private final CalendarReadPlatformService calendarReadPlatformService;
@Autowired
public GroupsApiResource(final PlatformSecurityContext context, final GroupReadPlatformService groupReadPlatformService,
@@ -86,7 +92,8 @@ public class GroupsApiResource {
final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService,
final CollectionSheetReadPlatformService collectionSheetReadPlatformService, final FromJsonHelper fromJsonHelper,
final GroupRolesReadPlatformService groupRolesReadPlatformService,
final AccountDetailsReadPlatformService accountDetailsReadPlatformService) {
final AccountDetailsReadPlatformService accountDetailsReadPlatformService, final CalendarReadPlatformService calendarReadPlatformService) {
this.context = context;
this.groupReadPlatformService = groupReadPlatformService;
this.centerReadPlatformService = centerReadPlatformService;
@@ -100,6 +107,7 @@ public class GroupsApiResource {
this.fromJsonHelper = fromJsonHelper;
this.groupRolesReadPlatformService = groupRolesReadPlatformService;
this.accountDetailsReadPlatformService = accountDetailsReadPlatformService;
this.calendarReadPlatformService = calendarReadPlatformService;
}
@GET
@@ -162,9 +170,12 @@ public class GroupsApiResource {
Collection<ClientData> membersOfGroup = null;
Collection<GroupRoleData> groupRoles = null;
GroupRoleData selectedRole = null;
Collection<CalendarData> calendars = null;
CalendarData collectionMeetingCalendar = null;
if (!associationParameters.isEmpty()) {
if (associationParameters.contains("all")) {
associationParameters.addAll(Arrays.asList("clientMembers", "groupRoles"));
associationParameters.addAll(Arrays.asList("clientMembers", "groupRoles", "calendars", "collectionMeetingCalendar"));
}
if (associationParameters.contains("clientMembers")) {
membersOfGroup = this.clientReadPlatformService.retrieveClientMembersOfGroup(groupId);
@@ -174,25 +185,36 @@ public class GroupsApiResource {
}
if (associationParameters.contains("groupRoles")) {
groupRoles = this.groupRolesReadPlatformService.retrieveGroupRoles(groupId);
if (CollectionUtils.isEmpty(membersOfGroup)) {
if (CollectionUtils.isEmpty(groupRoles)) {
groupRoles = null;
}
}
if (roleId != null) {
selectedRole = this.groupRolesReadPlatformService.retrieveGroupRole(groupId, roleId);
if (selectedRole != null) {
group = GroupGeneralData.updateSelectedRole(group, selectedRole);
if (associationParameters.contains("calendars")) {
final List<Integer> calendarTypeOptions = CalendarUtils.createIntegerListFromQueryParameter("all");
calendars = this.calendarReadPlatformService.retrieveParentCalendarsByEntity(groupId, CalendarEntityType.GROUPS.getValue(), calendarTypeOptions);
if (CollectionUtils.isEmpty(calendars)) {
calendars = null;
}
}
if (associationParameters.contains("collectionMeetingCalendar")) {
collectionMeetingCalendar = this.calendarReadPlatformService.retrieveCollctionCalendarByEntity(groupId, CalendarEntityType.GROUPS.getValue());
}
group = GroupGeneralData.withAssocations(group, membersOfGroup, groupRoles, calendars, collectionMeetingCalendar);
}
if (roleId != null) {
selectedRole = this.groupRolesReadPlatformService.retrieveGroupRole(groupId, roleId);
if (selectedRole != null) {
group = GroupGeneralData.updateSelectedRole(group, selectedRole);
}
}
final boolean template = ApiParameterHelper.template(uriInfo.getQueryParameters());
if (template) {
final GroupGeneralData templateGroup = this.groupReadPlatformService.retrieveTemplate(group.officeId(), false,
staffInSelectedOfficeOnly);
group = GroupGeneralData.withTemplateAndAssociations(templateGroup, group, membersOfGroup, groupRoles);
} else {
group = GroupGeneralData.withAssocations(group, membersOfGroup, groupRoles);
group = GroupGeneralData.withTemplate(templateGroup, group);
}
final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
@@ -11,6 +11,7 @@ import org.joda.time.LocalDate;
import org.mifosplatform.infrastructure.core.data.EnumOptionData;
import org.mifosplatform.organisation.office.data.OfficeData;
import org.mifosplatform.organisation.staff.data.StaffData;
import org.mifosplatform.portfolio.calendar.data.CalendarData;
/**
* Immutable data object representing groups.
@@ -40,17 +41,19 @@ public class CenterData {
private final Collection<OfficeData> officeOptions;
private final Collection<StaffData> staffOptions;
private final Collection<GroupGeneralData> groupMembersOptions;
private final CalendarData collectionMeetingCalendar;
public static CenterData template(final Long officeId, final LocalDate activationDate, final Collection<OfficeData> officeOptions,
final Collection<StaffData> staffOptions, final Collection<GroupGeneralData> groupMembersOptions) {
final CalendarData collectionMeetingCalendar = null;
return new CenterData(null, null, null, null, activationDate, officeId, null, null, null, null, null, officeOptions, staffOptions,
groupMembersOptions);
groupMembersOptions, collectionMeetingCalendar);
}
public static CenterData withTemplate(final CenterData templateCenter, final CenterData center) {
return new CenterData(center.id, center.name, center.externalId, center.status, center.activationDate, center.officeId,
center.officeName, center.staffId, center.staffName, center.hierarchy, center.groupMembers, templateCenter.officeOptions,
templateCenter.staffOptions, templateCenter.groupMembersOptions);
templateCenter.staffOptions, templateCenter.groupMembersOptions, templateCenter.collectionMeetingCalendar);
}
public static CenterData instance(final Long id, final String name, final String externalId, final EnumOptionData status,
@@ -61,21 +64,22 @@ public class CenterData {
final Collection<OfficeData> officeOptions = null;
final Collection<StaffData> staffOptions = null;
final Collection<GroupGeneralData> groupMembersOptions = null;
final CalendarData collectionMeetingCalendar = null;
return new CenterData(id, name, externalId, status, activationDate, officeId, officeName, staffId, staffName, hierarchy,
groupMembers, officeOptions, staffOptions, groupMembersOptions);
groupMembers, officeOptions, staffOptions, groupMembersOptions, collectionMeetingCalendar);
}
public static CenterData setGroups(final CenterData centerData, final Collection<GroupGeneralData> groupMembers) {
public static CenterData withAssociations(final CenterData centerData, final Collection<GroupGeneralData> groupMembers, final CalendarData collectionMeetingCalendar) {
return new CenterData(centerData.id, centerData.name, centerData.externalId, centerData.status, centerData.activationDate,
centerData.officeId, centerData.officeName, centerData.staffId, centerData.staffName, centerData.hierarchy, groupMembers,
centerData.officeOptions, centerData.staffOptions, centerData.groupMembersOptions);
centerData.officeOptions, centerData.staffOptions, centerData.groupMembersOptions, collectionMeetingCalendar);
}
private CenterData(final Long id, final String name, final String externalId, final EnumOptionData status,
final LocalDate activationDate, final Long officeId, final String officeName, final Long staffId, final String staffName,
final String hierarchy, final Collection<GroupGeneralData> groupMembers, final Collection<OfficeData> officeOptions,
final Collection<StaffData> staffOptions, final Collection<GroupGeneralData> groupMembersOptions) {
final Collection<StaffData> staffOptions, final Collection<GroupGeneralData> groupMembersOptions, final CalendarData collectionMeetingCalendar) {
this.id = id;
this.name = name;
this.externalId = externalId;
@@ -98,6 +102,7 @@ public class CenterData {
this.officeOptions = officeOptions;
this.staffOptions = staffOptions;
this.groupMembersOptions = groupMembersOptions;
this.collectionMeetingCalendar = collectionMeetingCalendar;
}
public Long officeId() {
@@ -12,6 +12,7 @@ import org.mifosplatform.infrastructure.codes.data.CodeValueData;
import org.mifosplatform.infrastructure.core.data.EnumOptionData;
import org.mifosplatform.organisation.office.data.OfficeData;
import org.mifosplatform.organisation.staff.data.StaffData;
import org.mifosplatform.portfolio.calendar.data.CalendarData;
import org.mifosplatform.portfolio.client.data.ClientData;
/**
@@ -40,6 +41,8 @@ public class GroupGeneralData {
// associations
private final Collection<ClientData> clientMembers;
private final Collection<GroupRoleData> groupRoles;
private final Collection<CalendarData> calendarsData;
private final CalendarData collectionMeetingCalendar;
// template
private final Collection<CenterData> centerOptions;
@@ -54,7 +57,7 @@ public class GroupGeneralData {
final Collection<GroupRoleData> groupRoles = null;
return new GroupGeneralData(groupId, groupName, null, null, null, null, null, null, null, null, null, null, clientMembers, null,
null, null, null, groupRoles, null, null);
null, null, null, groupRoles, null, null, null, null);
}
public static GroupGeneralData template(final Long officeId, final Long centerId, final String centerName, final Long staffId,
@@ -66,7 +69,7 @@ public class GroupGeneralData {
final Collection<GroupRoleData> groupRoles = null;
return new GroupGeneralData(null, null, null, null, null, officeId, null, centerId, centerName, staffId, staffName, null,
clientMembers, centerOptions, officeOptions, staffOptions, clientOptions, groupRoles, availableRoles, null);
clientMembers, centerOptions, officeOptions, staffOptions, clientOptions, groupRoles, availableRoles, null, null, null);
}
public static GroupGeneralData withTemplate(final GroupGeneralData templatedGrouping, final GroupGeneralData grouping) {
@@ -74,24 +77,15 @@ public class GroupGeneralData {
grouping.officeId, grouping.officeName, grouping.centerId, grouping.centerName, grouping.staffId, grouping.staffName,
grouping.hierarchy, grouping.clientMembers, templatedGrouping.centerOptions, templatedGrouping.officeOptions,
templatedGrouping.staffOptions, templatedGrouping.clientOptions, grouping.groupRoles, templatedGrouping.availableRoles,
grouping.selectedRole);
}
public static GroupGeneralData withTemplateAndAssociations(final GroupGeneralData templatedGrouping, final GroupGeneralData grouping,
final Collection<ClientData> membersOfGroup, final Collection<GroupRoleData> groupRoles) {
return new GroupGeneralData(grouping.id, grouping.name, grouping.externalId, grouping.status, grouping.activationDate,
grouping.officeId, grouping.officeName, grouping.centerId, grouping.centerName, grouping.staffId, grouping.staffName,
grouping.hierarchy, membersOfGroup, templatedGrouping.centerOptions, templatedGrouping.officeOptions,
templatedGrouping.staffOptions, templatedGrouping.clientOptions, groupRoles, templatedGrouping.availableRoles,
grouping.selectedRole);
grouping.selectedRole, grouping.calendarsData, grouping.collectionMeetingCalendar);
}
public static GroupGeneralData withAssocations(final GroupGeneralData grouping, final Collection<ClientData> membersOfGroup,
final Collection<GroupRoleData> groupRoles) {
final Collection<GroupRoleData> groupRoles, final Collection<CalendarData> calendarsData, final CalendarData collectionMeetingCalendar) {
return new GroupGeneralData(grouping.id, grouping.name, grouping.externalId, grouping.status, grouping.activationDate,
grouping.officeId, grouping.officeName, grouping.centerId, grouping.centerName, grouping.staffId, grouping.staffName,
grouping.hierarchy, membersOfGroup, grouping.centerOptions, grouping.officeOptions, grouping.staffOptions,
grouping.clientOptions, groupRoles, grouping.availableRoles, grouping.selectedRole);
grouping.clientOptions, groupRoles, grouping.availableRoles, grouping.selectedRole, calendarsData, collectionMeetingCalendar);
}
public static GroupGeneralData instance(final Long id, final String name, final String externalId, final EnumOptionData status,
@@ -106,10 +100,12 @@ public class GroupGeneralData {
final Collection<GroupRoleData> groupRoles = null;
final Collection<CodeValueData> availableRoles = null;
final GroupRoleData role = null;
final Collection<CalendarData> calendarsData = null;
final CalendarData collectionMeetingCalendar = null;
return new GroupGeneralData(id, name, externalId, status, activationDate, officeId, officeName, centerId, centerName, staffId,
staffName, hierarchy, clientMembers, centerOptions, officeOptions, staffOptions, clientOptions, groupRoles, availableRoles,
role);
role, calendarsData, collectionMeetingCalendar);
}
private GroupGeneralData(final Long id, final String name, final String externalId, final EnumOptionData status,
@@ -117,7 +113,8 @@ public class GroupGeneralData {
final Long staffId, final String staffName, final String hierarchy, final Collection<ClientData> clientMembers,
final Collection<CenterData> centerOptions, final Collection<OfficeData> officeOptions,
final Collection<StaffData> staffOptions, final Collection<ClientData> clientOptions,
final Collection<GroupRoleData> groupRoles, final Collection<CodeValueData> availableRoles, final GroupRoleData role) {
final Collection<GroupRoleData> groupRoles, final Collection<CodeValueData> availableRoles, final GroupRoleData role,
final Collection<CalendarData> calendarsData, final CalendarData collectionMeetingCalendar) {
this.id = id;
this.name = name;
this.externalId = externalId;
@@ -152,6 +149,8 @@ public class GroupGeneralData {
this.groupRoles = groupRoles;
this.availableRoles = availableRoles;
this.selectedRole = role;
this.calendarsData = calendarsData;
this.collectionMeetingCalendar = collectionMeetingCalendar;
}
public Long getId() {
@@ -174,7 +173,7 @@ public class GroupGeneralData {
return new GroupGeneralData(grouping.id, grouping.name, grouping.externalId, grouping.status, grouping.activationDate,
grouping.officeId, grouping.officeName, grouping.centerId, grouping.centerName, grouping.staffId, grouping.staffName,
grouping.hierarchy, grouping.clientMembers, grouping.centerOptions, grouping.officeOptions, grouping.staffOptions,
grouping.clientOptions, grouping.groupRoles, grouping.availableRoles, selectedRole);
grouping.clientOptions, grouping.groupRoles, grouping.availableRoles, selectedRole, grouping.calendarsData, grouping.collectionMeetingCalendar);
}
}
@@ -384,4 +384,13 @@ public final class Group extends AbstractPersistable<Long> {
public boolean isCenter() {
return this.groupLevel.isCenter();
}
public boolean isChildClient(final Long clientId){
if (clientId != null && this.clientMembers != null && !this.clientMembers.isEmpty()) {
for (Client client : this.clientMembers) {
if(client.getId().equals(clientId)) return true;
}
}
return false;
}
}
@@ -5,9 +5,11 @@
*/
package org.mifosplatform.portfolio.group.domain;
import java.util.Collection;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
public interface GroupRepository extends JpaRepository<Group, Long>, JpaSpecificationExecutor<Group> {
Collection<Group> findByParentId(Long parentId);
}
@@ -10,8 +10,12 @@ import org.mifosplatform.infrastructure.core.exception.AbstractPlatformDomainRul
public class ClientNotInGroupException extends AbstractPlatformDomainRuleException {
public ClientNotInGroupException(final Long clientId, final Long groupId) {
super("error.msg.group.client.not.in.group", "Client with identifier " + clientId + " is not in Group with identifier " + groupId,
clientId, groupId);
this("group.client.not.in.group", "Client with identifier " + clientId + " is not in Group with identifier " + groupId, clientId, groupId);
}
public ClientNotInGroupException(String postFix, String defaultUserMessage, Object... defaultUserMessageArgs) {
super("error.msg." + postFix , defaultUserMessage, defaultUserMessageArgs);
}
}
@@ -56,7 +56,7 @@ import org.mifosplatform.organisation.staff.domain.Staff;
import org.mifosplatform.organisation.workingdays.domain.WorkingDays;
import org.mifosplatform.organisation.workingdays.service.WorkingDaysUtil;
import org.mifosplatform.portfolio.accountdetails.domain.AccountType;
import org.mifosplatform.portfolio.calendar.service.CalendarHelper;
import org.mifosplatform.portfolio.calendar.service.CalendarUtils;
import org.mifosplatform.portfolio.charge.exception.LoanChargeCannotBeAddedException;
import org.mifosplatform.portfolio.client.domain.Client;
import org.mifosplatform.portfolio.collateral.data.CollateralData;
@@ -2387,41 +2387,47 @@ public class Loan extends AbstractPersistable<Long> {
public void updateLoanRepaymentScheduleDates(final LocalDate meetingStartDate, final String recuringRule,
final boolean isHolidayEnabled, final List<Holiday> holidays, final WorkingDays workingDays) {
//update repayment dates of repayment schedule installaments
LocalDate tmpFromDate = this.getDisbursementDate();//first repayment's from date is same as disbursement date.
//first repayment's from date is same as disbursement date.
LocalDate tmpFromDate = this.getDisbursementDate();
final PeriodFrequencyType repaymentPeriodFrequencyType = this.loanRepaymentScheduleDetail.getRepaymentPeriodFrequencyType();
final Integer loanRepaymentInterval = this.loanRepaymentScheduleDetail.getRepayEvery();
final String frequency = CalendarHelper.getMeetingFrequencyFromPeriodFrequencyType(repaymentPeriodFrequencyType);
final String frequency = CalendarUtils.getMeetingFrequencyFromPeriodFrequencyType(repaymentPeriodFrequencyType);
LocalDate newRepaymentDate = null;
for (final LoanRepaymentScheduleInstallment loanRepaymentScheduleInstallment : this.repaymentScheduleInstallments) {
final LocalDate oldDueDate = loanRepaymentScheduleInstallment.getDueDate();
// FIXME: AA this won't update repayment dates before current date.
if (oldDueDate.isAfter(meetingStartDate) && oldDueDate.isAfter(DateUtils.getLocalDateOfTenant())) {
newRepaymentDate = CalendarHelper.getNewRepaymentMeetingDate(recuringRule, meetingStartDate, oldDueDate,
newRepaymentDate = CalendarUtils.getNewRepaymentMeetingDate(recuringRule, meetingStartDate, oldDueDate,
loanRepaymentInterval, frequency, workingDays);
final LocalDate maxDateLimitForNewRepayment = getMaxDateLimitForNewRepayment(repaymentPeriodFrequencyType,
loanRepaymentInterval, tmpFromDate);
if (newRepaymentDate.isAfter(maxDateLimitForNewRepayment)) {
newRepaymentDate = CalendarHelper.getNextRepaymentMeetingDate(recuringRule, meetingStartDate, tmpFromDate,
newRepaymentDate = CalendarUtils.getNextRepaymentMeetingDate(recuringRule, meetingStartDate, tmpFromDate,
loanRepaymentInterval, frequency, workingDays);
}
if (isHolidayEnabled) {
newRepaymentDate = HolidayUtil.getRepaymentRescheduleDateToIfHoliday(newRepaymentDate, holidays);
// reset repayment date with new meeting date
newRepaymentDate = CalendarUtils.getNewRepaymentMeetingDate(recuringRule, meetingStartDate, oldDueDate,
loanRepaymentInterval, frequency, workingDays);
if (isHolidayEnabled) {
newRepaymentDate = HolidayUtil.getRepaymentRescheduleDateToIfHoliday(newRepaymentDate, holidays);
}
loanRepaymentScheduleInstallment.updateDueDate(newRepaymentDate);
// reset from date to get actual daysInPeriod
loanRepaymentScheduleInstallment.updateFromDate(tmpFromDate);
tmpFromDate = newRepaymentDate;// update with new repayment
// date
} else {
tmpFromDate = oldDueDate;
}
loanRepaymentScheduleInstallment.updateDueDate(newRepaymentDate);
// reset from date to get actual daysInPeriod
loanRepaymentScheduleInstallment.updateFromDate(tmpFromDate);
tmpFromDate = newRepaymentDate;// update with new repayment date
} else {
tmpFromDate = oldDueDate;
}
}
}
@@ -28,7 +28,7 @@ import org.mifosplatform.portfolio.calendar.domain.Calendar;
import org.mifosplatform.portfolio.calendar.domain.CalendarRepository;
import org.mifosplatform.portfolio.calendar.exception.CalendarNotFoundException;
import org.mifosplatform.portfolio.calendar.exception.MeetingFrequencyMismatchException;
import org.mifosplatform.portfolio.calendar.service.CalendarHelper;
import org.mifosplatform.portfolio.calendar.service.CalendarUtils;
import org.mifosplatform.portfolio.client.domain.Client;
import org.mifosplatform.portfolio.client.domain.ClientRepositoryWrapper;
import org.mifosplatform.portfolio.group.domain.Group;
@@ -149,9 +149,9 @@ public class LoanScheduleAssembler {
if (calendar == null) { throw new CalendarNotFoundException(calendarId); }
// validate repayment frequency and interval with meeting frequency and interval
PeriodFrequencyType meetingPeriodFrequency = CalendarHelper.getMeetingPeriodFrequencyType(calendar.getRecurrence());
PeriodFrequencyType meetingPeriodFrequency = CalendarUtils.getMeetingPeriodFrequencyType(calendar.getRecurrence());
validateRepaymentFrequencyIsSameAsMeetingFrequency(meetingPeriodFrequency.getValue(), repaymentFrequencyType,
CalendarHelper.getInterval(calendar.getRecurrence()), repaymentEvery);
CalendarUtils.getInterval(calendar.getRecurrence()), repaymentEvery);
}
if (synchDisbursement != null && synchDisbursement.booleanValue()) {
@@ -166,8 +166,8 @@ public class LoanScheduleAssembler {
if (calculatedRepaymentsStartingFromDate == null) {
// FIXME: AA - Possibility of having next meeting date immediately after disbursement date,
// need to have minimum number of days gap between disbursement and first repayment date.
final String frequency = CalendarHelper.getMeetingFrequencyFromPeriodFrequencyType(repaymentPeriodFrequencyType);
calculatedRepaymentsStartingFromDate = CalendarHelper.getFirstRepaymentMeetingDate(calendar, expectedDisbursementDate,
final String frequency = CalendarUtils.getMeetingFrequencyFromPeriodFrequencyType(repaymentPeriodFrequencyType);
calculatedRepaymentsStartingFromDate = CalendarUtils.getFirstRepaymentMeetingDate(calendar, expectedDisbursementDate,
repaymentEvery, frequency);
} else {// validate user provided repaymentsStartFromDate
validateRepaymentsStartDateWithMeetingDates(repaymentsStartingFromDate, calendar);
@@ -192,7 +192,7 @@ public class LoanScheduleAssembler {
private void validateRepaymentsStartDateWithMeetingDates(final LocalDate repaymentsStartingFromDate, final Calendar calendar) {
if (repaymentsStartingFromDate != null
&& !CalendarHelper.isValidRedurringDate(calendar.getRecurrence(), calendar.getStartDateLocalDate(),
&& !CalendarUtils.isValidRedurringDate(calendar.getRecurrence(), calendar.getStartDateLocalDate(),
repaymentsStartingFromDate)) {
final String errorMessage = "First repayment date '" + repaymentsStartingFromDate + "' do not fall on a meeting date";
throw new LoanApplicationDateException("first.repayment.date.do.not.match.meeting.date", errorMessage,
@@ -202,7 +202,7 @@ public class LoanScheduleAssembler {
private void validateDisbursementDateWithMeetingDates(final LocalDate expectedDisbursementDate, final Calendar calendar) {
// disbursement date should fall on a meeting date
if (!CalendarHelper.isValidRedurringDate(calendar.getRecurrence(), calendar.getStartDateLocalDate(), expectedDisbursementDate)) {
if (!CalendarUtils.isValidRedurringDate(calendar.getRecurrence(), calendar.getStartDateLocalDate(), expectedDisbursementDate)) {
final String errorMessage = "Expected disbursement date '" + expectedDisbursementDate + "' do not fall on a meeting date";
throw new LoanApplicationDateException("disbursement.date.do.not.match.meeting.date", errorMessage, expectedDisbursementDate);
}
@@ -24,7 +24,7 @@ import org.mifosplatform.infrastructure.core.serialization.FromJsonHelper;
import org.mifosplatform.portfolio.calendar.domain.Calendar;
import org.mifosplatform.portfolio.calendar.domain.CalendarInstance;
import org.mifosplatform.portfolio.calendar.exception.NotValidRecurringDateException;
import org.mifosplatform.portfolio.calendar.service.CalendarHelper;
import org.mifosplatform.portfolio.calendar.service.CalendarUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@@ -76,7 +76,7 @@ public final class LoanEventApiJsonValidator {
Calendar calendar = calendarInstance.getCalendar();
if (calendar != null && actualDisbursementDate != null) {
// Disbursement date should fall on a meeting date
if (!CalendarHelper.isValidRedurringDate(calendar.getRecurrence(), calendar.getStartDateLocalDate(), actualDisbursementDate)) {
if (!CalendarUtils.isValidRedurringDate(calendar.getRecurrence(), calendar.getStartDateLocalDate(), actualDisbursementDate)) {
final String errorMessage = "Expected disbursement date '" + actualDisbursementDate.toString()
+ "' does not fall on a meeting date.";
throw new NotValidRecurringDateException("loan.actual.disbursement.date", errorMessage, actualDisbursementDate.toString(),
@@ -147,7 +147,7 @@ public final class LoanEventApiJsonValidator {
Calendar calendar = calendarInstance.getCalendar();
if (calendar != null && repaymentDate != null) {
// Disbursement date should fall on a meeting date
if (!CalendarHelper.isValidRedurringDate(calendar.getRecurrence(), calendar.getStartDateLocalDate(), repaymentDate)) {
if (!CalendarUtils.isValidRedurringDate(calendar.getRecurrence(), calendar.getStartDateLocalDate(), repaymentDate)) {
final String errorMessage = "Transaction date '" + repaymentDate.toString()
+ "' does not fall on a meeting date.";
throw new NotValidRecurringDateException("loan.transaction.date", errorMessage, repaymentDate.toString(),
@@ -41,6 +41,7 @@ import org.mifosplatform.portfolio.client.service.ClientReadPlatformService;
import org.mifosplatform.portfolio.fund.data.FundData;
import org.mifosplatform.portfolio.fund.service.FundReadPlatformService;
import org.mifosplatform.portfolio.group.data.GroupGeneralData;
import org.mifosplatform.portfolio.group.data.GroupRoleData;
import org.mifosplatform.portfolio.group.service.GroupReadPlatformService;
import org.mifosplatform.portfolio.group.service.SearchParameters;
import org.mifosplatform.portfolio.loanaccount.data.DisbursementData;
@@ -280,7 +281,10 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService {
// get group associations
Collection<ClientData> membersOfGroup = this.clientReadPlatformService.retrieveClientMembersOfGroup(groupId);
if (!CollectionUtils.isEmpty(membersOfGroup)) {
groupAccount = GroupGeneralData.withAssocations(groupAccount, membersOfGroup, null);
final Collection<CalendarData> calendarsData = null;
final CalendarData collectionMeetingCalendar = null;
final Collection<GroupRoleData> groupRoles = null;
groupAccount = GroupGeneralData.withAssocations(groupAccount, membersOfGroup, groupRoles, calendarsData, collectionMeetingCalendar);
}
final LocalDate expectedDisbursementDate = DateUtils.getLocalDateOfTenant();
@@ -958,7 +962,10 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService {
// get group associations
Collection<ClientData> membersOfGroup = this.clientReadPlatformService.retrieveActiveClientMembersOfGroup(groupId);
if (!CollectionUtils.isEmpty(membersOfGroup)) {
groupAccount = GroupGeneralData.withAssocations(groupAccount, membersOfGroup, null);
final Collection<CalendarData> calendarsData = null;
final CalendarData collectionMeetingCalendar = null;
final Collection<GroupRoleData> groupRoles = null;
groupAccount = GroupGeneralData.withAssocations(groupAccount, membersOfGroup, groupRoles, calendarsData, collectionMeetingCalendar);
}
return LoanAccountData.groupDefaults(groupAccount, expectedDisbursementDate);
@@ -39,7 +39,7 @@ 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.service.CalendarHelper;
import org.mifosplatform.portfolio.calendar.service.CalendarUtils;
import org.mifosplatform.portfolio.charge.domain.Charge;
import org.mifosplatform.portfolio.charge.domain.ChargeRepositoryWrapper;
import org.mifosplatform.portfolio.charge.exception.LoanChargeCannotBeDeletedException;
@@ -244,9 +244,9 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
if (repaymentScheduleDetails != null) {// Not expecting to be
// null
final Integer repayEvery = repaymentScheduleDetails.getRepayEvery();
final String frequency = CalendarHelper.getMeetingFrequencyFromPeriodFrequencyType(repaymentScheduleDetails
final String frequency = CalendarUtils.getMeetingFrequencyFromPeriodFrequencyType(repaymentScheduleDetails
.getRepaymentPeriodFrequencyType());
calculatedRepaymentsStartingFromDate = CalendarHelper.getFirstRepaymentMeetingDate(calendar, actualDisbursementDate, repayEvery,
calculatedRepaymentsStartingFromDate = CalendarUtils.getFirstRepaymentMeetingDate(calendar, actualDisbursementDate, repayEvery,
frequency);
}
}
@@ -0,0 +1,44 @@
/**
* 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.meeting;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
public class MeetingApiConstants {
public static final String MEETING_RESOURCE_NAME = "meeting";
// general
public static final String localeParamName = "locale";
public static final String dateFormatParamName = "dateFormat";
// meetings parameters
public static final String idParamName = "id";
public static final String meetingDateParamName = "meetingDate";
public static final String calendarIdParamName = "calendarId";
public static final String clientsAttendanceParamName = "clientsAttendance";
// attendance parameters
public static final String clientIdParamName = "clientId";
public static final String attendanceTypeParamName = "attendanceType";
// attendance response parameters
public static final String clientsAttendance = "clientsAttendance";
// template response parameters
public static final String clients = "clients";
public static final String calendarData = "calendarData";
public static final String attendanceTypeOptions = "attendanceTypeOptions";
public static final Set<String> MEETING_REQUEST_DATA_PARAMETERS = new HashSet<String>(Arrays.asList(meetingDateParamName,
localeParamName, dateFormatParamName, calendarIdParamName, clientsAttendanceParamName));
public static final Set<String> MEETING_RESPONSE_DATA_PARAMETERS = new HashSet<String>(Arrays.asList(idParamName, meetingDateParamName,
clientsAttendance, clients, calendarData, attendanceTypeOptions));
}
@@ -0,0 +1,221 @@
/**
* 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.meeting.api;
import static org.mifosplatform.portfolio.meeting.MeetingApiConstants.MEETING_RESOURCE_NAME;
import static org.mifosplatform.portfolio.meeting.MeetingApiConstants.MEETING_RESPONSE_DATA_PARAMETERS;
import java.util.Collection;
import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.UriInfo;
import org.apache.commons.lang.StringUtils;
import org.mifosplatform.commands.domain.CommandWrapper;
import org.mifosplatform.commands.service.CommandWrapperBuilder;
import org.mifosplatform.commands.service.PortfolioCommandSourceWritePlatformService;
import org.mifosplatform.infrastructure.core.api.ApiRequestParameterHelper;
import org.mifosplatform.infrastructure.core.data.CommandProcessingResult;
import org.mifosplatform.infrastructure.core.exception.UnrecognizedQueryParamException;
import org.mifosplatform.infrastructure.core.serialization.ApiRequestJsonSerializationSettings;
import org.mifosplatform.infrastructure.core.serialization.DefaultToApiJsonSerializer;
import org.mifosplatform.infrastructure.core.service.DateUtils;
import org.mifosplatform.infrastructure.security.service.PlatformSecurityContext;
import org.mifosplatform.portfolio.calendar.data.CalendarData;
import org.mifosplatform.portfolio.calendar.domain.CalendarEntityType;
import org.mifosplatform.portfolio.calendar.service.CalendarReadPlatformService;
import org.mifosplatform.portfolio.client.data.ClientData;
import org.mifosplatform.portfolio.client.service.ClientReadPlatformService;
import org.mifosplatform.portfolio.meeting.attendance.data.ClientAttendanceData;
import org.mifosplatform.portfolio.meeting.attendance.service.AttendanceDropdownReadPlatformService;
import org.mifosplatform.portfolio.meeting.attendance.service.ClientAttendanceReadPlatformService;
import org.mifosplatform.portfolio.meeting.data.MeetingData;
import org.mifosplatform.portfolio.meeting.exception.MeetingNotSupportedResourceException;
import org.mifosplatform.portfolio.meeting.service.MeetingReadPlatformService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
@Path("/{entityType}/{entityId}/meetings")
@Component
@Scope("singleton")
public class MeetingsApiResource {
private final PlatformSecurityContext context;
private final MeetingReadPlatformService readPlatformService;
private final ClientAttendanceReadPlatformService attendanceReadPlatformService;
private final ClientReadPlatformService clientReadPlatformService;
private final CalendarReadPlatformService calendarReadPlatformService;
private final AttendanceDropdownReadPlatformService attendanceDropdownReadPlatformService;
private final DefaultToApiJsonSerializer<MeetingData> toApiJsonSerializer;
private final ApiRequestParameterHelper apiRequestParameterHelper;
private final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService;
@Autowired
public MeetingsApiResource(PlatformSecurityContext context, MeetingReadPlatformService readPlatformService,
ClientAttendanceReadPlatformService attendanceReadPlatformService, final ClientReadPlatformService clientReadPlatformService,
final CalendarReadPlatformService calendarReadPlatformService,
final AttendanceDropdownReadPlatformService attendanceDropdownReadPlatformService,
DefaultToApiJsonSerializer<MeetingData> toApiJsonSerializer, ApiRequestParameterHelper apiRequestParameterHelper,
PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService) {
this.context = context;
this.readPlatformService = readPlatformService;
this.attendanceReadPlatformService = attendanceReadPlatformService;
this.clientReadPlatformService = clientReadPlatformService;
this.calendarReadPlatformService = calendarReadPlatformService;
this.attendanceDropdownReadPlatformService = attendanceDropdownReadPlatformService;
this.toApiJsonSerializer = toApiJsonSerializer;
this.apiRequestParameterHelper = apiRequestParameterHelper;
this.commandsSourceWritePlatformService = commandsSourceWritePlatformService;
}
@GET
@Path("template")
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
public String template(@PathParam("entityType") final String entityType, @PathParam("entityId") final Long entityId,
@QueryParam("calendarId") Long calendarId, @Context final UriInfo uriInfo) {
this.context.authenticatedUser().validateHasReadPermission(MEETING_RESOURCE_NAME);
final Integer entityTypeId = CalendarEntityType.valueOf(entityType.toUpperCase()).getValue();
Collection<ClientData> clients = null;
CalendarData calendarData = null;
if (CalendarEntityType.isGroup(entityType)) {
clients = this.clientReadPlatformService.retrieveActiveClientMembersOfGroup(entityId);
} else if (CalendarEntityType.isCenter(entityType)) {
clients = this.clientReadPlatformService.retrieveActiveClientMembersOfCenter(entityId);
} else {
final String defaultUserMessage = "Meeting attendance is not supported for the resource " + entityType
+ ". The supported resources are [" + CalendarEntityType.GROUPS.name() + ", " + CalendarEntityType.CENTERS.name() + "]";
throw new MeetingNotSupportedResourceException(defaultUserMessage, CalendarEntityType.GROUPS.name(), CalendarEntityType.CENTERS.name());
}
if (calendarId != null) {
calendarData = this.calendarReadPlatformService.retrieveCalendar(calendarId, entityId, entityTypeId);
calendarData = this.calendarReadPlatformService.generateRecurringDate(calendarData, DateUtils.getLocalDateOfTenant());
}
final MeetingData meetingData = MeetingData.template(clients, calendarData,
attendanceDropdownReadPlatformService.retrieveAttendanceTypeOptions());
final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
return this.toApiJsonSerializer.serialize(settings, meetingData, MEETING_RESPONSE_DATA_PARAMETERS);
}
@GET
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
public String retrieveMeetings(@PathParam("entityType") final String entityType, @PathParam("entityId") final Long entityId,
@QueryParam("limit") final Integer limit, @Context final UriInfo uriInfo) {
this.context.authenticatedUser().validateHasReadPermission(MEETING_RESOURCE_NAME);
final Collection<MeetingData> meetingsData = this.readPlatformService.retrieveMeetingsByEntity(entityId, CalendarEntityType
.valueOf(entityType.toUpperCase()).getValue(), limit);
final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
return this.toApiJsonSerializer.serialize(settings, meetingsData, MEETING_RESPONSE_DATA_PARAMETERS);
}
@GET
@Path("{meetingId}")
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
public String retrieveMeeting(@PathParam("meetingId") final Long meetingId, @PathParam("entityType") final String entityType,
@PathParam("entityId") final Long entityId, @Context final UriInfo uriInfo) {
this.context.authenticatedUser().validateHasReadPermission(MEETING_RESOURCE_NAME);
final Integer entityTypeId = CalendarEntityType.valueOf(entityType.toUpperCase()).getValue();
MeetingData meetingData = this.readPlatformService.retrieveMeeting(meetingId, entityId, entityTypeId);
final Collection<ClientAttendanceData> clientsAttendance = this.attendanceReadPlatformService
.retrieveClientAttendanceByMeetingId(meetingId);
meetingData = MeetingData.withClientsAttendanceAndAttendanceTypeOptions(meetingData, clientsAttendance,
attendanceDropdownReadPlatformService.retrieveAttendanceTypeOptions());
final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
return this.toApiJsonSerializer.serialize(settings, meetingData, MEETING_RESPONSE_DATA_PARAMETERS);
}
@POST
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
public String createMeeting(@PathParam("entityType") final String entityType, @PathParam("entityId") final Long entityId,
final String apiRequestBodyAsJson) {
final CommandWrapper commandRequest = new CommandWrapperBuilder().createMeeting(entityType, entityId)
.withJson(apiRequestBodyAsJson).build();
CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
return this.toApiJsonSerializer.serialize(result);
}
@POST
@Path("{meetingId}")
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
public String performMeetingCommands(@PathParam("entityType") final String entityType, @PathParam("entityId") final Long entityId,
@PathParam("meetingId") final Long meetingId, @QueryParam("command") final String commandParam,
final String apiRequestBodyAsJson) {
final CommandWrapperBuilder builder = new CommandWrapperBuilder().withJson(apiRequestBodyAsJson);
CommandProcessingResult result = null;
if (is(commandParam, "saveOrUpdateAttendance")) {
final CommandWrapper commandRequest = builder.saveOrUpdateAttendance(meetingId, entityType, entityId).build();
result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
} else {
throw new UnrecognizedQueryParamException("command", commandParam, new Object[] { "saveOrUpdateAttendance" });
}
return this.toApiJsonSerializer.serialize(result);
}
@PUT
@Path("{meetingId}")
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
public String updateMeeting(@PathParam("entityType") final String entityType, @PathParam("entityId") final Long entityId,
@PathParam("meetingId") final Long meetingId, final String jsonRequestBody) {
final CommandWrapper commandRequest = new CommandWrapperBuilder().updateMeeting(entityType, entityId, meetingId)
.withJson(jsonRequestBody).build();
final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
return this.toApiJsonSerializer.serialize(result);
}
@DELETE
@Path("{meetingId}")
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
public String deleteMeeting(@PathParam("entityType") final String entityType, @PathParam("entityId") final Long entityId,
@PathParam("meetingId") final Long meetingId) {
final CommandWrapper commandRequest = new CommandWrapperBuilder().deleteMeeting(entityType, entityId, meetingId).build();
final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
return this.toApiJsonSerializer.serialize(result);
}
private boolean is(final String commandParam, final String commandValue) {
return StringUtils.isNotBlank(commandParam) && commandParam.trim().equalsIgnoreCase(commandValue);
}
}
@@ -0,0 +1,63 @@
/**
* 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.meeting.attendance;
import org.mifosplatform.portfolio.meeting.attendance.domain.ClientAttendance;
/**
* An enumeration of {@link ClientAttendance} type.
*/
public enum AttendanceType {
INVALID(0, "attendanceType.invalid"), //
PRESENT(1, "attendanceType.present"), //
ABSENT(2, "attendanceType.absent"), //
APPROVED(3, "attendanceType.approved"), //
LEAVE(4, "attendanceType.leave"), //
LATE(5, "attendanceType.late");
private final Integer value;
private final String code;
private AttendanceType(final Integer value, final String code) {
this.value = value;
this.code = code;
}
public Integer getValue() {
return this.value;
}
public String getCode() {
return code;
}
public static AttendanceType fromInt(final Integer attendanceTypeId) {
if (attendanceTypeId == null) { return AttendanceType.INVALID; }
AttendanceType attendanceType = AttendanceType.INVALID;
switch (attendanceTypeId) {
case 1:
attendanceType = AttendanceType.PRESENT;
break;
case 2:
attendanceType = AttendanceType.ABSENT;
break;
case 3:
attendanceType = AttendanceType.APPROVED;
break;
case 4:
attendanceType = AttendanceType.LEAVE;
break;
case 5:
attendanceType = AttendanceType.LATE;
break;
}
return attendanceType;
}
}
@@ -0,0 +1,35 @@
/**
* 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.meeting.attendance.data;
import org.mifosplatform.infrastructure.core.data.EnumOptionData;
/**
* Immutable data object representing a ClientAttendance.
*/
public class ClientAttendanceData {
@SuppressWarnings("unused")
private final Long id;
@SuppressWarnings("unused")
private final Long clientId;
@SuppressWarnings("unused")
private final String clientName;
@SuppressWarnings("unused")
private final EnumOptionData attendanceType;
public static ClientAttendanceData instance(Long id, Long clientId, String clientName, EnumOptionData attendanceType){
return new ClientAttendanceData(id, clientId, clientName, attendanceType);
}
private ClientAttendanceData(Long id, Long clientId, String clientName, EnumOptionData attendanceType) {
this.id = id;
this.clientId = clientId;
this.clientName = clientName;
this.attendanceType = attendanceType;
}
}
@@ -0,0 +1,54 @@
package org.mifosplatform.portfolio.meeting.attendance.domain;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.Table;
import javax.persistence.UniqueConstraint;
import org.mifosplatform.portfolio.client.domain.Client;
import org.mifosplatform.portfolio.meeting.domain.Meeting;
import org.springframework.data.jpa.domain.AbstractPersistable;
@Entity
@Table(name = "m_client_attendance", uniqueConstraints = { @UniqueConstraint(columnNames = { "client_id", "meeting_id" }, name = "unique_client_meeting_attendance") })
public class ClientAttendance extends AbstractPersistable<Long> {
@ManyToOne
@JoinColumn(name = "client_id", nullable = false)
private Client client;
@ManyToOne
@JoinColumn(name = "meeting_id", nullable = false)
private Meeting meeting;
@Column(name = "attendance_type_enum", nullable = false)
private Integer attendanceTypeId;
protected ClientAttendance() {
}
public static ClientAttendance createClientAttendance(final Client client, final Meeting meeting, final Integer attendanceTypeId) {
return new ClientAttendance(client, meeting, attendanceTypeId);
}
private ClientAttendance(final Client client, final Meeting meeting, final Integer attendanceTypeId) {
this.client = client;
this.meeting = meeting;
this.attendanceTypeId = attendanceTypeId;
}
public Long clientId() {
return client.getId();
}
public void updateAttendanceTypeId(final Integer attendanceTypeId) {
this.attendanceTypeId = attendanceTypeId;
}
public Integer getAttendanceTypeId() {
return this.attendanceTypeId;
}
}
@@ -0,0 +1,13 @@
/**
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/.
*/
package org.mifosplatform.portfolio.meeting.attendance.domain;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
public interface ClientAttendanceRepository extends JpaRepository<ClientAttendance, Long>, JpaSpecificationExecutor<ClientAttendance> {
}
@@ -0,0 +1,14 @@
/**
* 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.meeting.attendance.service;
import java.util.List;
import org.mifosplatform.infrastructure.core.data.EnumOptionData;
public interface AttendanceDropdownReadPlatformService {
List<EnumOptionData> retrieveAttendanceTypeOptions();
}
@@ -0,0 +1,22 @@
/**
* 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.meeting.attendance.service;
import java.util.List;
import org.mifosplatform.infrastructure.core.data.EnumOptionData;
import org.mifosplatform.portfolio.meeting.attendance.AttendanceType;
import org.springframework.stereotype.Service;
@Service
public class AttendanceDropdownReadPlatformServiceImpl implements AttendanceDropdownReadPlatformService {
@Override
public List<EnumOptionData> retrieveAttendanceTypeOptions() {
return AttendanceEnumerations.attendanceType(AttendanceType.values());
}
}
@@ -0,0 +1,57 @@
/**
* 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.meeting.attendance.service;
import java.util.ArrayList;
import java.util.List;
import org.mifosplatform.infrastructure.core.data.EnumOptionData;
import org.mifosplatform.portfolio.meeting.attendance.AttendanceType;
public class AttendanceEnumerations {
public static EnumOptionData attendanceType(final int attendanceType) {
return attendanceType(AttendanceType.fromInt(attendanceType));
}
public static EnumOptionData attendanceType(final AttendanceType attendanceType) {
EnumOptionData optionData = new EnumOptionData(AttendanceType.INVALID.getValue().longValue(), AttendanceType.INVALID.getCode(),
"Invalid");
switch (attendanceType) {
case INVALID:
optionData = new EnumOptionData(AttendanceType.INVALID.getValue().longValue(), AttendanceType.INVALID.getCode(), "Invalid");
break;
case PRESENT:
optionData = new EnumOptionData(AttendanceType.PRESENT.getValue().longValue(), AttendanceType.PRESENT.getCode(), "Present");
break;
case ABSENT:
optionData = new EnumOptionData(AttendanceType.ABSENT.getValue().longValue(), AttendanceType.ABSENT.getCode(), "Absent");
break;
case APPROVED:
optionData = new EnumOptionData(AttendanceType.APPROVED.getValue().longValue(), AttendanceType.APPROVED.getCode(),
"Approved");
break;
case LEAVE:
optionData = new EnumOptionData(AttendanceType.LEAVE.getValue().longValue(), AttendanceType.LEAVE.getCode(), "Leave");
break;
case LATE:
optionData = new EnumOptionData(AttendanceType.LATE.getValue().longValue(), AttendanceType.LATE.getCode(), "Late");
break;
}
return optionData;
}
public static List<EnumOptionData> attendanceType(final AttendanceType[] attendanceTypes) {
final List<EnumOptionData> optionDatas = new ArrayList<EnumOptionData>();
for (final AttendanceType attendanceType : attendanceTypes) {
if(attendanceType.getValue().equals(AttendanceType.INVALID.getValue())) continue;
optionDatas.add(attendanceType(attendanceType));
}
return optionDatas;
}
}
@@ -0,0 +1,14 @@
/**
* 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.meeting.attendance.service;
import java.util.Collection;
import org.mifosplatform.portfolio.meeting.attendance.data.ClientAttendanceData;
public interface ClientAttendanceReadPlatformService {
Collection<ClientAttendanceData> retrieveClientAttendanceByMeetingId(final Long meetingId);
}
@@ -0,0 +1,59 @@
/**
* 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.meeting.attendance.service;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Collection;
import org.mifosplatform.infrastructure.core.data.EnumOptionData;
import org.mifosplatform.infrastructure.core.service.RoutingDataSource;
import org.mifosplatform.portfolio.meeting.attendance.data.ClientAttendanceData;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.stereotype.Service;
@Service
public class ClientAttendanceReadPlatformServiceImpl implements ClientAttendanceReadPlatformService {
private final JdbcTemplate jdbcTemplate;
@Autowired
public ClientAttendanceReadPlatformServiceImpl(final RoutingDataSource dataSource) {
this.jdbcTemplate = new JdbcTemplate(dataSource);
}
private static final class ClientAttendanceDataMapper implements RowMapper<ClientAttendanceData> {
public String schema() {
return " select ca.id as id, ca.client_id as clientId, ca.attendance_type_enum as attendanceTypeId, " +
" c.display_name as clientName from m_meeting m inner join m_client_attendance ca on m.id = ca.meeting_id " +
" inner join m_client c on ca.client_id=c.id ";
}
@Override
public ClientAttendanceData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException {
final Long id = rs.getLong("id");
final Long clientId = rs.getLong("clientId");
final Integer attendanceTypeId = rs.getInt("attendanceTypeId");
final String clientName = rs.getString("clientName");
final EnumOptionData attendanceType = AttendanceEnumerations.attendanceType(attendanceTypeId);
return ClientAttendanceData.instance(id, clientId, clientName, attendanceType);
}
}
@Override
public Collection<ClientAttendanceData> retrieveClientAttendanceByMeetingId(final Long meetingId) {
final ClientAttendanceDataMapper rm = new ClientAttendanceDataMapper();
final String sql = rm.schema() + " where m.id = ? ";
return this.jdbcTemplate.query(sql, rm, new Object[] { meetingId });
}
}
@@ -0,0 +1,69 @@
/**
* 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.meeting.data;
import java.util.Collection;
import java.util.List;
import org.joda.time.LocalDate;
import org.mifosplatform.infrastructure.core.data.EnumOptionData;
import org.mifosplatform.portfolio.calendar.data.CalendarData;
import org.mifosplatform.portfolio.client.data.ClientData;
import org.mifosplatform.portfolio.meeting.attendance.data.ClientAttendanceData;
/**
* Immutable data object representing a Meeting.
*/
public class MeetingData {
private final Long id;
private final LocalDate meetingDate;
private final Collection<ClientAttendanceData> clientsAttendance;
// template data
private final Collection<ClientData> clients;
private final CalendarData calendarData;
@SuppressWarnings("unused")
private final List<EnumOptionData> attendanceTypeOptions;
public static MeetingData instance(final Long id, final LocalDate meetingDate) {
final Collection<ClientAttendanceData> clientsAttendance = null;
final Collection<ClientData> clients = null;
final CalendarData calendarData = null;
final List<EnumOptionData> attendanceTypeOptions = null;
return new MeetingData(id, meetingDate, clientsAttendance, clients, calendarData, attendanceTypeOptions);
}
public static MeetingData withClientsAttendanceAndAttendanceTypeOptions(final MeetingData meetingData,
final Collection<ClientAttendanceData> clientsAttendance, final List<EnumOptionData> attendanceTypesOptions) {
return new MeetingData(meetingData.id, meetingData.meetingDate, clientsAttendance, meetingData.clients, meetingData.calendarData,
attendanceTypesOptions);
}
public static MeetingData template(final Collection<ClientData> clients, final CalendarData calendarData,
final List<EnumOptionData> attendanceTypeOptions) {
final Long id = null;
final LocalDate meetingDate = null;
final Collection<ClientAttendanceData> clientsAttendance = null;
return new MeetingData(id, meetingDate, clientsAttendance, clients, calendarData, attendanceTypeOptions);
}
public static MeetingData withAttendanceTypeOptions(final MeetingData meetingData, final List<EnumOptionData> attendanceTypeOptions) {
return new MeetingData(meetingData.id, meetingData.meetingDate, meetingData.clientsAttendance, meetingData.clients,
meetingData.calendarData, attendanceTypeOptions);
}
private MeetingData(Long id, LocalDate meetingDate, Collection<ClientAttendanceData> clientsAttendance,
final Collection<ClientData> clients, final CalendarData calendarData, final List<EnumOptionData> attendanceTypeOptions) {
this.id = id;
this.meetingDate = meetingDate;
this.clientsAttendance = clientsAttendance;
this.clients = clients;
this.calendarData = calendarData;
this.attendanceTypeOptions = attendanceTypeOptions;
}
}
@@ -0,0 +1,144 @@
/**
* 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.meeting.data;
import static org.mifosplatform.portfolio.meeting.MeetingApiConstants.MEETING_REQUEST_DATA_PARAMETERS;
import static org.mifosplatform.portfolio.meeting.MeetingApiConstants.MEETING_RESOURCE_NAME;
import static org.mifosplatform.portfolio.meeting.MeetingApiConstants.clientsAttendanceParamName;
import static org.mifosplatform.portfolio.meeting.MeetingApiConstants.attendanceTypeParamName;
import static org.mifosplatform.portfolio.meeting.MeetingApiConstants.calendarIdParamName;
import static org.mifosplatform.portfolio.meeting.MeetingApiConstants.clientIdParamName;
import static org.mifosplatform.portfolio.meeting.MeetingApiConstants.meetingDateParamName;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import org.apache.commons.lang.StringUtils;
import org.joda.time.LocalDate;
import org.mifosplatform.infrastructure.core.api.JsonCommand;
import org.mifosplatform.infrastructure.core.data.ApiParameterError;
import org.mifosplatform.infrastructure.core.data.DataValidatorBuilder;
import org.mifosplatform.infrastructure.core.exception.InvalidJsonException;
import org.mifosplatform.infrastructure.core.exception.PlatformApiDataValidationException;
import org.mifosplatform.infrastructure.core.serialization.FromJsonHelper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.reflect.TypeToken;
@Component
public class MeetingDataValidator {
private final FromJsonHelper fromApiJsonHelper;
@Autowired
public MeetingDataValidator(final FromJsonHelper fromApiJsonHelper) {
this.fromApiJsonHelper = fromApiJsonHelper;
}
public void validateForCreate(final JsonCommand command) {
final String json = command.json();
if (StringUtils.isBlank(json)) { throw new InvalidJsonException(); }
final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, MEETING_REQUEST_DATA_PARAMETERS);
final List<ApiParameterError> dataValidationErrors = new ArrayList<ApiParameterError>();
final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors)
.resource(MEETING_RESOURCE_NAME);
final JsonElement element = fromApiJsonHelper.parse(json);
final String meetingDateStr = this.fromApiJsonHelper.extractStringNamed(meetingDateParamName, element);
baseDataValidator.reset().parameter(meetingDateParamName).value(meetingDateStr).notBlank();
if(!StringUtils.isBlank(meetingDateStr)){
final LocalDate meetingDate = this.fromApiJsonHelper.extractLocalDateNamed(meetingDateParamName, element);
baseDataValidator.reset().parameter(meetingDateParamName).value(meetingDate).notNull();
}
final Long calendarId = this.fromApiJsonHelper.extractLongNamed(calendarIdParamName, element);
baseDataValidator.reset().parameter(calendarIdParamName).value(calendarId).notNull();
validateAttendanceDetails(element, baseDataValidator);
throwExceptionIfValidationWarningsExist(dataValidationErrors);
}
public void validateForUpdate(final JsonCommand command) {
final String json = command.json();
if (StringUtils.isBlank(json)) { throw new InvalidJsonException(); }
final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, MEETING_REQUEST_DATA_PARAMETERS);
final List<ApiParameterError> dataValidationErrors = new ArrayList<ApiParameterError>();
final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors)
.resource(MEETING_RESOURCE_NAME);
final JsonElement element = fromApiJsonHelper.parse(json);
if (this.fromApiJsonHelper.parameterExists(meetingDateParamName, element)) {
final String meetingDateStr = this.fromApiJsonHelper.extractStringNamed(meetingDateParamName, element);
baseDataValidator.reset().parameter(meetingDateParamName).value(meetingDateStr).notBlank();
if(!StringUtils.isBlank(meetingDateStr)){
final LocalDate meetingDate = this.fromApiJsonHelper.extractLocalDateNamed(meetingDateParamName, element);
baseDataValidator.reset().parameter(meetingDateParamName).value(meetingDate).notNull();
}
}
if(this.fromApiJsonHelper.parameterExists(calendarIdParamName, element)){
final Long calendarId = this.fromApiJsonHelper.extractLongNamed(calendarIdParamName, element);
baseDataValidator.reset().parameter(calendarIdParamName).value(calendarId).notNull();
}
validateAttendanceDetails(element, baseDataValidator);
throwExceptionIfValidationWarningsExist(dataValidationErrors);
}
public void validateForUpdateAttendance(final JsonCommand command) {
final String json = command.json();
if (StringUtils.isBlank(json)) { throw new InvalidJsonException(); }
final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, MEETING_REQUEST_DATA_PARAMETERS);
final List<ApiParameterError> dataValidationErrors = new ArrayList<ApiParameterError>();
final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors)
.resource(MEETING_RESOURCE_NAME);
final JsonElement element = fromApiJsonHelper.parse(json);
validateAttendanceDetails(element, baseDataValidator);
throwExceptionIfValidationWarningsExist(dataValidationErrors);
}
private void throwExceptionIfValidationWarningsExist(final List<ApiParameterError> dataValidationErrors) {
if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
}
private void validateAttendanceDetails(final JsonElement element, final DataValidatorBuilder baseDataValidator){
final JsonObject topLevelJsonElement = element.getAsJsonObject();
if (element.isJsonObject()) {
if (topLevelJsonElement.has(clientsAttendanceParamName)
&& topLevelJsonElement.get(clientsAttendanceParamName).isJsonArray()) {
final JsonArray array = topLevelJsonElement.get(clientsAttendanceParamName).getAsJsonArray();
for (int i = 0; i < array.size(); i++) {
final JsonObject attendanceElement = array.get(i).getAsJsonObject();
final Long clientId = this.fromApiJsonHelper.extractLongNamed(clientIdParamName, attendanceElement);
final Long attendanceType = this.fromApiJsonHelper.extractLongNamed(attendanceTypeParamName, attendanceElement);
baseDataValidator.reset().parameter(clientsAttendanceParamName + "[" + i + "]." + clientIdParamName).value(clientId).notNull().integerGreaterThanZero();
baseDataValidator.reset().parameter(clientsAttendanceParamName + "[" + i + "]." + attendanceTypeParamName).value(attendanceType).notNull().integerGreaterThanZero();
}
}
}
}
}
@@ -0,0 +1,188 @@
/**
* 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.meeting.domain;
import static org.mifosplatform.portfolio.meeting.MeetingApiConstants.clientsAttendanceParamName;
import static org.mifosplatform.portfolio.meeting.MeetingApiConstants.attendanceTypeParamName;
import static org.mifosplatform.portfolio.meeting.MeetingApiConstants.clientIdParamName;
import static org.mifosplatform.portfolio.meeting.MeetingApiConstants.meetingDateParamName;
import java.util.Collection;
import java.util.Date;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.OneToMany;
import javax.persistence.Table;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;
import javax.persistence.UniqueConstraint;
import org.joda.time.LocalDate;
import org.mifosplatform.infrastructure.core.api.JsonCommand;
import org.mifosplatform.infrastructure.core.service.DateUtils;
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.exception.NotValidRecurringDateException;
import org.mifosplatform.portfolio.calendar.service.CalendarUtils;
import org.mifosplatform.portfolio.meeting.attendance.domain.ClientAttendance;
import org.mifosplatform.portfolio.meeting.exception.MeetingDateException;
import org.springframework.data.jpa.domain.AbstractPersistable;
@Entity
@Table(name = "m_meeting", uniqueConstraints = { @UniqueConstraint(columnNames = { "calendar_instance_id", "meeting_date" }, name = "unique_calendar_instance_id_meeting_date") })
public class Meeting extends AbstractPersistable<Long> {
@ManyToOne
@JoinColumn(name = "calendar_instance_id", nullable = false)
private CalendarInstance calendarInstance;
@Column(name = "meeting_date", nullable = false)
@Temporal(TemporalType.DATE)
private Date meetingDate;
@OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL, mappedBy = "meeting", orphanRemoval = true)
private Set<ClientAttendance> clientsAttendance;
protected Meeting() {
//
}
private Meeting(CalendarInstance calendarInstance, Date meetingDate) {
this.calendarInstance = calendarInstance;
this.meetingDate = meetingDate;
}
public static Meeting createNew(final CalendarInstance calendarInstance, final Date meetingDate) {
if(!isValidMeetingDate(calendarInstance, meetingDate)){
throw new NotValidRecurringDateException("meeting", "The date '" + meetingDate + "' is not a valid meeting date.", meetingDate);
}
return new Meeting(calendarInstance, meetingDate);
}
public void associateClientsAttendance(final Collection<ClientAttendance> clientsAttendance) {
//do not allow to capture attendance in advance.
if(isMeetingDateAfter(DateUtils.getLocalDateOfTenant())){
final String errorMessage = "Attendance cannot be in the future.";
throw new MeetingDateException("cannot.be.a.future.date", errorMessage, getMeetingDateLocalDate());
}
this.clientsAttendance = new HashSet<ClientAttendance>(clientsAttendance);
}
public Map<String, Object> update(final JsonCommand command) {
final Map<String, Object> actualChanges = new LinkedHashMap<String, Object>(9);
final String dateFormatAsInput = command.dateFormat();
final String localeAsInput = command.locale();
if (command.isChangeInLocalDateParameterNamed(meetingDateParamName, getMeetingDateLocalDate())) {
final String valueAsInput = command.stringValueOfParameterNamed(meetingDateParamName);
final LocalDate newValue = command.localDateValueOfParameterNamed(meetingDateParamName);
actualChanges.put(meetingDateParamName, valueAsInput);
actualChanges.put("dateFormat", dateFormatAsInput);
actualChanges.put("locale", localeAsInput);
this.meetingDate = newValue.toDate();
if(!isValidMeetingDate(this.calendarInstance, this.meetingDate)){
throw new NotValidRecurringDateException("meeting", "Not a valid meeting date", meetingDate);
}
}
return actualChanges;
}
public Map<String, Object> updateAttendance(final Collection<ClientAttendance> clientsAttendance) {
final Map<String, Object> actualChanges = new LinkedHashMap<String, Object>(1);
final Map<String, Object> clientAttendanceChanges = new LinkedHashMap<String, Object>(clientsAttendance.size());
updateAttendanceLoop: for (ClientAttendance clientAttendance : clientsAttendance) {
for (ClientAttendance clientAttendanceOriginal : this.clientsAttendance) {
if (clientAttendanceOriginal.clientId().equals(clientAttendance.clientId())) {
final Integer newValue = clientAttendance.getAttendanceTypeId();
if(!newValue.equals(clientAttendanceOriginal.getAttendanceTypeId())){
clientAttendanceOriginal.updateAttendanceTypeId(newValue);
final Map<String, Object> clientAttendanceChange = new LinkedHashMap<String, Object>(2);
clientAttendanceChange.put(clientIdParamName, clientAttendanceOriginal.clientId());
clientAttendanceChange.put(attendanceTypeParamName, newValue);
clientAttendanceChanges.put(clientAttendanceOriginal.clientId().toString(), clientAttendanceChange);
}
continue updateAttendanceLoop;
}
}
final Map<String, Object> clientAttendanceChange = new LinkedHashMap<String, Object>();
clientAttendanceChange.put(clientIdParamName, clientAttendance.clientId());
clientAttendanceChange.put(attendanceTypeParamName, clientAttendance.getAttendanceTypeId());
clientAttendanceChanges.put(clientAttendance.clientId().toString(), clientAttendanceChange);
//New attendance record
this.clientsAttendance.add(clientAttendance);
}
actualChanges.put(clientsAttendanceParamName, clientAttendanceChanges);
return actualChanges;
}
public Long entityId() {
return calendarInstance.getEntityId();
}
public boolean isCenterEntity(){
return CalendarEntityType.isCenter(calendarInstance.getEntityTypeId());
}
public boolean isGroupEntity(){
return CalendarEntityType.isGroup(calendarInstance.getEntityTypeId());
}
public LocalDate getMeetingDateLocalDate() {
LocalDate meetingDateLocalDate = null;
if (this.meetingDate != null) {
meetingDateLocalDate = LocalDate.fromDateFields(this.meetingDate);
}
return meetingDateLocalDate;
}
public Date getMeetingDate(){
return this.meetingDate;
}
public boolean isMeetingDateBefore(final LocalDate newStartDate) {
return (this.meetingDate != null && newStartDate != null && getMeetingDateLocalDate().isBefore(newStartDate)) ? true : false;
}
private static boolean isValidMeetingDate(final CalendarInstance calendarInstance, final Date meetingDate ){
final Calendar calendar = calendarInstance.getCalendar();
LocalDate meetingDateLocalDate = null;
if (meetingDate != null) {
meetingDateLocalDate = LocalDate.fromDateFields(meetingDate);
}
if(meetingDateLocalDate == null || !CalendarUtils.isValidRedurringDate(calendar.getRecurrence(), calendar.getStartDateLocalDate(), meetingDateLocalDate)){
return false;
}
return true;
}
private boolean isMeetingDateAfter(final LocalDate date){
return getMeetingDateLocalDate().isAfter(date);
}
public Collection<ClientAttendance> getClientsAttendance() {
return this.clientsAttendance;
}
}
@@ -0,0 +1,13 @@
/**
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/.
*/
package org.mifosplatform.portfolio.meeting.domain;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
public interface MeetingRepository extends JpaRepository<Meeting, Long>, JpaSpecificationExecutor<Meeting> {
}
@@ -0,0 +1,51 @@
/**
* 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.meeting.domain;
import org.mifosplatform.portfolio.meeting.exception.MeetingNotFoundException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
/**
* <p>
* Wrapper for {@link MeetingRepository} that is responsible for checking
* if {@link Meeting} is returned when using <code>findOne</code>
* repository method and throwing an appropriate not found exception.
* </p>
*
* <p>
* This is to avoid need for checking and throwing in multiple areas of code
* base where {@link MeetingRepository} is required.
* </p>
*/
@Service
public class MeetingRepositoryWrapper {
private final MeetingRepository repository;
@Autowired
public MeetingRepositoryWrapper(final MeetingRepository repository) {
this.repository = repository;
}
public Meeting findOneWithNotFoundDetection(final Long meetingId) {
final Meeting meeting = this.repository.findOne(meetingId);
if (meeting == null) { throw new MeetingNotFoundException(meetingId); }
return meeting;
}
public void save(final Meeting meeting) {
this.repository.save(meeting);
}
public void delete(final Meeting meeting) {
this.repository.delete(meeting);
}
public void saveAndFlush(final Meeting meeting) {
this.repository.saveAndFlush(meeting);
}
}
@@ -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.meeting.exception;
import org.mifosplatform.infrastructure.core.exception.AbstractPlatformDomainRuleException;
/**
* A {@link RuntimeException} thrown when Meeting date violates a domain rule.
*/
public class MeetingDateException extends AbstractPlatformDomainRuleException {
public MeetingDateException(final String postFix, final String defaultUserMessage, final Object... defaultUserMessageArgs) {
super("error.msg.meeting.date." + postFix, defaultUserMessage, defaultUserMessageArgs);
}
}
@@ -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.meeting.exception;
import org.mifosplatform.infrastructure.core.exception.AbstractPlatformResourceNotFoundException;
/**
* A {@link RuntimeException} thrown when Meeting resources are not found.
*/
public class MeetingNotFoundException extends AbstractPlatformResourceNotFoundException {
public MeetingNotFoundException(final Long id) {
super("error.msg.meeting.id.invalid", "Meeting with identifier " + id + " does not exist", id);
}
}
@@ -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.meeting.exception;
import org.mifosplatform.infrastructure.core.exception.AbstractPlatformDomainRuleException;
/**
* {@link AbstractPlatformDomainRuleException} thrown when meeting is captured against not supported resource.
*/
public class MeetingNotSupportedResourceException extends AbstractPlatformDomainRuleException {
public MeetingNotSupportedResourceException(String defaultUserMessage, Object... defaultUserMessageArgs) {
super("error.msg.meeting.not.supported.resource", defaultUserMessage, defaultUserMessageArgs);
}
}
@@ -0,0 +1,32 @@
/**
* 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.meeting.handler;
import org.mifosplatform.commands.handler.NewCommandSourceHandler;
import org.mifosplatform.infrastructure.core.api.JsonCommand;
import org.mifosplatform.infrastructure.core.data.CommandProcessingResult;
import org.mifosplatform.portfolio.meeting.service.MeetingWritePlatformService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
public class CreateMeetingCommandHandler implements NewCommandSourceHandler {
private final MeetingWritePlatformService writePlatformService;
@Autowired
public CreateMeetingCommandHandler(final MeetingWritePlatformService writePlatformService) {
this.writePlatformService = writePlatformService;
}
@Transactional
@Override
public CommandProcessingResult processCommand(final JsonCommand command) {
return this.writePlatformService.createMeeting(command);
}
}
@@ -0,0 +1,31 @@
/**
* 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.meeting.handler;
import org.mifosplatform.commands.handler.NewCommandSourceHandler;
import org.mifosplatform.infrastructure.core.api.JsonCommand;
import org.mifosplatform.infrastructure.core.data.CommandProcessingResult;
import org.mifosplatform.portfolio.meeting.service.MeetingWritePlatformService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
public class DeleteMeetingCommandHandler implements NewCommandSourceHandler {
private final MeetingWritePlatformService writePlatformService;
@Autowired
public DeleteMeetingCommandHandler(final MeetingWritePlatformService writePlatformService) {
this.writePlatformService = writePlatformService;
}
@Transactional
@Override
public CommandProcessingResult processCommand(final JsonCommand command) {
return this.writePlatformService.deleteMeeting(command.entityId());
}
}
@@ -0,0 +1,33 @@
/**
* 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.meeting.handler;
import org.mifosplatform.commands.handler.NewCommandSourceHandler;
import org.mifosplatform.infrastructure.core.api.JsonCommand;
import org.mifosplatform.infrastructure.core.data.CommandProcessingResult;
import org.mifosplatform.portfolio.meeting.service.MeetingWritePlatformService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
public class UpdateMeetingAttendanceCommandHandler implements NewCommandSourceHandler {
private final MeetingWritePlatformService writePlatformService;
@Autowired
public UpdateMeetingAttendanceCommandHandler(final MeetingWritePlatformService writePlatformService) {
this.writePlatformService = writePlatformService;
}
@Transactional
@Override
public CommandProcessingResult processCommand(final JsonCommand command) {
return this.writePlatformService.saveOrUpdateAttendance(command);
}
}
@@ -0,0 +1,33 @@
/**
* 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.meeting.handler;
import org.mifosplatform.commands.handler.NewCommandSourceHandler;
import org.mifosplatform.infrastructure.core.api.JsonCommand;
import org.mifosplatform.infrastructure.core.data.CommandProcessingResult;
import org.mifosplatform.portfolio.meeting.service.MeetingWritePlatformService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
public class UpdateMeetingCommandHandler implements NewCommandSourceHandler {
private final MeetingWritePlatformService writePlatformService;
@Autowired
public UpdateMeetingCommandHandler(final MeetingWritePlatformService writePlatformService) {
this.writePlatformService = writePlatformService;
}
@Transactional
@Override
public CommandProcessingResult processCommand(final JsonCommand command) {
return this.writePlatformService.updateMeeting(command);
}
}
@@ -0,0 +1,21 @@
/**
* 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.meeting.service;
import java.util.Collection;
import java.util.List;
import org.mifosplatform.portfolio.meeting.data.MeetingData;
public interface MeetingReadPlatformService {
MeetingData retrieveMeeting(final Long meetingId, Long entityId, Integer entityTypeId);
Collection<MeetingData> retrieveMeetingsByEntity(final Long entityId, final Integer entityTypeId, Integer limit);
Collection<MeetingData> retrieveMeetingsByEntityByCalendarType(final Long entityId, final Integer entityTypeId,
final List<Integer> calendarTypeOptions);
}
@@ -0,0 +1,89 @@
/**
* 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.meeting.service;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Collection;
import java.util.List;
import org.joda.time.LocalDate;
import org.mifosplatform.infrastructure.core.domain.JdbcSupport;
import org.mifosplatform.infrastructure.core.service.RoutingDataSource;
import org.mifosplatform.portfolio.calendar.service.CalendarUtils;
import org.mifosplatform.portfolio.meeting.data.MeetingData;
import org.mifosplatform.portfolio.meeting.exception.MeetingNotFoundException;
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;
@Service
public class MeetingReadPlatformServiceImpl implements MeetingReadPlatformService {
private final JdbcTemplate jdbcTemplate;
@Autowired
public MeetingReadPlatformServiceImpl(final RoutingDataSource dataSource) {
this.jdbcTemplate = new JdbcTemplate(dataSource);
}
private static final class MeetingDataMapper implements RowMapper<MeetingData> {
public String schema() {
return " select m.id as id, m.meeting_date as meetingDate from m_meeting m inner join m_calendar_instance ci on m.calendar_instance_id = ci.id ";
}
@Override
public MeetingData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException {
final Long id = rs.getLong("id");
final LocalDate meetingDate = JdbcSupport.getLocalDate(rs, "meetingDate");
return MeetingData.instance(id, meetingDate);
}
}
@Override
public MeetingData retrieveMeeting(final Long meetingId, Long entityId, Integer entityTypeId) {
try {
final MeetingDataMapper rm = new MeetingDataMapper();
final String sql = rm.schema() + " where m.id = ? and ci.entity_id = ? and ci.entity_type_enum = ? ";
return this.jdbcTemplate.queryForObject(sql, rm, new Object[] { meetingId, entityId, entityTypeId });
} catch (final EmptyResultDataAccessException e) {
throw new MeetingNotFoundException(meetingId);
}
}
@Override
public Collection<MeetingData> retrieveMeetingsByEntity(Long entityId, Integer entityTypeId, Integer limit) {
final MeetingDataMapper rm = new MeetingDataMapper();
String sql = rm.schema() + " where ci.entity_id = ? and ci.entity_type_enum = ? ";
if(limit != null && limit > 0){
sql = sql + " order by m.meeting_date desc " + " limit " + limit;
}
return this.jdbcTemplate.query(sql, rm, new Object[] { entityId, entityTypeId });
}
@Override
public Collection<MeetingData> retrieveMeetingsByEntityByCalendarType(Long entityId, Integer entityTypeId,
List<Integer> calendarTypeOptions) {
final MeetingDataMapper rm = new MeetingDataMapper();
String sqlCalendarTypeOptions = CalendarUtils.getSqlCalendarTypeOptionsInString(calendarTypeOptions);
final String sql = rm.schema()
+ " inner join m_calendar c on ci.calendar_id=c.id where ci.entity_id = ? and ci.entity_type_enum = ? and c.calendar_type_enum in ("
+ sqlCalendarTypeOptions + ") order by c.start_date ";
return this.jdbcTemplate.query(sql, rm, new Object[] { entityId, entityTypeId });
}
}
@@ -0,0 +1,20 @@
/**
* 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.meeting.service;
import org.mifosplatform.infrastructure.core.api.JsonCommand;
import org.mifosplatform.infrastructure.core.data.CommandProcessingResult;
public interface MeetingWritePlatformService {
CommandProcessingResult createMeeting(JsonCommand command);
CommandProcessingResult updateMeeting(JsonCommand command);
CommandProcessingResult deleteMeeting(Long meetingId);
CommandProcessingResult saveOrUpdateAttendance(JsonCommand command);
}
@@ -0,0 +1,229 @@
/**
* 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.meeting.service;
import static org.mifosplatform.portfolio.meeting.MeetingApiConstants.attendanceTypeParamName;
import static org.mifosplatform.portfolio.meeting.MeetingApiConstants.calendarIdParamName;
import static org.mifosplatform.portfolio.meeting.MeetingApiConstants.clientIdParamName;
import static org.mifosplatform.portfolio.meeting.MeetingApiConstants.clientsAttendanceParamName;
import static org.mifosplatform.portfolio.meeting.MeetingApiConstants.meetingDateParamName;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.Map;
import org.mifosplatform.infrastructure.core.api.JsonCommand;
import org.mifosplatform.infrastructure.core.data.CommandProcessingResult;
import org.mifosplatform.infrastructure.core.data.CommandProcessingResultBuilder;
import org.mifosplatform.infrastructure.core.exception.PlatformDataIntegrityException;
import org.mifosplatform.infrastructure.core.serialization.FromJsonHelper;
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.CalendarInstanceNotFoundException;
import org.mifosplatform.portfolio.calendar.exception.CalendarNotFoundException;
import org.mifosplatform.portfolio.client.domain.Client;
import org.mifosplatform.portfolio.client.domain.ClientRepository;
import org.mifosplatform.portfolio.group.domain.Group;
import org.mifosplatform.portfolio.group.domain.GroupRepository;
import org.mifosplatform.portfolio.group.exception.ClientNotInGroupException;
import org.mifosplatform.portfolio.meeting.attendance.domain.ClientAttendance;
import org.mifosplatform.portfolio.meeting.data.MeetingDataValidator;
import org.mifosplatform.portfolio.meeting.domain.Meeting;
import org.mifosplatform.portfolio.meeting.domain.MeetingRepositoryWrapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.stereotype.Service;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
@Service
public class MeetingWritePlatformServiceJpaRepositoryImpl implements MeetingWritePlatformService {
private final MeetingRepositoryWrapper meetingRepository;
private final MeetingDataValidator meetingDataValidator;
private final CalendarInstanceRepository calendarInstanceRepository;
private final CalendarRepository calendarRepository;
private final ClientRepository clientRepository;
private final GroupRepository groupRepository;
private final FromJsonHelper fromApiJsonHelper;
@Autowired
public MeetingWritePlatformServiceJpaRepositoryImpl(MeetingRepositoryWrapper meetingRepository,
MeetingDataValidator meetingDataValidator, CalendarInstanceRepository calendarInstanceRepository,
CalendarRepository calendarRepository, final ClientRepository clientRepository, final GroupRepository groupRepository,
final FromJsonHelper fromApiJsonHelper) {
this.meetingRepository = meetingRepository;
this.meetingDataValidator = meetingDataValidator;
this.calendarInstanceRepository = calendarInstanceRepository;
this.calendarRepository = calendarRepository;
this.clientRepository = clientRepository;
this.groupRepository = groupRepository;
this.fromApiJsonHelper = fromApiJsonHelper;
}
@Override
public CommandProcessingResult createMeeting(JsonCommand command) {
this.meetingDataValidator.validateForCreate(command);
final Date meetingDate = command.DateValueOfParameterNamed(meetingDateParamName);
try {
final CalendarInstance calendarInstance = getCalendarInstance(command);
// create new meeting
final Meeting newMeeting = Meeting.createNew(calendarInstance, meetingDate);
final Collection<ClientAttendance> clientsAttendance = getClientsAttendance(newMeeting, command);
if (clientsAttendance != null && !clientsAttendance.isEmpty()) {
newMeeting.associateClientsAttendance(clientsAttendance);
}
// save meeting details
this.meetingRepository.save(newMeeting);
final Long groupId = newMeeting.isGroupEntity() ? newMeeting.entityId() : null;
return new CommandProcessingResultBuilder() //
.withEntityId(newMeeting.getId()) //
.withGroupId(groupId).build();
} catch (DataIntegrityViolationException dve) {
handleMeetingDataIntegrityIssues(meetingDate, dve);
return new CommandProcessingResultBuilder() //
.build();
}
}
private CalendarInstance getCalendarInstance(final JsonCommand command) {
final Long calendarId = command.longValueOfParameterNamed(calendarIdParamName);
final Long entityId = command.getSupportedEntityId();
final Calendar calendarForUpdate = this.calendarRepository.findOne(calendarId);
if (calendarForUpdate == null) { throw new CalendarNotFoundException(calendarId); }
final Integer entityTypeId = CalendarEntityType.valueOf(command.getSupportedEntityType().toUpperCase()).getValue();
final CalendarInstance calendarInstance = calendarInstanceRepository.findByCalendarIdAndEntityIdAndEntityTypeId(
calendarForUpdate.getId(), entityId, entityTypeId);
if(calendarInstance == null) {
final String postFix = "for." + command.getSupportedEntityType() + "not.found";
final String defaultUserMessage = "No Calendar Instance details found for group with identifier " + entityId + " and calendar with identifier " + calendarId;
throw new CalendarInstanceNotFoundException(postFix, defaultUserMessage, entityId, calendarId);
}
return calendarInstance;
}
private Collection<ClientAttendance> getClientsAttendance(final Meeting meeting, final JsonCommand command) {
final Collection<ClientAttendance> clientsAttendance = new ArrayList<ClientAttendance>();
Collection<Group> childGroups = null;
if (meeting.isCenterEntity()) {
childGroups = this.groupRepository.findByParentId(meeting.entityId());
}
final String json = command.json();
final JsonElement element = this.fromApiJsonHelper.parse(json);
final JsonObject topLevelJsonElement = element.getAsJsonObject();
if (element.isJsonObject()) {
if (topLevelJsonElement.has(clientsAttendanceParamName) && topLevelJsonElement.get(clientsAttendanceParamName).isJsonArray()) {
final JsonArray array = topLevelJsonElement.get(clientsAttendanceParamName).getAsJsonArray();
for (int i = 0; i < array.size(); i++) {
final JsonObject attendanceElement = array.get(i).getAsJsonObject();
final Long clientId = this.fromApiJsonHelper.extractLongNamed(clientIdParamName, attendanceElement);
final Integer attendanceTypeId = this.fromApiJsonHelper.extractIntegerSansLocaleNamed(attendanceTypeParamName,
attendanceElement);
final Client client = this.clientRepository.findOne(clientId);
if (meeting.isGroupEntity() && !client.isChildOfGroup(meeting.entityId())) {
throw new ClientNotInGroupException(clientId, meeting.entityId());
} else if (meeting.isCenterEntity()) {
if (childGroups != null && !childGroups.isEmpty()) {
boolean isChildClient = false;
for (Group group : childGroups) {
if (group.isChildClient(clientId)){
isChildClient = true;
break;
}
}
if(!isChildClient){
final String defaultUserMessage = "Client with identifier " + clientId + " is not in center "
+ meeting.entityId();
throw new ClientNotInGroupException("client.not.in.center", defaultUserMessage, clientId, meeting.entityId());
}
}
}
final ClientAttendance clientAttendance = ClientAttendance.createClientAttendance(client, meeting, attendanceTypeId);
clientsAttendance.add(clientAttendance);
}
}
}
return clientsAttendance;
}
@Override
public CommandProcessingResult updateMeeting(JsonCommand command) {
this.meetingDataValidator.validateForUpdate(command);
final Meeting meetingForUpdate = this.meetingRepository.findOneWithNotFoundDetection(command.entityId());
final Map<String, Object> changes = meetingForUpdate.update(command);
try {
if (!changes.isEmpty()) {
this.meetingRepository.saveAndFlush(meetingForUpdate);
}
} catch (DataIntegrityViolationException dve) {
handleMeetingDataIntegrityIssues(meetingForUpdate.getMeetingDate(), dve);
return new CommandProcessingResultBuilder() //
.build();
}
final Long groupId = meetingForUpdate.isGroupEntity() ? meetingForUpdate.entityId() : null;
return new CommandProcessingResultBuilder() //
.withEntityId(meetingForUpdate.getId()) //
.withGroupId(groupId) //
.with(changes) //
.build();
}
@Override
public CommandProcessingResult deleteMeeting(Long meetingId) {
final Meeting meetingForDelete = this.meetingRepository.findOneWithNotFoundDetection(meetingId);
this.meetingRepository.delete(meetingForDelete);
return new CommandProcessingResultBuilder() //
.withEntityId(meetingId) //
.build();
}
@Override
public CommandProcessingResult saveOrUpdateAttendance(JsonCommand command) {
this.meetingDataValidator.validateForUpdateAttendance(command);
final Meeting meetingForUpdate = this.meetingRepository.findOneWithNotFoundDetection(command.entityId());
final Collection<ClientAttendance> clientsAttendance = getClientsAttendance(meetingForUpdate, command);
final Map<String, Object> changes = meetingForUpdate.updateAttendance(clientsAttendance);
this.meetingRepository.saveAndFlush(meetingForUpdate);
final Long groupId = meetingForUpdate.isGroupEntity() ? meetingForUpdate.entityId() : null;
return new CommandProcessingResultBuilder() //
.withEntityId(meetingForUpdate.getId()) //
.withGroupId(groupId) //
.with(changes) //
.build();
}
private void handleMeetingDataIntegrityIssues(final Date meetingDate, final DataIntegrityViolationException dve) {
final Throwable realCause = dve.getMostSpecificCause();
if (realCause.getMessage().contains("unique_calendar_instance_id_meeting_date")) {
throw new PlatformDataIntegrityException("error.msg.meeting.duplicate", "A meeting with date '" + meetingDate + "' already exists",
meetingDateParamName, meetingDate); }
throw new PlatformDataIntegrityException("error.msg.meeting.unknown.data.integrity.issue",
"Unknown data integrity issue with resource: " + realCause.getMessage());
}
}