From 28bbd361c209abe0bd73b7384a1d8cb6d65f45ad Mon Sep 17 00:00:00 2001 From: ashok-conflux Date: Wed, 24 Jul 2013 10:27:27 +0530 Subject: [PATCH] MIFOSX-496: Capture client attendance details --- .../V101__client_attendance_tables.sql | 30 +++ .../commands/domain/CommandWrapper.java | 8 + .../service/CommandWrapperBuilder.java | 35 +++ .../SynchronousCommandProcessingService.java | 12 + .../workingdays/service/WorkingDaysUtil.java | 4 +- .../calendar/api/CalendarsApiResource.java | 36 +-- .../portfolio/calendar/domain/Calendar.java | 6 +- .../calendar/domain/CalendarEntityType.java | 16 ++ .../domain/CalendarInstanceRepository.java | 2 +- .../CalendarInstanceRepositoryWrapper.java | 51 ++++ .../CalendarInstanceNotFoundException.java | 22 ++ .../service/CalendarReadPlatformService.java | 5 + .../CalendarReadPlatformServiceImpl.java | 61 +++-- ...CalendarHelper.java => CalendarUtils.java} | 69 +++++- ...WritePlatformServiceJpaRepositoryImpl.java | 2 +- .../portfolio/client/domain/Client.java | 9 + .../client/domain/ClientRepository.java | 2 +- .../service/ClientReadPlatformService.java | 2 + .../ClientReadPlatformServiceImpl.java | 14 ++ ...ollectionSheetReadPlatformServiceImpl.java | 3 +- .../group/api/CentersApiResource.java | 21 +- .../group/api/GroupingTypesApiConstants.java | 3 +- .../group/api/GroupsApiResource.java | 42 +++- .../portfolio/group/data/CenterData.java | 17 +- .../group/data/GroupGeneralData.java | 33 ++- .../portfolio/group/domain/Group.java | 9 + .../group/domain/GroupRepository.java | 4 +- .../exception/ClientNotInGroupException.java | 8 +- .../portfolio/loanaccount/domain/Loan.java | 36 +-- .../service/LoanScheduleAssembler.java | 14 +- .../LoanEventApiJsonValidator.java | 6 +- .../service/LoanReadPlatformServiceImpl.java | 11 +- ...WritePlatformServiceJpaRepositoryImpl.java | 6 +- .../meeting/MeetingApiConstants.java | 44 ++++ .../meeting/api/MeetingsApiResource.java | 221 +++++++++++++++++ .../meeting/attendance/AttendanceType.java | 63 +++++ .../attendance/data/ClientAttendanceData.java | 35 +++ .../attendance/domain/ClientAttendance.java | 54 +++++ .../domain/ClientAttendanceRepository.java | 13 + ...AttendanceDropdownReadPlatformService.java | 14 ++ ...ndanceDropdownReadPlatformServiceImpl.java | 22 ++ .../service/AttendanceEnumerations.java | 57 +++++ .../ClientAttendanceReadPlatformService.java | 14 ++ ...ientAttendanceReadPlatformServiceImpl.java | 59 +++++ .../portfolio/meeting/data/MeetingData.java | 69 ++++++ .../meeting/data/MeetingDataValidator.java | 144 +++++++++++ .../portfolio/meeting/domain/Meeting.java | 188 ++++++++++++++ .../meeting/domain/MeetingRepository.java | 13 + .../domain/MeetingRepositoryWrapper.java | 51 ++++ .../exception/MeetingDateException.java | 18 ++ .../exception/MeetingNotFoundException.java | 18 ++ .../MeetingNotSupportedResourceException.java | 18 ++ .../handler/CreateMeetingCommandHandler.java | 32 +++ .../handler/DeleteMeetingCommandHandler.java | 31 +++ ...UpdateMeetingAttendanceCommandHandler.java | 33 +++ .../handler/UpdateMeetingCommandHandler.java | 33 +++ .../service/MeetingReadPlatformService.java | 21 ++ .../MeetingReadPlatformServiceImpl.java | 89 +++++++ .../service/MeetingWritePlatformService.java | 20 ++ ...WritePlatformServiceJpaRepositoryImpl.java | 229 ++++++++++++++++++ 60 files changed, 2056 insertions(+), 146 deletions(-) create mode 100644 mifosng-db/migrations/core_db/V101__client_attendance_tables.sql create mode 100644 mifosng-provider/src/main/java/org/mifosplatform/portfolio/calendar/domain/CalendarInstanceRepositoryWrapper.java create mode 100644 mifosng-provider/src/main/java/org/mifosplatform/portfolio/calendar/exception/CalendarInstanceNotFoundException.java rename mifosng-provider/src/main/java/org/mifosplatform/portfolio/calendar/service/{CalendarHelper.java => CalendarUtils.java} (85%) create mode 100644 mifosng-provider/src/main/java/org/mifosplatform/portfolio/meeting/MeetingApiConstants.java create mode 100644 mifosng-provider/src/main/java/org/mifosplatform/portfolio/meeting/api/MeetingsApiResource.java create mode 100644 mifosng-provider/src/main/java/org/mifosplatform/portfolio/meeting/attendance/AttendanceType.java create mode 100644 mifosng-provider/src/main/java/org/mifosplatform/portfolio/meeting/attendance/data/ClientAttendanceData.java create mode 100644 mifosng-provider/src/main/java/org/mifosplatform/portfolio/meeting/attendance/domain/ClientAttendance.java create mode 100644 mifosng-provider/src/main/java/org/mifosplatform/portfolio/meeting/attendance/domain/ClientAttendanceRepository.java create mode 100644 mifosng-provider/src/main/java/org/mifosplatform/portfolio/meeting/attendance/service/AttendanceDropdownReadPlatformService.java create mode 100644 mifosng-provider/src/main/java/org/mifosplatform/portfolio/meeting/attendance/service/AttendanceDropdownReadPlatformServiceImpl.java create mode 100644 mifosng-provider/src/main/java/org/mifosplatform/portfolio/meeting/attendance/service/AttendanceEnumerations.java create mode 100644 mifosng-provider/src/main/java/org/mifosplatform/portfolio/meeting/attendance/service/ClientAttendanceReadPlatformService.java create mode 100644 mifosng-provider/src/main/java/org/mifosplatform/portfolio/meeting/attendance/service/ClientAttendanceReadPlatformServiceImpl.java create mode 100644 mifosng-provider/src/main/java/org/mifosplatform/portfolio/meeting/data/MeetingData.java create mode 100644 mifosng-provider/src/main/java/org/mifosplatform/portfolio/meeting/data/MeetingDataValidator.java create mode 100644 mifosng-provider/src/main/java/org/mifosplatform/portfolio/meeting/domain/Meeting.java create mode 100644 mifosng-provider/src/main/java/org/mifosplatform/portfolio/meeting/domain/MeetingRepository.java create mode 100644 mifosng-provider/src/main/java/org/mifosplatform/portfolio/meeting/domain/MeetingRepositoryWrapper.java create mode 100644 mifosng-provider/src/main/java/org/mifosplatform/portfolio/meeting/exception/MeetingDateException.java create mode 100644 mifosng-provider/src/main/java/org/mifosplatform/portfolio/meeting/exception/MeetingNotFoundException.java create mode 100644 mifosng-provider/src/main/java/org/mifosplatform/portfolio/meeting/exception/MeetingNotSupportedResourceException.java create mode 100644 mifosng-provider/src/main/java/org/mifosplatform/portfolio/meeting/handler/CreateMeetingCommandHandler.java create mode 100644 mifosng-provider/src/main/java/org/mifosplatform/portfolio/meeting/handler/DeleteMeetingCommandHandler.java create mode 100644 mifosng-provider/src/main/java/org/mifosplatform/portfolio/meeting/handler/UpdateMeetingAttendanceCommandHandler.java create mode 100644 mifosng-provider/src/main/java/org/mifosplatform/portfolio/meeting/handler/UpdateMeetingCommandHandler.java create mode 100644 mifosng-provider/src/main/java/org/mifosplatform/portfolio/meeting/service/MeetingReadPlatformService.java create mode 100644 mifosng-provider/src/main/java/org/mifosplatform/portfolio/meeting/service/MeetingReadPlatformServiceImpl.java create mode 100644 mifosng-provider/src/main/java/org/mifosplatform/portfolio/meeting/service/MeetingWritePlatformService.java create mode 100644 mifosng-provider/src/main/java/org/mifosplatform/portfolio/meeting/service/MeetingWritePlatformServiceJpaRepositoryImpl.java diff --git a/mifosng-db/migrations/core_db/V101__client_attendance_tables.sql b/mifosng-db/migrations/core_db/V101__client_attendance_tables.sql new file mode 100644 index 000000000..e55b58ac8 --- /dev/null +++ b/mifosng-db/migrations/core_db/V101__client_attendance_tables.sql @@ -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); diff --git a/mifosng-provider/src/main/java/org/mifosplatform/commands/domain/CommandWrapper.java b/mifosng-provider/src/main/java/org/mifosplatform/commands/domain/CommandWrapper.java index 2cc730d21..3a809e0c9 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/commands/domain/CommandWrapper.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/commands/domain/CommandWrapper.java @@ -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"); + } } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/commands/service/CommandWrapperBuilder.java b/mifosng-provider/src/main/java/org/mifosplatform/commands/service/CommandWrapperBuilder.java index 357a9150c..caa102376 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/commands/service/CommandWrapperBuilder.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/commands/service/CommandWrapperBuilder.java @@ -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; + } } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/commands/service/SynchronousCommandProcessingService.java b/mifosng-provider/src/main/java/org/mifosplatform/commands/service/SynchronousCommandProcessingService.java index e1208c0a4..a70f704d1 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/commands/service/SynchronousCommandProcessingService.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/commands/service/SynchronousCommandProcessingService.java @@ -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()); } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/organisation/workingdays/service/WorkingDaysUtil.java b/mifosng-provider/src/main/java/org/mifosplatform/organisation/workingdays/service/WorkingDaysUtil.java index 56a4a5b02..4fb1dc9f0 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/organisation/workingdays/service/WorkingDaysUtil.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/organisation/workingdays/service/WorkingDaysUtil.java @@ -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); } } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/calendar/api/CalendarsApiResource.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/calendar/api/CalendarsApiResource.java index d7a80ef7e..5afcb3311 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/calendar/api/CalendarsApiResource.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/calendar/api/CalendarsApiResource.java @@ -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 calendarsData = new ArrayList(); - List calendarTypeOptions = createIntegerListFromQueryParameter(calendarType); + List calendarTypeOptions = CalendarUtils.createIntegerListFromQueryParameter(calendarType); if (!associationParameters.isEmpty()) { @@ -191,37 +192,4 @@ public class CalendarsApiResource { return new CalendarData(calendarData, entityTypeOptions, calendarTypeOptions, remindByOptions); } - - public List createIntegerListFromQueryParameter(String calendarTypeQuery) { - List calendarTypeOptions = new ArrayList(); - // 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 calendarTypeOptionsInQuery = new ArrayList(); - 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; - } - } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/calendar/domain/Calendar.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/calendar/domain/Calendar.java index 49395caf0..eb25c0a79 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/calendar/domain/Calendar.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/calendar/domain/Calendar.java @@ -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 { //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); } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/calendar/domain/CalendarEntityType.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/calendar/domain/CalendarEntityType.java index b893a1b93..95cf5329d 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/calendar/domain/CalendarEntityType.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/calendar/domain/CalendarEntityType.java @@ -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; + } } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/calendar/domain/CalendarInstanceRepository.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/calendar/domain/CalendarInstanceRepository.java index f11547652..fe7fd2553 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/calendar/domain/CalendarInstanceRepository.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/calendar/domain/CalendarInstanceRepository.java @@ -14,7 +14,7 @@ import org.springframework.data.repository.query.Param; public interface CalendarInstanceRepository extends JpaRepository, JpaSpecificationExecutor { - CalendarInstance findByCalendarAndEntityIdAndEntityTypeId(Long calendarId, Long entityId, Integer entityTypeId); + CalendarInstance findByCalendarIdAndEntityIdAndEntityTypeId(Long calendarId, Long entityId, Integer entityTypeId); Collection findByEntityIdAndEntityTypeId(Long entityId, Integer entityTypeId); diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/calendar/domain/CalendarInstanceRepositoryWrapper.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/calendar/domain/CalendarInstanceRepositoryWrapper.java new file mode 100644 index 000000000..ce94b7088 --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/calendar/domain/CalendarInstanceRepositoryWrapper.java @@ -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; + +/** + *

+ * Wrapper for {@link CalendarInstanceRepository} that is responsible for checking + * if {@link CalendarInstance} is returned when using findOne + * repository method and throwing an appropriate not found exception. + *

+ * + *

+ * This is to avoid need for checking and throwing in multiple areas of code + * base where {@link CalendarInstanceRepository} is required. + *

+ */ +@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); + } +} \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/calendar/exception/CalendarInstanceNotFoundException.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/calendar/exception/CalendarInstanceNotFoundException.java new file mode 100644 index 000000000..13042f555 --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/calendar/exception/CalendarInstanceNotFoundException.java @@ -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); + } +} \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/calendar/service/CalendarReadPlatformService.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/calendar/service/CalendarReadPlatformService.java index 81b02a433..4a8b719e4 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/calendar/service/CalendarReadPlatformService.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/calendar/service/CalendarReadPlatformService.java @@ -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 generateRecurringDates(final Collection calendarsData); CalendarData retrieveLoanCalendar(final Long loanId); + + CalendarData retrieveCollctionCalendarByEntity(final Long entityId, final Integer entityTypeId); } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/calendar/service/CalendarReadPlatformServiceImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/calendar/service/CalendarReadPlatformServiceImpl.java index 9927279ea..353c842f5 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/calendar/service/CalendarReadPlatformServiceImpl.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/calendar/service/CalendarReadPlatformServiceImpl.java @@ -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 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 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 recurringDates = CalendarHelper.getRecurringDates(rrule, seedDate, startDate, endDate, -1); - final Collection nextTenRecurringDates = CalendarHelper.getRecurringDates(rrule, seedDate, endDate); + + final Collection recurringDates = CalendarUtils.getRecurringDates(rrule, seedDate, startDate, endDate, -1); + final Collection 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 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; - } + } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/calendar/service/CalendarHelper.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/calendar/service/CalendarUtils.java similarity index 85% rename from mifosng-provider/src/main/java/org/mifosplatform/portfolio/calendar/service/CalendarHelper.java rename to mifosng-provider/src/main/java/org/mifosplatform/portfolio/calendar/service/CalendarUtils.java index df8a4634c..a02cc61fa 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/calendar/service/CalendarHelper.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/calendar/service/CalendarUtils.java @@ -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 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 createIntegerListFromQueryParameter(String calendarTypeQuery) { + List calendarTypeOptions = new ArrayList(); + // 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 calendarTypeOptionsInQuery = new ArrayList(); + 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 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; + } } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/calendar/service/CalendarWritePlatformServiceJpaRepositoryImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/calendar/service/CalendarWritePlatformServiceJpaRepositoryImpl.java index ad59d6e4b..fe70c2e74 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/calendar/service/CalendarWritePlatformServiceJpaRepositoryImpl.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/calendar/service/CalendarWritePlatformServiceJpaRepositoryImpl.java @@ -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) // diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/domain/Client.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/domain/Client.java index 7b644b87f..085ea3d0d 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/domain/Client.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/domain/Client.java @@ -468,4 +468,13 @@ public final class Client extends AbstractPersistable { 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; + } } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/domain/ClientRepository.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/domain/ClientRepository.java index 2b30304dd..1b8af41fa 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/domain/ClientRepository.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/domain/ClientRepository.java @@ -9,5 +9,5 @@ import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.JpaSpecificationExecutor; public interface ClientRepository extends JpaRepository, JpaSpecificationExecutor { - // no added behaviour + // no added behaviour } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/service/ClientReadPlatformService.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/service/ClientReadPlatformService.java index 4a60e8e86..2d769e9c3 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/service/ClientReadPlatformService.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/service/ClientReadPlatformService.java @@ -28,6 +28,8 @@ public interface ClientReadPlatformService { Collection retrieveClientMembersOfGroup(Long groupId); Collection retrieveActiveClientMembersOfGroup(Long groupId); + + Collection retrieveActiveClientMembersOfCenter(final Long centerId); ClientData retrieveAllClosureReasons(String clientClosureReason); } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/service/ClientReadPlatformServiceImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/service/ClientReadPlatformServiceImpl.java index ee93c5285..8eb05e30a 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/service/ClientReadPlatformServiceImpl.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/service/ClientReadPlatformServiceImpl.java @@ -298,6 +298,20 @@ public class ClientReadPlatformServiceImpl implements ClientReadPlatformService } } + @Override + public Collection 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 { private final String schema; diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/collectionsheet/service/CollectionSheetReadPlatformServiceImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/collectionsheet/service/CollectionSheetReadPlatformServiceImpl.java index 08f8f51c4..64d7f8386 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/collectionsheet/service/CollectionSheetReadPlatformServiceImpl.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/collectionsheet/service/CollectionSheetReadPlatformServiceImpl.java @@ -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 "; diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/group/api/CentersApiResource.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/group/api/CentersApiResource.java index f363af10a..70dca5147 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/group/api/CentersApiResource.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/group/api/CentersApiResource.java @@ -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 associationParameters = ApiParameterHelper.extractAssociationsForResponseIfProvided(uriInfo.getQueryParameters()); - + CalendarData collectionMeetingCalendar = null; + Collection 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); diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/group/api/GroupingTypesApiConstants.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/group/api/GroupingTypesApiConstants.java index 9151d270d..935ee0c00 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/group/api/GroupingTypesApiConstants.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/group/api/GroupingTypesApiConstants.java @@ -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 CENTER_REQUEST_DATA_PARAMETERS = new HashSet(Arrays.asList(localeParamName, dateFormatParamName, idParamName, nameParamName, externalIdParamName, officeIdParamName, staffIdParamName, activeParamName, @@ -59,7 +60,7 @@ public class GroupingTypesApiConstants { public static final Set GROUP_REQUEST_DATA_PARAMETERS = new HashSet(Arrays.asList(localeParamName, dateFormatParamName, idParamName, nameParamName, externalIdParamName, centerIdParamName, officeIdParamName, staffIdParamName, activeParamName, - activationDateParamName, clientMembersParamName)); + activationDateParamName, clientMembersParamName, collectionMeetingCalendar)); public static final Set GROUP_ROLES_REQUEST_DATA_PARAMETERS = new HashSet(Arrays.asList(roleParamName, clientIdParamName)); diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/group/api/GroupsApiResource.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/group/api/GroupsApiResource.java index 5ccd9f420..33119a77b 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/group/api/GroupsApiResource.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/group/api/GroupsApiResource.java @@ -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 membersOfGroup = null; Collection groupRoles = null; GroupRoleData selectedRole = null; + Collection 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 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()); diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/group/data/CenterData.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/group/data/CenterData.java index 277d8c85b..8de5f2068 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/group/data/CenterData.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/group/data/CenterData.java @@ -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 officeOptions; private final Collection staffOptions; private final Collection groupMembersOptions; + private final CalendarData collectionMeetingCalendar; public static CenterData template(final Long officeId, final LocalDate activationDate, final Collection officeOptions, final Collection staffOptions, final Collection 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 officeOptions = null; final Collection staffOptions = null; final Collection 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 groupMembers) { + public static CenterData withAssociations(final CenterData centerData, final Collection 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 groupMembers, final Collection officeOptions, - final Collection staffOptions, final Collection groupMembersOptions) { + final Collection staffOptions, final Collection 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() { diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/group/data/GroupGeneralData.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/group/data/GroupGeneralData.java index 63d037eb8..722d84d1e 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/group/data/GroupGeneralData.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/group/data/GroupGeneralData.java @@ -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 clientMembers; private final Collection groupRoles; + private final Collection calendarsData; + private final CalendarData collectionMeetingCalendar; // template private final Collection centerOptions; @@ -54,7 +57,7 @@ public class GroupGeneralData { final Collection 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 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 membersOfGroup, final Collection 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 membersOfGroup, - final Collection groupRoles) { + final Collection groupRoles, final Collection 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 groupRoles = null; final Collection availableRoles = null; final GroupRoleData role = null; + final Collection 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 clientMembers, final Collection centerOptions, final Collection officeOptions, final Collection staffOptions, final Collection clientOptions, - final Collection groupRoles, final Collection availableRoles, final GroupRoleData role) { + final Collection groupRoles, final Collection availableRoles, final GroupRoleData role, + final Collection 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); } } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/group/domain/Group.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/group/domain/Group.java index 4f128779b..65a9915de 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/group/domain/Group.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/group/domain/Group.java @@ -384,4 +384,13 @@ public final class Group extends AbstractPersistable { 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; + } } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/group/domain/GroupRepository.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/group/domain/GroupRepository.java index f99bdabbf..4a0402fef 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/group/domain/GroupRepository.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/group/domain/GroupRepository.java @@ -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, JpaSpecificationExecutor { - + Collection findByParentId(Long parentId); } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/group/exception/ClientNotInGroupException.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/group/exception/ClientNotInGroupException.java index 951ec72eb..c87e5eb42 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/group/exception/ClientNotInGroupException.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/group/exception/ClientNotInGroupException.java @@ -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); } } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/Loan.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/Loan.java index 5c14f0708..dda71b4bc 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/Loan.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/Loan.java @@ -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 { public void updateLoanRepaymentScheduleDates(final LocalDate meetingStartDate, final String recuringRule, final boolean isHolidayEnabled, final List 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; } - } } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/service/LoanScheduleAssembler.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/service/LoanScheduleAssembler.java index 367694e52..9634c8be5 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/service/LoanScheduleAssembler.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/service/LoanScheduleAssembler.java @@ -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); } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/serialization/LoanEventApiJsonValidator.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/serialization/LoanEventApiJsonValidator.java index a5a9149fd..35631a06e 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/serialization/LoanEventApiJsonValidator.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/serialization/LoanEventApiJsonValidator.java @@ -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(), diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java index b4decf64c..bc95be622 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java @@ -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 membersOfGroup = this.clientReadPlatformService.retrieveClientMembersOfGroup(groupId); if (!CollectionUtils.isEmpty(membersOfGroup)) { - groupAccount = GroupGeneralData.withAssocations(groupAccount, membersOfGroup, null); + final Collection calendarsData = null; + final CalendarData collectionMeetingCalendar = null; + final Collection 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 membersOfGroup = this.clientReadPlatformService.retrieveActiveClientMembersOfGroup(groupId); if (!CollectionUtils.isEmpty(membersOfGroup)) { - groupAccount = GroupGeneralData.withAssocations(groupAccount, membersOfGroup, null); + final Collection calendarsData = null; + final CalendarData collectionMeetingCalendar = null; + final Collection groupRoles = null; + groupAccount = GroupGeneralData.withAssocations(groupAccount, membersOfGroup, groupRoles, calendarsData, collectionMeetingCalendar); } return LoanAccountData.groupDefaults(groupAccount, expectedDisbursementDate); diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java index 4e882616f..6300b2494 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java @@ -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); } } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/meeting/MeetingApiConstants.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/meeting/MeetingApiConstants.java new file mode 100644 index 000000000..8e5850057 --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/meeting/MeetingApiConstants.java @@ -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 MEETING_REQUEST_DATA_PARAMETERS = new HashSet(Arrays.asList(meetingDateParamName, + localeParamName, dateFormatParamName, calendarIdParamName, clientsAttendanceParamName)); + + public static final Set MEETING_RESPONSE_DATA_PARAMETERS = new HashSet(Arrays.asList(idParamName, meetingDateParamName, + clientsAttendance, clients, calendarData, attendanceTypeOptions)); + +} \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/meeting/api/MeetingsApiResource.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/meeting/api/MeetingsApiResource.java new file mode 100644 index 000000000..e69c70753 --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/meeting/api/MeetingsApiResource.java @@ -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 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 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 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 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 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); + } +} \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/meeting/attendance/AttendanceType.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/meeting/attendance/AttendanceType.java new file mode 100644 index 000000000..92fc1383b --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/meeting/attendance/AttendanceType.java @@ -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; + } + +} \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/meeting/attendance/data/ClientAttendanceData.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/meeting/attendance/data/ClientAttendanceData.java new file mode 100644 index 000000000..4533d4eb7 --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/meeting/attendance/data/ClientAttendanceData.java @@ -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; + } + +} diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/meeting/attendance/domain/ClientAttendance.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/meeting/attendance/domain/ClientAttendance.java new file mode 100644 index 000000000..e7c07ae75 --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/meeting/attendance/domain/ClientAttendance.java @@ -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 { + + @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; + } +} diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/meeting/attendance/domain/ClientAttendanceRepository.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/meeting/attendance/domain/ClientAttendanceRepository.java new file mode 100644 index 000000000..e2615baee --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/meeting/attendance/domain/ClientAttendanceRepository.java @@ -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, JpaSpecificationExecutor { + +} diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/meeting/attendance/service/AttendanceDropdownReadPlatformService.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/meeting/attendance/service/AttendanceDropdownReadPlatformService.java new file mode 100644 index 000000000..ee99387c8 --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/meeting/attendance/service/AttendanceDropdownReadPlatformService.java @@ -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 retrieveAttendanceTypeOptions(); +} \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/meeting/attendance/service/AttendanceDropdownReadPlatformServiceImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/meeting/attendance/service/AttendanceDropdownReadPlatformServiceImpl.java new file mode 100644 index 000000000..86e4dafd9 --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/meeting/attendance/service/AttendanceDropdownReadPlatformServiceImpl.java @@ -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 retrieveAttendanceTypeOptions() { + return AttendanceEnumerations.attendanceType(AttendanceType.values()); + } + +} diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/meeting/attendance/service/AttendanceEnumerations.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/meeting/attendance/service/AttendanceEnumerations.java new file mode 100644 index 000000000..258739ab9 --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/meeting/attendance/service/AttendanceEnumerations.java @@ -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 attendanceType(final AttendanceType[] attendanceTypes) { + final List optionDatas = new ArrayList(); + for (final AttendanceType attendanceType : attendanceTypes) { + if(attendanceType.getValue().equals(AttendanceType.INVALID.getValue())) continue; + optionDatas.add(attendanceType(attendanceType)); + } + return optionDatas; + } +} \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/meeting/attendance/service/ClientAttendanceReadPlatformService.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/meeting/attendance/service/ClientAttendanceReadPlatformService.java new file mode 100644 index 000000000..77a22ea54 --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/meeting/attendance/service/ClientAttendanceReadPlatformService.java @@ -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 retrieveClientAttendanceByMeetingId(final Long meetingId); +} diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/meeting/attendance/service/ClientAttendanceReadPlatformServiceImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/meeting/attendance/service/ClientAttendanceReadPlatformServiceImpl.java new file mode 100644 index 000000000..a28c7365e --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/meeting/attendance/service/ClientAttendanceReadPlatformServiceImpl.java @@ -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 { + + 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 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 }); + } + +} \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/meeting/data/MeetingData.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/meeting/data/MeetingData.java new file mode 100644 index 000000000..8fd10081f --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/meeting/data/MeetingData.java @@ -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 clientsAttendance; + + // template data + private final Collection clients; + private final CalendarData calendarData; + @SuppressWarnings("unused") + private final List attendanceTypeOptions; + + public static MeetingData instance(final Long id, final LocalDate meetingDate) { + final Collection clientsAttendance = null; + final Collection clients = null; + final CalendarData calendarData = null; + final List attendanceTypeOptions = null; + return new MeetingData(id, meetingDate, clientsAttendance, clients, calendarData, attendanceTypeOptions); + } + + public static MeetingData withClientsAttendanceAndAttendanceTypeOptions(final MeetingData meetingData, + final Collection clientsAttendance, final List attendanceTypesOptions) { + return new MeetingData(meetingData.id, meetingData.meetingDate, clientsAttendance, meetingData.clients, meetingData.calendarData, + attendanceTypesOptions); + } + + public static MeetingData template(final Collection clients, final CalendarData calendarData, + final List attendanceTypeOptions) { + final Long id = null; + final LocalDate meetingDate = null; + final Collection clientsAttendance = null; + return new MeetingData(id, meetingDate, clientsAttendance, clients, calendarData, attendanceTypeOptions); + } + + public static MeetingData withAttendanceTypeOptions(final MeetingData meetingData, final List attendanceTypeOptions) { + + return new MeetingData(meetingData.id, meetingData.meetingDate, meetingData.clientsAttendance, meetingData.clients, + meetingData.calendarData, attendanceTypeOptions); + } + + private MeetingData(Long id, LocalDate meetingDate, Collection clientsAttendance, + final Collection clients, final CalendarData calendarData, final List attendanceTypeOptions) { + this.id = id; + this.meetingDate = meetingDate; + this.clientsAttendance = clientsAttendance; + this.clients = clients; + this.calendarData = calendarData; + this.attendanceTypeOptions = attendanceTypeOptions; + } +} diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/meeting/data/MeetingDataValidator.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/meeting/data/MeetingDataValidator.java new file mode 100644 index 000000000..aab73bdaf --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/meeting/data/MeetingDataValidator.java @@ -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>() {}.getType(); + fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, MEETING_REQUEST_DATA_PARAMETERS); + + final List dataValidationErrors = new ArrayList(); + 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>() {}.getType(); + fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, MEETING_REQUEST_DATA_PARAMETERS); + + final List dataValidationErrors = new ArrayList(); + 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>() {}.getType(); + fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, MEETING_REQUEST_DATA_PARAMETERS); + + final List dataValidationErrors = new ArrayList(); + 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 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(); + } + } + } + } +} \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/meeting/domain/Meeting.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/meeting/domain/Meeting.java new file mode 100644 index 000000000..1dcabee46 --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/meeting/domain/Meeting.java @@ -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 { + + @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 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 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(clientsAttendance); + } + + public Map update(final JsonCommand command) { + final Map actualChanges = new LinkedHashMap(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 updateAttendance(final Collection clientsAttendance) { + final Map actualChanges = new LinkedHashMap(1); + final Map clientAttendanceChanges = new LinkedHashMap(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 clientAttendanceChange = new LinkedHashMap(2); + clientAttendanceChange.put(clientIdParamName, clientAttendanceOriginal.clientId()); + clientAttendanceChange.put(attendanceTypeParamName, newValue); + clientAttendanceChanges.put(clientAttendanceOriginal.clientId().toString(), clientAttendanceChange); + } + continue updateAttendanceLoop; + } + } + + final Map clientAttendanceChange = new LinkedHashMap(); + 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 getClientsAttendance() { + return this.clientsAttendance; + } + +} diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/meeting/domain/MeetingRepository.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/meeting/domain/MeetingRepository.java new file mode 100644 index 000000000..d13b66970 --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/meeting/domain/MeetingRepository.java @@ -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, JpaSpecificationExecutor { + +} diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/meeting/domain/MeetingRepositoryWrapper.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/meeting/domain/MeetingRepositoryWrapper.java new file mode 100644 index 000000000..e80292a62 --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/meeting/domain/MeetingRepositoryWrapper.java @@ -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; + +/** + *

+ * Wrapper for {@link MeetingRepository} that is responsible for checking + * if {@link Meeting} is returned when using findOne + * repository method and throwing an appropriate not found exception. + *

+ * + *

+ * This is to avoid need for checking and throwing in multiple areas of code + * base where {@link MeetingRepository} is required. + *

+ */ +@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); + } +} \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/meeting/exception/MeetingDateException.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/meeting/exception/MeetingDateException.java new file mode 100644 index 000000000..5b3dd2c0f --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/meeting/exception/MeetingDateException.java @@ -0,0 +1,18 @@ +/** + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. + */ +package org.mifosplatform.portfolio.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); + } +} \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/meeting/exception/MeetingNotFoundException.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/meeting/exception/MeetingNotFoundException.java new file mode 100644 index 000000000..b5e200b93 --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/meeting/exception/MeetingNotFoundException.java @@ -0,0 +1,18 @@ +/** + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. + */ +package org.mifosplatform.portfolio.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); + } +} \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/meeting/exception/MeetingNotSupportedResourceException.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/meeting/exception/MeetingNotSupportedResourceException.java new file mode 100644 index 000000000..e240aeea5 --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/meeting/exception/MeetingNotSupportedResourceException.java @@ -0,0 +1,18 @@ +/** + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. + */ +package org.mifosplatform.portfolio.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); + } +} \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/meeting/handler/CreateMeetingCommandHandler.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/meeting/handler/CreateMeetingCommandHandler.java new file mode 100644 index 000000000..690817f59 --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/meeting/handler/CreateMeetingCommandHandler.java @@ -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); + } + +} diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/meeting/handler/DeleteMeetingCommandHandler.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/meeting/handler/DeleteMeetingCommandHandler.java new file mode 100644 index 000000000..cf1ef32ed --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/meeting/handler/DeleteMeetingCommandHandler.java @@ -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()); + } +} \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/meeting/handler/UpdateMeetingAttendanceCommandHandler.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/meeting/handler/UpdateMeetingAttendanceCommandHandler.java new file mode 100644 index 000000000..cf52a63e0 --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/meeting/handler/UpdateMeetingAttendanceCommandHandler.java @@ -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); + } + +} diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/meeting/handler/UpdateMeetingCommandHandler.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/meeting/handler/UpdateMeetingCommandHandler.java new file mode 100644 index 000000000..208eeac77 --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/meeting/handler/UpdateMeetingCommandHandler.java @@ -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); + } + +} diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/meeting/service/MeetingReadPlatformService.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/meeting/service/MeetingReadPlatformService.java new file mode 100644 index 000000000..05350ccd5 --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/meeting/service/MeetingReadPlatformService.java @@ -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 retrieveMeetingsByEntity(final Long entityId, final Integer entityTypeId, Integer limit); + + Collection retrieveMeetingsByEntityByCalendarType(final Long entityId, final Integer entityTypeId, + final List calendarTypeOptions); +} diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/meeting/service/MeetingReadPlatformServiceImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/meeting/service/MeetingReadPlatformServiceImpl.java new file mode 100644 index 000000000..9904e5b7f --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/meeting/service/MeetingReadPlatformServiceImpl.java @@ -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 { + + 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 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 retrieveMeetingsByEntityByCalendarType(Long entityId, Integer entityTypeId, + List 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 }); + } + +} \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/meeting/service/MeetingWritePlatformService.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/meeting/service/MeetingWritePlatformService.java new file mode 100644 index 000000000..5d7ac4952 --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/meeting/service/MeetingWritePlatformService.java @@ -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); +} diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/meeting/service/MeetingWritePlatformServiceJpaRepositoryImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/meeting/service/MeetingWritePlatformServiceJpaRepositoryImpl.java new file mode 100644 index 000000000..f4b39854c --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/meeting/service/MeetingWritePlatformServiceJpaRepositoryImpl.java @@ -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 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 getClientsAttendance(final Meeting meeting, final JsonCommand command) { + final Collection clientsAttendance = new ArrayList(); + + Collection 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 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 clientsAttendance = getClientsAttendance(meetingForUpdate, command); + final Map 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()); + } +}