You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@fineract.apache.org by ju...@apache.org on 2019/10/09 07:40:03 UTC
[fineract-cn-group] 01/50: Initial Commit
This is an automated email from the ASF dual-hosted git repository.
juhan pushed a commit to branch develop
in repository https://gitbox.apache.org/repos/asf/fineract-cn-group.git
commit fe3968e6f7198ce8670b93a1fe9e8d528e7326ba
Author: Markus Geiss <mg...@mifos.org>
AuthorDate: Thu Mar 16 14:48:55 2017 +0100
Initial Commit
---
.gitignore | 16 +
HEADER | 13 +
LICENSE | 201 +++++++++++++
README.md | 25 ++
api/build.gradle | 33 ++
api/settings.gradle | 1 +
.../java/io/mifos/group/api/v1/EventConstants.java | 45 +++
.../group/api/v1/client/GroupAlreadyExists.java | 19 ++
.../io/mifos/group/api/v1/client/GroupClient.java | 174 +++++++++++
.../v1/client/GroupDefinitionAlreadyExists.java | 19 ++
.../api/v1/client/GroupDefinitionNotFound.java | 19 ++
.../api/v1/client/GroupDefinitionValidation.java | 19 ++
.../api/v1/client/GroupNotFoundException.java | 19 ++
.../api/v1/client/GroupValidationException.java | 19 ++
.../java/io/mifos/group/api/v1/domain/Address.java | 83 ++++++
.../api/v1/domain/AssignedEmployeeHolder.java | 33 ++
.../io/mifos/group/api/v1/domain/Attendee.java | 54 ++++
.../java/io/mifos/group/api/v1/domain/Cycle.java | 67 +++++
.../java/io/mifos/group/api/v1/domain/Group.java | 202 +++++++++++++
.../io/mifos/group/api/v1/domain/GroupCommand.java | 66 ++++
.../mifos/group/api/v1/domain/GroupDefinition.java | 116 +++++++
.../io/mifos/group/api/v1/domain/GroupPage.java | 53 ++++
.../java/io/mifos/group/api/v1/domain/Meeting.java | 127 ++++++++
.../mifos/group/api/v1/domain/SignOffMeeting.java | 64 ++++
build.gradle | 29 ++
component-test/build.gradle | 38 +++
component-test/settings.gradle | 1 +
.../src/main/java/io/mifos/group/TestGroup.java | 275 +++++++++++++++++
.../java/io/mifos/group/TestGroupDefinition.java | 139 +++++++++
.../listener/GroupDefinitionEventListener.java | 47 +++
.../mifos/group/listener/GroupEventListener.java | 67 +++++
.../group/listener/MigrationEventListener.java | 47 +++
.../mifos/group/util/GroupDefinitionGenerator.java | 41 +++
.../java/io/mifos/group/util/GroupGenerator.java | 59 ++++
component-test/src/main/resources/logback.xml | 28 ++
gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 54212 bytes
gradle/wrapper/gradle-wrapper.properties | 6 +
gradlew | 172 +++++++++++
gradlew.bat | 84 ++++++
service/build.gradle | 63 ++++
service/settings.gradle | 1 +
.../io/mifos/group/service/GroupApplication.java | 29 ++
.../io/mifos/group/service/GroupConfiguration.java | 64 ++++
.../io/mifos/group/service/ServiceConstants.java | 20 ++
.../internal/command/ActivateGroupCommand.java | 38 +++
.../internal/command/CloseGroupCommand.java | 38 +++
.../internal/command/CreateGroupCommand.java | 32 ++
.../command/CreateGroupDefinitionCommand.java | 32 ++
.../internal/command/InitializeServiceCommand.java | 23 ++
.../internal/command/ReopenGroupCommand.java | 38 +++
.../internal/command/SignOffMeetingCommand.java | 38 +++
.../command/UpdateAssignedEmployeeCommand.java | 36 +++
.../internal/command/UpdateLeadersCommand.java | 38 +++
.../internal/command/UpdateMembersCommand.java | 38 +++
.../internal/command/handler/GroupAggregate.java | 332 +++++++++++++++++++++
.../command/handler/MigrationAggregate.java | 58 ++++
.../service/internal/mapper/AddressMapper.java | 48 +++
.../service/internal/mapper/AttendeeMapper.java | 33 ++
.../internal/mapper/GroupCommandMapper.java | 49 +++
.../internal/mapper/GroupDefinitionMapper.java | 51 ++++
.../group/service/internal/mapper/GroupMapper.java | 48 +++
.../service/internal/mapper/MeetingMapper.java | 43 +++
.../service/internal/repository/AddressEntity.java | 105 +++++++
.../internal/repository/AddressRepository.java | 23 ++
.../internal/repository/AttendeeEntity.java | 79 +++++
.../internal/repository/AttendeeRepository.java | 27 ++
.../internal/repository/GroupCommandEntity.java | 104 +++++++
.../repository/GroupCommandRepository.java | 27 ++
.../internal/repository/GroupDefinitionEntity.java | 161 ++++++++++
.../repository/GroupDefinitionRepository.java | 27 ++
.../service/internal/repository/GroupEntity.java | 207 +++++++++++++
.../internal/repository/GroupRepository.java | 31 ++
.../service/internal/repository/MeetingEntity.java | 138 +++++++++
.../internal/repository/MeetingRepository.java | 39 +++
.../internal/service/GroupDefinitionService.java | 55 ++++
.../service/internal/service/GroupService.java | 134 +++++++++
.../rest/GroupDefinitionRestController.java | 104 +++++++
.../group/service/rest/GroupRestController.java | 269 +++++++++++++++++
.../service/rest/MigrationRestController.java | 60 ++++
service/src/main/resources/application.yml | 67 +++++
service/src/main/resources/bootstrap.yml | 19 ++
.../db/migrations/mariadb/V1__initial_setup.sql | 100 +++++++
settings.gradle | 5 +
shared.gradle | 64 ++++
84 files changed, 5555 insertions(+)
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..95274b9
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,16 @@
+.gradle
+.idea
+**/build/
+**/target/
+
+# Ignore Gradle GUI config
+gradle-app.setting
+
+# Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored)
+!gradle-wrapper.jar
+
+*.iml
+
+*.log
+
+*.toDelete
\ No newline at end of file
diff --git a/HEADER b/HEADER
new file mode 100644
index 0000000..76f9222
--- /dev/null
+++ b/HEADER
@@ -0,0 +1,13 @@
+Copyright ${year} ${name}
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
\ No newline at end of file
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..8dada3e
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,201 @@
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "{}"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright {yyyy} {name of copyright owner}
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..bec5de8
--- /dev/null
+++ b/README.md
@@ -0,0 +1,25 @@
+# Mifos I/O Group Management
+
+[![Join the chat at https://gitter.im/mifos-initiative/mifos.io](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/mifos-initiative/mifos.io?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
+
+This project provides Group management capabilities.
+
+## Versioning
+The version numbers follow the [Semantic Versioning](http://semver.org/) scheme.
+
+In addition to MAJOR.MINOR.PATCH the following postfixes are used to indicate the development state.
+
+* BUILD-SNAPSHOT - A release currently in development.
+* M - A _milestone_ release include specific sets of functions and are released as soon as the functionality is complete.
+* RC - A _release candidate_ is a version with potential to be a final product, considered _code complete_.
+* RELEASE - _General availability_ indicates that this release is the best available version and is recommended for all usage.
+
+The versioning layout is {MAJOR}.{MINOR}.{PATCH}-{INDICATOR}[.{PATCH}]. Only milestones and release candidates can have patch versions. Some examples:
+
+1.2.3.BUILD-SNAPSHOT
+1.3.5.M.1
+1.5.7.RC.2
+2.0.0.RELEASE
+
+## License
+See [LICENSE](LICENSE) file.
diff --git a/api/build.gradle b/api/build.gradle
new file mode 100644
index 0000000..5dc9656
--- /dev/null
+++ b/api/build.gradle
@@ -0,0 +1,33 @@
+buildscript {
+ repositories {
+ jcenter()
+ }
+
+ dependencies {
+ classpath 'io.spring.gradle:dependency-management-plugin:0.6.0.RELEASE' }
+}
+
+plugins {
+ id "com.github.hierynomus.license" version "0.13.1"
+}
+
+apply from: '../shared.gradle'
+
+dependencies {
+ compile(
+ [group: 'org.springframework.cloud', name: 'spring-cloud-starter-feign'],
+ [group: 'io.mifos.core', name: 'api', version: versions.frameworkapi],
+ [group: 'org.hibernate', name: 'hibernate-validator', version: versions.validator]
+ )
+}
+
+publishing {
+ publications {
+ api(MavenPublication) {
+ from components.java
+ groupId project.group
+ artifactId project.name
+ version project.version
+ }
+ }
+}
diff --git a/api/settings.gradle b/api/settings.gradle
new file mode 100644
index 0000000..5cd7dd3
--- /dev/null
+++ b/api/settings.gradle
@@ -0,0 +1 @@
+rootProject.name = 'api'
diff --git a/api/src/main/java/io/mifos/group/api/v1/EventConstants.java b/api/src/main/java/io/mifos/group/api/v1/EventConstants.java
new file mode 100644
index 0000000..f18fe33
--- /dev/null
+++ b/api/src/main/java/io/mifos/group/api/v1/EventConstants.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2017 The Mifos Initiative
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.mifos.group.api.v1;
+
+@SuppressWarnings("unused")
+public interface EventConstants {
+
+ String DESTINATION = "group-v1";
+ String SELECTOR_NAME = "action";
+
+ String INITIALIZE = "initialize";
+ String SELECTOR_INITIALIZE = SELECTOR_NAME + " = '" + INITIALIZE + "'";
+
+ String POST_GROUP_DEFINITION = "post-group-definition";
+ String SELECTOR_POST_GROUP_DEFINITION = SELECTOR_NAME + " = '" + POST_GROUP_DEFINITION + "'";
+
+ String POST_GROUP = "post-group";
+ String SELECTOR_POST_GROUP = SELECTOR_NAME + " = '" + POST_GROUP + "'";
+ String PUT_GROUP = "put-group";
+ String SELECTOR_PUT_GROUP = SELECTOR_NAME + " = '" + PUT_GROUP + "'";
+ String ACTIVATE_GROUP = "activate-group";
+ String SELECTOR_ACTIVATE_GROUP = SELECTOR_NAME + " = '" + ACTIVATE_GROUP + "'";
+ String CLOSE_GROUP = "close-group";
+ String SELECTOR_CLOSE_GROUP = SELECTOR_NAME + " = '" + CLOSE_GROUP + "'";
+ String REOPEN_GROUP = "reopen-group";
+ String SELECTOR_REOPEN_GROUP = SELECTOR_NAME + " = '" + REOPEN_GROUP + "'";
+
+ String POST_MEETING = "post-meeting";
+ String SELECTOR_POST_MEETING = SELECTOR_NAME + " = '" + POST_MEETING + "'";
+ String PUT_MEETING = "put-meeting";
+ String SELECTOR_PUT_MEETING = SELECTOR_NAME + " = '" + PUT_MEETING + "'";
+}
diff --git a/api/src/main/java/io/mifos/group/api/v1/client/GroupAlreadyExists.java b/api/src/main/java/io/mifos/group/api/v1/client/GroupAlreadyExists.java
new file mode 100644
index 0000000..35fd6e2
--- /dev/null
+++ b/api/src/main/java/io/mifos/group/api/v1/client/GroupAlreadyExists.java
@@ -0,0 +1,19 @@
+/*
+ * Copyright 2017 The Mifos Initiative
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.mifos.group.api.v1.client;
+
+public class GroupAlreadyExists extends RuntimeException {
+}
diff --git a/api/src/main/java/io/mifos/group/api/v1/client/GroupClient.java b/api/src/main/java/io/mifos/group/api/v1/client/GroupClient.java
new file mode 100644
index 0000000..f1e44b9
--- /dev/null
+++ b/api/src/main/java/io/mifos/group/api/v1/client/GroupClient.java
@@ -0,0 +1,174 @@
+/*
+ * Copyright 2017 The Mifos Initiative
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.mifos.group.api.v1.client;
+
+import io.mifos.core.api.annotation.ThrowsException;
+import io.mifos.core.api.annotation.ThrowsExceptions;
+import io.mifos.core.api.util.CustomFeignClientsConfiguration;
+import io.mifos.group.api.v1.domain.AssignedEmployeeHolder;
+import io.mifos.group.api.v1.domain.SignOffMeeting;
+import io.mifos.group.api.v1.domain.Group;
+import io.mifos.group.api.v1.domain.GroupCommand;
+import io.mifos.group.api.v1.domain.GroupDefinition;
+import io.mifos.group.api.v1.domain.GroupPage;
+import io.mifos.group.api.v1.domain.Meeting;
+import org.springframework.cloud.netflix.feign.FeignClient;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.MediaType;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestMethod;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.ResponseBody;
+
+import java.util.List;
+import java.util.Set;
+
+@SuppressWarnings("unused")
+@FeignClient(name="group-v1", path="/group/v1", configuration=CustomFeignClientsConfiguration.class)
+public interface GroupClient {
+
+ @RequestMapping(
+ value = "/definitions",
+ method = RequestMethod.POST,
+ produces = MediaType.APPLICATION_JSON_VALUE,
+ consumes = MediaType.APPLICATION_JSON_VALUE
+ )
+ @ThrowsExceptions({
+ @ThrowsException(status = HttpStatus.CONFLICT, exception = GroupDefinitionAlreadyExists.class),
+ @ThrowsException(status = HttpStatus.BAD_REQUEST, exception = GroupDefinitionValidation.class)
+ })
+ void createGroupDefinition(@RequestBody final GroupDefinition groupDefinition);
+
+ @RequestMapping(
+ value = "/definitions/{identifier}",
+ method = RequestMethod.GET,
+ produces = MediaType.ALL_VALUE,
+ consumes = MediaType.APPLICATION_JSON_VALUE
+ )
+ @ResponseBody
+ @ThrowsException(status = HttpStatus.NOT_FOUND, exception = GroupDefinitionNotFound.class)
+ GroupDefinition findGroupDefinition(@PathVariable("identifier") final String identifier);
+
+ @RequestMapping(
+ value = "/definitions",
+ method = RequestMethod.GET,
+ produces = MediaType.ALL_VALUE,
+ consumes = MediaType.APPLICATION_JSON_VALUE
+ )
+ @ResponseBody
+ List<GroupDefinition> fetchGroupDefinitions();
+
+ @RequestMapping(
+ value = "/groups",
+ method = RequestMethod.POST,
+ produces = MediaType.APPLICATION_JSON_VALUE,
+ consumes = MediaType.APPLICATION_JSON_VALUE
+ )
+ @ThrowsExceptions({
+ @ThrowsException(status = HttpStatus.CONFLICT, exception = GroupAlreadyExists.class),
+ @ThrowsException(status = HttpStatus.BAD_REQUEST, exception = GroupValidationException.class)
+ })
+ void createGroup(@RequestBody final Group group);
+
+ @RequestMapping(
+ value = "/groups",
+ method = RequestMethod.GET,
+ produces = MediaType.ALL_VALUE,
+ consumes = MediaType.APPLICATION_JSON_VALUE
+ )
+ GroupPage fetchGroups(@RequestParam("employee") final String employee,
+ @RequestParam("page") final Integer page,
+ @RequestParam("size") final Integer size,
+ @RequestParam("sortColumn") final String sortColumn,
+ @RequestParam("sortDirection") final String sortDirection);
+
+ @RequestMapping(
+ value = "/groups/{identifier}",
+ method = RequestMethod.GET,
+ produces = MediaType.ALL_VALUE,
+ consumes = MediaType.APPLICATION_JSON_VALUE
+ )
+ @ThrowsException(status = HttpStatus.NOT_FOUND, exception = GroupNotFoundException.class)
+ Group findGroup(@PathVariable("identifier") final String identifier);
+
+ @RequestMapping(
+ value = "/groups/{identifier}/leaders",
+ method = RequestMethod.PUT,
+ produces = MediaType.APPLICATION_JSON_VALUE,
+ consumes = MediaType.APPLICATION_JSON_VALUE
+ )
+ @ThrowsException(status = HttpStatus.NOT_FOUND, exception = GroupNotFoundException.class)
+ void updateLeaders(@PathVariable("identifier") final String identifier, final Set<String> customerIdentifiers);
+
+ @RequestMapping(
+ value = "/groups/{identifier}/members",
+ method = RequestMethod.PUT,
+ produces = MediaType.APPLICATION_JSON_VALUE,
+ consumes = MediaType.APPLICATION_JSON_VALUE
+ )
+ @ThrowsException(status = HttpStatus.NOT_FOUND, exception = GroupNotFoundException.class)
+ void updateMembers(@PathVariable("identifier") final String identifier, final Set<String> customerIdentifiers);
+
+ @RequestMapping(
+ value = "/groups/{identifier}/employee",
+ method = RequestMethod.PUT,
+ produces = MediaType.APPLICATION_JSON_VALUE,
+ consumes = MediaType.APPLICATION_JSON_VALUE
+ )
+ @ThrowsException(status = HttpStatus.NOT_FOUND, exception = GroupNotFoundException.class)
+ void updateAssignedEmployee(@PathVariable("identifier") final String identifier,
+ final AssignedEmployeeHolder assignedEmployeeHolder);
+
+ @RequestMapping(
+ value = "/groups/{identifier}/commands",
+ method = RequestMethod.POST,
+ produces = MediaType.APPLICATION_JSON_VALUE,
+ consumes = MediaType.APPLICATION_JSON_VALUE
+ )
+ @ThrowsException(status = HttpStatus.NOT_FOUND, exception = GroupNotFoundException.class)
+ void processGroupCommand(@PathVariable("identifier") final String identifier, final GroupCommand groupCommand);
+
+ @RequestMapping(
+ value = "/groups/{identifier}/commands",
+ method = RequestMethod.GET,
+ produces = MediaType.ALL_VALUE,
+ consumes = MediaType.APPLICATION_JSON_VALUE
+ )
+ @ThrowsException(status = HttpStatus.NOT_FOUND, exception = GroupNotFoundException.class)
+ List<GroupCommand> fetchGroupCommands(@PathVariable("identifier") final String identifier);
+
+ @RequestMapping(
+ value = "/groups/{identifier}/meetings",
+ method = RequestMethod.GET,
+ produces = MediaType.ALL_VALUE,
+ consumes = MediaType.APPLICATION_JSON_VALUE
+ )
+ @ThrowsException(status = HttpStatus.NOT_FOUND, exception = GroupNotFoundException.class)
+ List<Meeting> fetchMeetings(
+ @PathVariable("identifier") final String groupIdentifier,
+ @RequestParam("upcoming") final Boolean upcoming);
+
+ @RequestMapping(
+ value = "/groups/{identifier}/meetings",
+ method = RequestMethod.PUT,
+ produces = MediaType.APPLICATION_JSON_VALUE,
+ consumes = MediaType.APPLICATION_JSON_VALUE
+ )
+ @ThrowsException(status = HttpStatus.NOT_FOUND, exception = GroupNotFoundException.class)
+ void closeMeeting(@PathVariable("identifier") final String groupIdentifier, final SignOffMeeting signOffMeeting);
+}
diff --git a/api/src/main/java/io/mifos/group/api/v1/client/GroupDefinitionAlreadyExists.java b/api/src/main/java/io/mifos/group/api/v1/client/GroupDefinitionAlreadyExists.java
new file mode 100644
index 0000000..261411a
--- /dev/null
+++ b/api/src/main/java/io/mifos/group/api/v1/client/GroupDefinitionAlreadyExists.java
@@ -0,0 +1,19 @@
+/*
+ * Copyright 2017 The Mifos Initiative
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.mifos.group.api.v1.client;
+
+public class GroupDefinitionAlreadyExists extends RuntimeException {
+}
diff --git a/api/src/main/java/io/mifos/group/api/v1/client/GroupDefinitionNotFound.java b/api/src/main/java/io/mifos/group/api/v1/client/GroupDefinitionNotFound.java
new file mode 100644
index 0000000..99e2676
--- /dev/null
+++ b/api/src/main/java/io/mifos/group/api/v1/client/GroupDefinitionNotFound.java
@@ -0,0 +1,19 @@
+/*
+ * Copyright 2017 The Mifos Initiative
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.mifos.group.api.v1.client;
+
+public class GroupDefinitionNotFound extends RuntimeException {
+}
diff --git a/api/src/main/java/io/mifos/group/api/v1/client/GroupDefinitionValidation.java b/api/src/main/java/io/mifos/group/api/v1/client/GroupDefinitionValidation.java
new file mode 100644
index 0000000..75be3a1
--- /dev/null
+++ b/api/src/main/java/io/mifos/group/api/v1/client/GroupDefinitionValidation.java
@@ -0,0 +1,19 @@
+/*
+ * Copyright 2017 The Mifos Initiative
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.mifos.group.api.v1.client;
+
+public class GroupDefinitionValidation extends RuntimeException {
+}
diff --git a/api/src/main/java/io/mifos/group/api/v1/client/GroupNotFoundException.java b/api/src/main/java/io/mifos/group/api/v1/client/GroupNotFoundException.java
new file mode 100644
index 0000000..629c5e6
--- /dev/null
+++ b/api/src/main/java/io/mifos/group/api/v1/client/GroupNotFoundException.java
@@ -0,0 +1,19 @@
+/*
+ * Copyright 2017 The Mifos Initiative
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.mifos.group.api.v1.client;
+
+public class GroupNotFoundException extends RuntimeException {
+}
diff --git a/api/src/main/java/io/mifos/group/api/v1/client/GroupValidationException.java b/api/src/main/java/io/mifos/group/api/v1/client/GroupValidationException.java
new file mode 100644
index 0000000..2a3bd0b
--- /dev/null
+++ b/api/src/main/java/io/mifos/group/api/v1/client/GroupValidationException.java
@@ -0,0 +1,19 @@
+/*
+ * Copyright 2017 The Mifos Initiative
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.mifos.group.api.v1.client;
+
+public class GroupValidationException extends RuntimeException {
+}
diff --git a/api/src/main/java/io/mifos/group/api/v1/domain/Address.java b/api/src/main/java/io/mifos/group/api/v1/domain/Address.java
new file mode 100644
index 0000000..902b255
--- /dev/null
+++ b/api/src/main/java/io/mifos/group/api/v1/domain/Address.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright 2017 The Mifos Initiative
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.mifos.group.api.v1.domain;
+
+import org.hibernate.validator.constraints.NotBlank;
+
+public class Address {
+
+ @NotBlank
+ private String street;
+ @NotBlank
+ private String city;
+ private String region;
+ private String postalCode;
+ @NotBlank
+ private String countryCode;
+ private String country;
+
+ public Address() {
+ super();
+ }
+
+ public String getStreet() {
+ return this.street;
+ }
+
+ public void setStreet(final String street) {
+ this.street = street;
+ }
+
+ public String getCity() {
+ return this.city;
+ }
+
+ public void setCity(final String city) {
+ this.city = city;
+ }
+
+ public String getRegion() {
+ return this.region;
+ }
+
+ public void setRegion(final String region) {
+ this.region = region;
+ }
+
+ public String getPostalCode() {
+ return this.postalCode;
+ }
+
+ public void setPostalCode(final String postalCode) {
+ this.postalCode = postalCode;
+ }
+
+ public String getCountryCode() {
+ return this.countryCode;
+ }
+
+ public void setCountryCode(final String countryCode) {
+ this.countryCode = countryCode;
+ }
+
+ public String getCountry() {
+ return this.country;
+ }
+
+ public void setCountry(final String country) {
+ this.country = country;
+ }
+}
diff --git a/api/src/main/java/io/mifos/group/api/v1/domain/AssignedEmployeeHolder.java b/api/src/main/java/io/mifos/group/api/v1/domain/AssignedEmployeeHolder.java
new file mode 100644
index 0000000..e2ce5d8
--- /dev/null
+++ b/api/src/main/java/io/mifos/group/api/v1/domain/AssignedEmployeeHolder.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2017 The Mifos Initiative
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.mifos.group.api.v1.domain;
+
+public class AssignedEmployeeHolder {
+
+ private String identifier;
+
+ public AssignedEmployeeHolder() {
+ super();
+ }
+
+ public String getIdentifier() {
+ return this.identifier;
+ }
+
+ public void setIdentifier(final String identifier) {
+ this.identifier = identifier;
+ }
+}
diff --git a/api/src/main/java/io/mifos/group/api/v1/domain/Attendee.java b/api/src/main/java/io/mifos/group/api/v1/domain/Attendee.java
new file mode 100644
index 0000000..147be78
--- /dev/null
+++ b/api/src/main/java/io/mifos/group/api/v1/domain/Attendee.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2017 The Mifos Initiative
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.mifos.group.api.v1.domain;
+
+import org.hibernate.validator.constraints.NotBlank;
+
+import javax.validation.constraints.NotNull;
+
+public class Attendee {
+
+ @NotBlank
+ private String customerIdentifier;
+ @NotNull
+ private Status status;
+
+ public Attendee() {
+ super();
+ }
+
+ public String getCustomerIdentifier() {
+ return this.customerIdentifier;
+ }
+
+ public void setCustomerIdentifier(final String customerIdentifier) {
+ this.customerIdentifier = customerIdentifier;
+ }
+
+ public String getStatus() {
+ return this.status.name();
+ }
+
+ public void setStatus(final String status) {
+ this.status = Status.valueOf(status);
+ }
+
+ public enum Status {
+ EXPECTED,
+ ATTENDED,
+ MISSED
+ }
+}
diff --git a/api/src/main/java/io/mifos/group/api/v1/domain/Cycle.java b/api/src/main/java/io/mifos/group/api/v1/domain/Cycle.java
new file mode 100644
index 0000000..261c651
--- /dev/null
+++ b/api/src/main/java/io/mifos/group/api/v1/domain/Cycle.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright 2017 The Mifos Initiative
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.mifos.group.api.v1.domain;
+
+import javax.validation.constraints.NotNull;
+
+public class Cycle {
+
+ @NotNull
+ private Integer numberOfMeetings;
+ @NotNull
+ private Frequency frequency;
+ private Adjustment adjustment;
+
+ public Cycle() {
+ super();
+ }
+
+ public Integer getNumberOfMeetings() {
+ return this.numberOfMeetings;
+ }
+
+ public void setNumberOfMeetings(final Integer numberOfMeetings) {
+ this.numberOfMeetings = numberOfMeetings;
+ }
+
+ public String getFrequency() {
+ return this.frequency.name();
+ }
+
+ public void setFrequency(final String frequency) {
+ this.frequency = Frequency.valueOf(frequency);
+ }
+
+ public String getAdjustment() {
+ return this.adjustment.name();
+ }
+
+ public void setAdjustment(final String adjustment) {
+ this.adjustment = Adjustment.valueOf(adjustment);
+ }
+
+ public enum Frequency {
+ DAILY,
+ WEEKLY,
+ FORTNIGHTLY,
+ MONTHLY
+ }
+
+ public enum Adjustment {
+ NEXT_BUSINESS_DAY,
+ SKIP
+ }
+}
diff --git a/api/src/main/java/io/mifos/group/api/v1/domain/Group.java b/api/src/main/java/io/mifos/group/api/v1/domain/Group.java
new file mode 100644
index 0000000..c38d955
--- /dev/null
+++ b/api/src/main/java/io/mifos/group/api/v1/domain/Group.java
@@ -0,0 +1,202 @@
+/*
+ * Copyright 2017 The Mifos Initiative
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.mifos.group.api.v1.domain;
+
+import org.hibernate.validator.constraints.NotBlank;
+
+import javax.validation.Valid;
+import javax.validation.constraints.NotNull;
+import javax.validation.constraints.Size;
+import java.util.Objects;
+import java.util.Set;
+
+public class Group {
+
+ @NotBlank
+ private String identifier;
+ @NotBlank
+ private String groupDefinitionIdentifier;
+ @NotBlank
+ private String name;
+ @Size(max = 15)
+ private Set<String> leaders;
+ @Size(min = 1, max = 60)
+ private Set<String> members;
+ @NotBlank
+ private String office;
+ @NotBlank
+ private String assignedEmployee;
+ @NotNull
+ private Integer weekday;
+ private Status status;
+ @Valid
+ private Address address;
+ private String createdOn;
+ private String createdBy;
+ private String lastModifiedOn;
+ private String lastModifiedBy;
+
+ public Group() {
+ super();
+ }
+
+ public String getGroupDefinitionIdentifier() {
+ return this.groupDefinitionIdentifier;
+ }
+
+ public void setGroupDefinitionIdentifier(final String groupDefinitionIdentifier) {
+ this.groupDefinitionIdentifier = groupDefinitionIdentifier;
+ }
+
+ public String getIdentifier() {
+ return this.identifier;
+ }
+
+ public void setIdentifier(final String identifier) {
+ this.identifier = identifier;
+ }
+
+ public String getName() {
+ return this.name;
+ }
+
+ public void setName(final String name) {
+ this.name = name;
+ }
+
+ public Set<String> getLeaders() {
+ return this.leaders;
+ }
+
+ public void setLeaders(final Set<String> leaders) {
+ this.leaders = leaders;
+ }
+
+ public Set<String> getMembers() {
+ return this.members;
+ }
+
+ public void setMembers(final Set<String> members) {
+ this.members = members;
+ }
+
+ public String getOffice() {
+ return this.office;
+ }
+
+ public void setOffice(final String office) {
+ this.office = office;
+ }
+
+ public String getAssignedEmployee() {
+ return this.assignedEmployee;
+ }
+
+ public void setAssignedEmployee(final String assignedEmployee) {
+ this.assignedEmployee = assignedEmployee;
+ }
+
+ public String getStatus() {
+ return this.status.name();
+ }
+
+ public void setStatus(final String status) {
+ this.status = Status.valueOf(status);
+ }
+
+ public Integer getWeekday() {
+ return this.weekday;
+ }
+
+ public void setWeekday(final Integer weekday) {
+ this.weekday = weekday;
+ }
+
+ public Address getAddress() {
+ return this.address;
+ }
+
+ public void setAddress(final Address address) {
+ this.address = address;
+ }
+
+ public String getCreatedOn() {
+ return this.createdOn;
+ }
+
+ public void setCreatedOn(final String createdOn) {
+ this.createdOn = createdOn;
+ }
+
+ public String getCreatedBy() {
+ return this.createdBy;
+ }
+
+ public void setCreatedBy(final String createdBy) {
+ this.createdBy = createdBy;
+ }
+
+ public String getLastModifiedOn() {
+ return this.lastModifiedOn;
+ }
+
+ public void setLastModifiedOn(final String lastModifiedOn) {
+ this.lastModifiedOn = lastModifiedOn;
+ }
+
+ public String getLastModifiedBy() {
+ return this.lastModifiedBy;
+ }
+
+ public void setLastModifiedBy(final String lastModifiedBy) {
+ this.lastModifiedBy = lastModifiedBy;
+ }
+
+ public enum Weekday {
+ MONDAY(1),
+ TUESDAY(2),
+ WEDNESDAY(3),
+ THURSDAY(4),
+ FRIDAY(5),
+ SATURDAY(6),
+ SUNDAY(7);
+
+ private Integer value;
+
+ Weekday(final Integer value) {
+ this.value = value;
+ }
+
+ public static Weekday from(final Integer dayOfWeek) {
+ for (Weekday weekday : Weekday.values()) {
+ if (Objects.equals(weekday.value, dayOfWeek)) {
+ return weekday;
+ }
+ }
+ throw new IllegalArgumentException("Unknown day of week '" + dayOfWeek + "'.");
+ }
+
+ public Integer getValue() {
+ return this.value;
+ }
+ }
+
+ public enum Status {
+ PENDING,
+ ACTIVE,
+ CLOSED
+ }
+}
diff --git a/api/src/main/java/io/mifos/group/api/v1/domain/GroupCommand.java b/api/src/main/java/io/mifos/group/api/v1/domain/GroupCommand.java
new file mode 100644
index 0000000..1941d47
--- /dev/null
+++ b/api/src/main/java/io/mifos/group/api/v1/domain/GroupCommand.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2017 The Mifos Initiative
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.mifos.group.api.v1.domain;
+
+public class GroupCommand {
+
+ private Action action;
+ private String note;
+ private String createdBy;
+ private String createdOn;
+
+ public GroupCommand() {
+ super();
+ }
+
+ public String getAction() {
+ return this.action.name();
+ }
+
+ public void setAction(final String action) {
+ this.action = Action.valueOf(action);
+ }
+
+ public String getNote() {
+ return this.note;
+ }
+
+ public void setNote(final String note) {
+ this.note = note;
+ }
+
+ public String getCreatedBy() {
+ return this.createdBy;
+ }
+
+ public void setCreatedBy(final String createdBy) {
+ this.createdBy = createdBy;
+ }
+
+ public String getCreatedOn() {
+ return this.createdOn;
+ }
+
+ public void setCreatedOn(final String createdOn) {
+ this.createdOn = createdOn;
+ }
+
+ public enum Action {
+ ACTIVATE,
+ CLOSE,
+ REOPEN
+ }
+}
diff --git a/api/src/main/java/io/mifos/group/api/v1/domain/GroupDefinition.java b/api/src/main/java/io/mifos/group/api/v1/domain/GroupDefinition.java
new file mode 100644
index 0000000..70f7726
--- /dev/null
+++ b/api/src/main/java/io/mifos/group/api/v1/domain/GroupDefinition.java
@@ -0,0 +1,116 @@
+/*
+ * Copyright 2017 The Mifos Initiative
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.mifos.group.api.v1.domain;
+
+import org.hibernate.validator.constraints.NotBlank;
+
+import javax.validation.Valid;
+import javax.validation.constraints.Min;
+import javax.validation.constraints.NotNull;
+
+public class GroupDefinition {
+
+ @NotBlank
+ private String identifier;
+ private String description;
+ @NotNull
+ @Min(1L)
+ private Integer minimalSize;
+ @Min(2L)
+ private Integer maximalSize;
+ @Valid
+ private Cycle cycle;
+ private String createOn;
+ private String createdBy;
+ private String lastModifiedOn;
+ private String lastModifiedBy;
+
+ public GroupDefinition() {
+ super();
+ }
+
+ public String getIdentifier() {
+ return this.identifier;
+ }
+
+ public void setIdentifier(final String identifier) {
+ this.identifier = identifier;
+ }
+
+ public String getDescription() {
+ return this.description;
+ }
+
+ public void setDescription(final String description) {
+ this.description = description;
+ }
+
+ public Integer getMinimalSize() {
+ return this.minimalSize;
+ }
+
+ public void setMinimalSize(final Integer minimalSize) {
+ this.minimalSize = minimalSize;
+ }
+
+ public Integer getMaximalSize() {
+ return this.maximalSize;
+ }
+
+ public void setMaximalSize(final Integer maximalSize) {
+ this.maximalSize = maximalSize;
+ }
+
+ public Cycle getCycle() {
+ return this.cycle;
+ }
+
+ public void setCycle(final Cycle cycle) {
+ this.cycle = cycle;
+ }
+
+ public String getCreateOn() {
+ return this.createOn;
+ }
+
+ public void setCreateOn(final String createOn) {
+ this.createOn = createOn;
+ }
+
+ public String getCreatedBy() {
+ return this.createdBy;
+ }
+
+ public void setCreatedBy(final String createdBy) {
+ this.createdBy = createdBy;
+ }
+
+ public String getLastModifiedOn() {
+ return this.lastModifiedOn;
+ }
+
+ public void setLastModifiedOn(final String lastModifiedOn) {
+ this.lastModifiedOn = lastModifiedOn;
+ }
+
+ public String getLastModifiedBy() {
+ return this.lastModifiedBy;
+ }
+
+ public void setLastModifiedBy(final String lastModifiedBy) {
+ this.lastModifiedBy = lastModifiedBy;
+ }
+}
diff --git a/api/src/main/java/io/mifos/group/api/v1/domain/GroupPage.java b/api/src/main/java/io/mifos/group/api/v1/domain/GroupPage.java
new file mode 100644
index 0000000..7ddd739
--- /dev/null
+++ b/api/src/main/java/io/mifos/group/api/v1/domain/GroupPage.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2017 The Mifos Initiative
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.mifos.group.api.v1.domain;
+
+import java.util.List;
+
+public class GroupPage {
+
+ private List<Group> groups;
+ private Integer totalPages;
+ private Long totalElements;
+
+ public GroupPage() {
+ super();
+ }
+
+ public List<Group> getGroups() {
+ return this.groups;
+ }
+
+ public void setGroups(final List<Group> groups) {
+ this.groups = groups;
+ }
+
+ public Integer getTotalPages() {
+ return this.totalPages;
+ }
+
+ public void setTotalPages(final Integer totalPages) {
+ this.totalPages = totalPages;
+ }
+
+ public Long getTotalElements() {
+ return this.totalElements;
+ }
+
+ public void setTotalElements(final Long totalElements) {
+ this.totalElements = totalElements;
+ }
+}
diff --git a/api/src/main/java/io/mifos/group/api/v1/domain/Meeting.java b/api/src/main/java/io/mifos/group/api/v1/domain/Meeting.java
new file mode 100644
index 0000000..7770ac3
--- /dev/null
+++ b/api/src/main/java/io/mifos/group/api/v1/domain/Meeting.java
@@ -0,0 +1,127 @@
+/*
+ * Copyright 2017 The Mifos Initiative
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.mifos.group.api.v1.domain;
+
+import org.hibernate.validator.constraints.NotBlank;
+import org.hibernate.validator.constraints.NotEmpty;
+
+import javax.validation.Valid;
+import javax.validation.constraints.NotNull;
+import java.util.Set;
+
+public class Meeting {
+
+ @NotBlank
+ private Integer meetingSequence;
+ @NotBlank
+ private String groupIdentifier;
+ @NotNull
+ private Integer currentCycle;
+ @NotEmpty
+ @Valid
+ private Set<Attendee> attendees;
+ private String scheduledFor;
+ @Valid
+ private Address location;
+ private String heldOn;
+ private Long duration;
+ private String createdOn;
+ private String createdBy;
+
+ public Meeting() {
+ super();
+ }
+
+ public Integer getMeetingSequence() {
+ return this.meetingSequence;
+ }
+
+ public void setMeetingSequence(final Integer meetingSequence) {
+ this.meetingSequence = meetingSequence;
+ }
+
+ public String getGroupIdentifier() {
+ return this.groupIdentifier;
+ }
+
+ public void setGroupIdentifier(final String groupIdentifier) {
+ this.groupIdentifier = groupIdentifier;
+ }
+
+ public Integer getCurrentCycle() {
+ return this.currentCycle;
+ }
+
+ public void setCurrentCycle(final Integer currentCycle) {
+ this.currentCycle = currentCycle;
+ }
+
+ public Set<Attendee> getAttendees() {
+ return this.attendees;
+ }
+
+ public void setAttendees(final Set<Attendee> attendees) {
+ this.attendees = attendees;
+ }
+
+ public String getScheduledFor() {
+ return this.scheduledFor;
+ }
+
+ public void setScheduledFor(final String scheduledFor) {
+ this.scheduledFor = scheduledFor;
+ }
+
+ public Address getLocation() {
+ return this.location;
+ }
+
+ public void setLocation(final Address location) {
+ this.location = location;
+ }
+
+ public String getHeldOn() {
+ return this.heldOn;
+ }
+
+ public void setHeldOn(final String heldOn) {
+ this.heldOn = heldOn;
+ }
+
+ public Long getDuration() {
+ return this.duration;
+ }
+
+ public void setDuration(final Long duration) {
+ this.duration = duration;
+ }
+
+ public String getCreatedOn() {
+ return this.createdOn;
+ }
+
+ public void setCreatedOn(final String createdOn) {
+ this.createdOn = createdOn;
+ }
+
+ public String getCreatedBy() {
+ return this.createdBy;
+ }
+
+ public void setCreatedBy(final String createdBy) {
+ this.createdBy = createdBy;
+ }
+}
diff --git a/api/src/main/java/io/mifos/group/api/v1/domain/SignOffMeeting.java b/api/src/main/java/io/mifos/group/api/v1/domain/SignOffMeeting.java
new file mode 100644
index 0000000..a8e71f7
--- /dev/null
+++ b/api/src/main/java/io/mifos/group/api/v1/domain/SignOffMeeting.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright 2017 The Mifos Initiative
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.mifos.group.api.v1.domain;
+
+import javax.validation.Valid;
+import java.util.Set;
+
+public class SignOffMeeting {
+
+ @Valid
+ private Integer cycle;
+ private Integer sequence;
+ private Set<Attendee> attendees;
+ private Long duration;
+
+ public SignOffMeeting() {
+ super();
+ }
+
+ public Integer getCycle() {
+ return this.cycle;
+ }
+
+ public void setCycle(final Integer cycle) {
+ this.cycle = cycle;
+ }
+
+ public Integer getSequence() {
+ return this.sequence;
+ }
+
+ public void setSequence(final Integer sequence) {
+ this.sequence = sequence;
+ }
+
+ public Set<Attendee> getAttendees() {
+ return this.attendees;
+ }
+
+ public void setAttendees(final Set<Attendee> attendees) {
+ this.attendees = attendees;
+ }
+
+ public Long getDuration() {
+ return this.duration;
+ }
+
+ public void setDuration(final Long duration) {
+ this.duration = duration;
+ }
+}
diff --git a/build.gradle b/build.gradle
new file mode 100644
index 0000000..b8c09e6
--- /dev/null
+++ b/build.gradle
@@ -0,0 +1,29 @@
+group 'io.mifos'
+
+task publishApiToMavenLocal {
+ dependsOn gradle.includedBuild('api').task(':publishToMavenLocal')
+}
+
+task publishServiceToMavenLocal {
+ mustRunAfter publishApiToMavenLocal
+ dependsOn gradle.includedBuild('service').task(':publishToMavenLocal')
+}
+
+task publishComponentTestToMavenLocal {
+ mustRunAfter publishApiToMavenLocal
+ mustRunAfter publishServiceToMavenLocal
+ dependsOn gradle.includedBuild('component-test').task(':publishToMavenLocal')
+}
+
+task publishToMavenLocal {
+ group 'all'
+ dependsOn publishApiToMavenLocal
+ dependsOn publishServiceToMavenLocal
+ dependsOn publishComponentTestToMavenLocal
+}
+
+task prepareForTest {
+ group 'all'
+ dependsOn publishToMavenLocal
+ dependsOn gradle.includedBuild('component-test').task(':build')
+}
diff --git a/component-test/build.gradle b/component-test/build.gradle
new file mode 100644
index 0000000..b9a16bc
--- /dev/null
+++ b/component-test/build.gradle
@@ -0,0 +1,38 @@
+buildscript {
+ ext {
+ springBootVersion = '1.4.1.RELEASE'
+ }
+
+ repositories {
+ jcenter()
+ }
+
+ dependencies {
+ classpath ("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
+ }
+}
+
+plugins {
+ id "com.github.hierynomus.license" version "0.13.1"
+}
+
+apply from: '../shared.gradle'
+
+dependencies {
+ compile(
+ [group: 'io.mifos.group', name: 'api', version: project.version],
+ [group: 'io.mifos.group', name: 'service', version: project.version],
+ [group: 'io.mifos.core', name: 'api', version: versions.frameworkapi],
+ [group: 'io.mifos.core', name: 'test', version: versions.frameworktest],
+ [group: 'io.mifos.anubis', name: 'test', version: versions.frameworkanubis],
+ [group: 'org.springframework.boot', name: 'spring-boot-starter-test']
+ )
+}
+
+publishing {
+ publications {
+ mavenJava(MavenPublication) {
+ from components.java
+ }
+ }
+}
diff --git a/component-test/settings.gradle b/component-test/settings.gradle
new file mode 100644
index 0000000..b2e36e3
--- /dev/null
+++ b/component-test/settings.gradle
@@ -0,0 +1 @@
+rootProject.name = 'component-test'
diff --git a/component-test/src/main/java/io/mifos/group/TestGroup.java b/component-test/src/main/java/io/mifos/group/TestGroup.java
new file mode 100644
index 0000000..91110f9
--- /dev/null
+++ b/component-test/src/main/java/io/mifos/group/TestGroup.java
@@ -0,0 +1,275 @@
+/*
+ * Copyright 2017 The Mifos Initiative
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.mifos.group;
+
+import io.mifos.anubis.test.v1.TenantApplicationSecurityEnvironmentTestRule;
+import io.mifos.core.api.context.AutoUserContext;
+import io.mifos.core.test.env.TestEnvironment;
+import io.mifos.core.test.fixture.TenantDataStoreContextTestRule;
+import io.mifos.core.test.fixture.cassandra.CassandraInitializer;
+import io.mifos.core.test.fixture.mariadb.MariaDBInitializer;
+import io.mifos.core.test.listener.EnableEventRecording;
+import io.mifos.core.test.listener.EventRecorder;
+import io.mifos.group.api.v1.EventConstants;
+import io.mifos.group.api.v1.client.GroupClient;
+import io.mifos.group.api.v1.domain.AssignedEmployeeHolder;
+import io.mifos.group.api.v1.domain.Attendee;
+import io.mifos.group.api.v1.domain.Group;
+import io.mifos.group.api.v1.domain.GroupCommand;
+import io.mifos.group.api.v1.domain.GroupDefinition;
+import io.mifos.group.api.v1.domain.Meeting;
+import io.mifos.group.api.v1.domain.SignOffMeeting;
+import io.mifos.group.service.GroupConfiguration;
+import io.mifos.group.util.GroupDefinitionGenerator;
+import io.mifos.group.util.GroupGenerator;
+import org.apache.commons.lang3.RandomStringUtils;
+import org.junit.*;
+import org.junit.rules.RuleChain;
+import org.junit.rules.TestRule;
+import org.junit.runner.RunWith;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.cloud.netflix.feign.EnableFeignClients;
+import org.springframework.cloud.netflix.ribbon.RibbonClient;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.ComponentScan;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Import;
+import org.springframework.test.context.junit4.SpringRunner;
+
+import java.time.Clock;
+import java.time.ZonedDateTime;
+import java.time.format.DateTimeFormatter;
+import java.util.Arrays;
+import java.util.List;
+import java.util.stream.Collectors;
+
+@RunWith(SpringRunner.class)
+@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT)
+public class TestGroup {
+ private static final String APP_NAME = "group-v1";
+ private static final String TEST_USER = "ranefer";
+
+ private final static TestEnvironment testEnvironment = new TestEnvironment(APP_NAME);
+ private final static CassandraInitializer cassandraInitializer = new CassandraInitializer();
+ private final static MariaDBInitializer mariaDBInitializer = new MariaDBInitializer();
+ private final static TenantDataStoreContextTestRule tenantDataStoreContext = TenantDataStoreContextTestRule.forRandomTenantName(cassandraInitializer, mariaDBInitializer);
+
+ @ClassRule
+ public static TestRule orderClassRules = RuleChain
+ .outerRule(testEnvironment)
+ .around(cassandraInitializer)
+ .around(mariaDBInitializer)
+ .around(tenantDataStoreContext);
+
+ @Rule
+ public final TenantApplicationSecurityEnvironmentTestRule tenantApplicationSecurityEnvironment
+ = new TenantApplicationSecurityEnvironmentTestRule(testEnvironment, this::waitForInitialize);
+ @Autowired
+ private GroupClient groupClient;
+ @Autowired
+ private EventRecorder eventRecorder;
+
+ private AutoUserContext userContext;
+
+ public TestGroup() {
+ super();
+ }
+
+ @Before
+ public void prepTest() {
+ userContext = this.tenantApplicationSecurityEnvironment.createAutoUserContext(TestGroup.TEST_USER);
+ }
+
+ @After
+ public void cleanTest() {
+ userContext.close();
+ }
+
+ public boolean waitForInitialize() {
+ try {
+ return this.eventRecorder.wait(EventConstants.INITIALIZE, EventConstants.INITIALIZE);
+ } catch (final InterruptedException e) {
+ throw new IllegalStateException(e);
+ }
+ }
+
+ @Test
+ public void shouldCreateGroup() throws Exception {
+ final GroupDefinition randomGroupDefinition = GroupDefinitionGenerator.createRandomGroupDefinition();
+
+ this.groupClient.createGroupDefinition(randomGroupDefinition);
+
+ this.eventRecorder.wait(EventConstants.POST_GROUP_DEFINITION, randomGroupDefinition.getIdentifier());
+
+ final Group randomGroup = GroupGenerator.createRandomGroup(randomGroupDefinition.getIdentifier());
+
+ this.groupClient.createGroup(randomGroup);
+
+ this.eventRecorder.wait(EventConstants.POST_GROUP, randomGroup.getIdentifier());
+
+ final Group fetchedGroup = this.groupClient.findGroup(randomGroup.getIdentifier());
+ Assert.assertEquals(randomGroup.getIdentifier(), fetchedGroup.getIdentifier());
+ Assert.assertEquals(randomGroup.getGroupDefinitionIdentifier(), fetchedGroup.getGroupDefinitionIdentifier());
+ Assert.assertEquals(randomGroup.getName(), fetchedGroup.getName());
+ Assert.assertEquals(randomGroup.getOffice(), fetchedGroup.getOffice());
+ Assert.assertEquals(randomGroup.getAssignedEmployee(), fetchedGroup.getAssignedEmployee());
+ Assert.assertEquals(randomGroup.getWeekday(), fetchedGroup.getWeekday());
+ Assert.assertEquals(Group.Status.PENDING.name(), fetchedGroup.getStatus());
+ Assert.assertEquals(randomGroup.getLeaders().size(), fetchedGroup.getLeaders().size());
+ Assert.assertEquals(randomGroup.getMembers().size(), fetchedGroup.getMembers().size());
+ Assert.assertNotNull(fetchedGroup.getCreatedBy());
+ Assert.assertNotNull(fetchedGroup.getCreatedOn());
+ Assert.assertNull(fetchedGroup.getLastModifiedBy());
+ Assert.assertNull(fetchedGroup.getLastModifiedOn());
+ Assert.assertNotNull(fetchedGroup.getAddress());
+ }
+
+ @Test
+ public void shouldActivateCommand() throws Exception {
+ final GroupDefinition randomGroupDefinition = GroupDefinitionGenerator.createRandomGroupDefinition();
+ this.groupClient.createGroupDefinition(randomGroupDefinition);
+ this.eventRecorder.wait(EventConstants.POST_GROUP_DEFINITION, randomGroupDefinition.getIdentifier());
+
+ final Group randomGroup = GroupGenerator.createRandomGroup(randomGroupDefinition.getIdentifier());
+ this.groupClient.createGroup(randomGroup);
+ this.eventRecorder.wait(EventConstants.POST_GROUP, randomGroup.getIdentifier());
+
+ final Group fetchedGroup = this.groupClient.findGroup(randomGroup.getIdentifier());
+ Assert.assertEquals(Group.Status.PENDING.name(), fetchedGroup.getStatus());
+
+ final GroupCommand activate = new GroupCommand();
+ activate.setAction(GroupCommand.Action.ACTIVATE.name());
+ activate.setNote(RandomStringUtils.randomAlphanumeric(256));
+ activate.setCreatedBy(TestGroup.TEST_USER);
+ activate.setCreatedOn(ZonedDateTime.now(Clock.systemUTC()).format(DateTimeFormatter.ISO_ZONED_DATE_TIME));
+
+ this.groupClient.processGroupCommand(randomGroup.getIdentifier(), activate);
+ this.eventRecorder.wait(EventConstants.ACTIVATE_GROUP, randomGroup.getIdentifier());
+
+ final Group activatedGroup = this.groupClient.findGroup(randomGroup.getIdentifier());
+ Assert.assertEquals(Group.Status.ACTIVE.name(), activatedGroup.getStatus());
+
+ final List<GroupCommand> groupCommands = this.groupClient.fetchGroupCommands(activatedGroup.getIdentifier());
+ Assert.assertTrue(groupCommands.size() == 1);
+ final GroupCommand groupCommand = groupCommands.get(0);
+ Assert.assertEquals(activate.getAction(), groupCommand.getAction());
+ Assert.assertEquals(activate.getNote(), groupCommand.getNote());
+ Assert.assertEquals(activate.getCreatedBy(), groupCommand.getCreatedBy());
+ Assert.assertNotNull(groupCommand.getCreatedOn());
+
+ final List<Meeting> meetings = this.groupClient.fetchMeetings(activatedGroup.getIdentifier(), Boolean.FALSE);
+ Assert.assertNotNull(meetings);
+ Assert.assertEquals(randomGroupDefinition.getCycle().getNumberOfMeetings(), Integer.valueOf(meetings.size()));
+
+ final Meeting meeting2signOff = meetings.get(0);
+ final SignOffMeeting signOffMeeting = new SignOffMeeting();
+ signOffMeeting.setCycle(meeting2signOff.getCurrentCycle());
+ signOffMeeting.setSequence(meeting2signOff.getMeetingSequence());
+ signOffMeeting.setDuration(120L);
+ signOffMeeting.setAttendees(meeting2signOff.getAttendees()
+ .stream()
+ .map(attendee -> {
+ attendee.setStatus(Attendee.Status.ATTENDED.name());
+ return attendee;
+ })
+ .collect(Collectors.toSet())
+ );
+
+ this.groupClient.closeMeeting(activatedGroup.getIdentifier(), signOffMeeting);
+ this.eventRecorder.wait(EventConstants.PUT_GROUP, activatedGroup.getIdentifier());
+ }
+
+ @Test
+ public void shouldUpdateLeaders() throws Exception {
+ final GroupDefinition randomGroupDefinition = GroupDefinitionGenerator.createRandomGroupDefinition();
+ this.groupClient.createGroupDefinition(randomGroupDefinition);
+ this.eventRecorder.wait(EventConstants.POST_GROUP_DEFINITION, randomGroupDefinition.getIdentifier());
+
+ final Group randomGroup = GroupGenerator.createRandomGroup(randomGroupDefinition.getIdentifier());
+ this.groupClient.createGroup(randomGroup);
+ this.eventRecorder.wait(EventConstants.POST_GROUP, randomGroup.getIdentifier());
+
+ final int currentLeadersSize = randomGroup.getLeaders().size();
+ randomGroup.getLeaders().add(RandomStringUtils.randomAlphanumeric(32));
+ this.groupClient.updateLeaders(randomGroup.getIdentifier(), randomGroup.getLeaders());
+ this.eventRecorder.wait(EventConstants.PUT_GROUP, randomGroup.getIdentifier());
+
+ final Group fetchedGroup = this.groupClient.findGroup(randomGroup.getIdentifier());
+ Assert.assertEquals((currentLeadersSize + 1), fetchedGroup.getLeaders().size());
+ }
+
+ @Test
+ public void shouldUpdateMembers() throws Exception {
+ final GroupDefinition randomGroupDefinition = GroupDefinitionGenerator.createRandomGroupDefinition();
+ this.groupClient.createGroupDefinition(randomGroupDefinition);
+ this.eventRecorder.wait(EventConstants.POST_GROUP_DEFINITION, randomGroupDefinition.getIdentifier());
+
+ final Group randomGroup = GroupGenerator.createRandomGroup(randomGroupDefinition.getIdentifier());
+ this.groupClient.createGroup(randomGroup);
+ this.eventRecorder.wait(EventConstants.POST_GROUP, randomGroup.getIdentifier());
+
+ final int currentMembersSize = randomGroup.getMembers().size();
+ randomGroup.getMembers().addAll(Arrays.asList(
+ RandomStringUtils.randomAlphanumeric(32),
+ RandomStringUtils.randomAlphanumeric(32)
+ ));
+ this.groupClient.updateMembers(randomGroup.getIdentifier(), randomGroup.getMembers());
+ this.eventRecorder.wait(EventConstants.PUT_GROUP, randomGroup.getIdentifier());
+
+ final Group fetchedGroup = this.groupClient.findGroup(randomGroup.getIdentifier());
+ Assert.assertEquals((currentMembersSize + 2), fetchedGroup.getMembers().size());
+ }
+
+ @Test
+ public void shouldUpdateAssignedEmployee() throws Exception {
+ final GroupDefinition randomGroupDefinition = GroupDefinitionGenerator.createRandomGroupDefinition();
+ this.groupClient.createGroupDefinition(randomGroupDefinition);
+ this.eventRecorder.wait(EventConstants.POST_GROUP_DEFINITION, randomGroupDefinition.getIdentifier());
+
+ final Group randomGroup = GroupGenerator.createRandomGroup(randomGroupDefinition.getIdentifier());
+ this.groupClient.createGroup(randomGroup);
+ this.eventRecorder.wait(EventConstants.POST_GROUP, randomGroup.getIdentifier());
+
+ final AssignedEmployeeHolder anotherEmployee = new AssignedEmployeeHolder();
+ anotherEmployee.setIdentifier(RandomStringUtils.randomAlphanumeric(32));
+
+ this.groupClient.updateAssignedEmployee(randomGroup.getIdentifier(), anotherEmployee);
+ this.eventRecorder.wait(EventConstants.PUT_GROUP, randomGroup.getIdentifier());
+
+ final Group fetchedGroup = this.groupClient.findGroup(randomGroup.getIdentifier());
+ Assert.assertEquals(anotherEmployee.getIdentifier(), fetchedGroup.getAssignedEmployee());
+ }
+
+ @Configuration
+ @EnableEventRecording
+ @EnableFeignClients(basePackages = {"io.mifos.group.api.v1.client"})
+ @RibbonClient(name = APP_NAME)
+ @Import({GroupConfiguration.class})
+ @ComponentScan("io.mifos.group.listener")
+ public static class TestConfiguration {
+ public TestConfiguration() {
+ super();
+ }
+
+ @Bean()
+ public Logger logger() {
+ return LoggerFactory.getLogger("test-logger");
+ }
+ }
+}
diff --git a/component-test/src/main/java/io/mifos/group/TestGroupDefinition.java b/component-test/src/main/java/io/mifos/group/TestGroupDefinition.java
new file mode 100644
index 0000000..b33ad72
--- /dev/null
+++ b/component-test/src/main/java/io/mifos/group/TestGroupDefinition.java
@@ -0,0 +1,139 @@
+/*
+ * Copyright 2017 The Mifos Initiative
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.mifos.group;
+
+import io.mifos.anubis.test.v1.TenantApplicationSecurityEnvironmentTestRule;
+import io.mifos.core.api.context.AutoUserContext;
+import io.mifos.core.lang.TenantContextHolder;
+import io.mifos.core.test.env.TestEnvironment;
+import io.mifos.core.test.fixture.TenantDataStoreContextTestRule;
+import io.mifos.core.test.fixture.cassandra.CassandraInitializer;
+import io.mifos.core.test.fixture.mariadb.MariaDBInitializer;
+import io.mifos.core.test.listener.EnableEventRecording;
+import io.mifos.core.test.listener.EventRecorder;
+import io.mifos.group.api.v1.EventConstants;
+import io.mifos.group.api.v1.client.GroupClient;
+import io.mifos.group.api.v1.domain.GroupDefinition;
+import io.mifos.group.service.GroupConfiguration;
+import io.mifos.group.util.GroupDefinitionGenerator;
+import org.junit.*;
+import org.junit.rules.RuleChain;
+import org.junit.rules.TestRule;
+import org.junit.runner.RunWith;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.cloud.netflix.feign.EnableFeignClients;
+import org.springframework.cloud.netflix.ribbon.RibbonClient;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.ComponentScan;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Import;
+import org.springframework.test.context.junit4.SpringRunner;
+
+@RunWith(SpringRunner.class)
+@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT)
+public class TestGroupDefinition {
+ private static final String APP_NAME = "group-v1";
+ private static final String TEST_USER = "ranefer";
+
+ private final static TestEnvironment testEnvironment = new TestEnvironment(APP_NAME);
+ private final static CassandraInitializer cassandraInitializer = new CassandraInitializer();
+ private final static MariaDBInitializer mariaDBInitializer = new MariaDBInitializer();
+ private final static TenantDataStoreContextTestRule tenantDataStoreContext = TenantDataStoreContextTestRule.forRandomTenantName(cassandraInitializer, mariaDBInitializer);
+
+ @ClassRule
+ public static TestRule orderClassRules = RuleChain
+ .outerRule(testEnvironment)
+ .around(cassandraInitializer)
+ .around(mariaDBInitializer)
+ .around(tenantDataStoreContext);
+
+ @Rule
+ public final TenantApplicationSecurityEnvironmentTestRule tenantApplicationSecurityEnvironment
+ = new TenantApplicationSecurityEnvironmentTestRule(testEnvironment, this::waitForInitialize);
+
+ @Autowired
+ private GroupClient groupClient;
+ @Autowired
+ private EventRecorder eventRecorder;
+
+ private AutoUserContext userContext;
+
+ public TestGroupDefinition() {
+ super();
+ }
+
+ @Before
+ public void prepTest() {
+ userContext = this.tenantApplicationSecurityEnvironment.createAutoUserContext(TestGroupDefinition.TEST_USER);
+ }
+
+ @After
+ public void cleanTest() {
+ TenantContextHolder.clear();
+ userContext.close();
+ }
+
+ public boolean waitForInitialize() {
+ try {
+ return this.eventRecorder.wait(EventConstants.INITIALIZE, EventConstants.INITIALIZE);
+ } catch (final InterruptedException e) {
+ throw new IllegalStateException(e);
+ }
+ }
+
+ @Test
+ public void shouldCreateGroupDefinition() throws Exception {
+ final GroupDefinition randomGroupDefinition = GroupDefinitionGenerator.createRandomGroupDefinition();
+ this.groupClient.createGroupDefinition(randomGroupDefinition);
+
+ this.eventRecorder.wait(EventConstants.POST_GROUP_DEFINITION, randomGroupDefinition.getIdentifier());
+
+ final GroupDefinition fetchedGroupDefinition = this.groupClient.findGroupDefinition(randomGroupDefinition.getIdentifier());
+
+ Assert.assertEquals(randomGroupDefinition.getIdentifier(), fetchedGroupDefinition.getIdentifier());
+ Assert.assertEquals(randomGroupDefinition.getDescription(), fetchedGroupDefinition.getDescription());
+ Assert.assertEquals(randomGroupDefinition.getMinimalSize(), fetchedGroupDefinition.getMinimalSize());
+ Assert.assertEquals(randomGroupDefinition.getMaximalSize(), fetchedGroupDefinition.getMaximalSize());
+ Assert.assertNotNull(fetchedGroupDefinition.getCycle());
+ Assert.assertEquals(randomGroupDefinition.getCycle().getNumberOfMeetings(), fetchedGroupDefinition.getCycle().getNumberOfMeetings());
+ Assert.assertEquals(randomGroupDefinition.getCycle().getFrequency(), fetchedGroupDefinition.getCycle().getFrequency());
+ Assert.assertEquals(randomGroupDefinition.getCycle().getAdjustment(), fetchedGroupDefinition.getCycle().getAdjustment());
+ Assert.assertNotNull(fetchedGroupDefinition.getCreatedBy());
+ Assert.assertNotNull(fetchedGroupDefinition.getCreateOn());
+ Assert.assertNull(fetchedGroupDefinition.getLastModifiedBy());
+ Assert.assertNull(fetchedGroupDefinition.getLastModifiedOn());
+ }
+
+ @Configuration
+ @EnableEventRecording
+ @EnableFeignClients(basePackages = {"io.mifos.group.api.v1.client"})
+ @RibbonClient(name = APP_NAME)
+ @Import({GroupConfiguration.class})
+ @ComponentScan("io.mifos.group.listener")
+ public static class TestConfiguration {
+ public TestConfiguration() {
+ super();
+ }
+
+ @Bean()
+ public Logger logger() {
+ return LoggerFactory.getLogger("test-logger");
+ }
+ }
+}
diff --git a/component-test/src/main/java/io/mifos/group/listener/GroupDefinitionEventListener.java b/component-test/src/main/java/io/mifos/group/listener/GroupDefinitionEventListener.java
new file mode 100644
index 0000000..a64b355
--- /dev/null
+++ b/component-test/src/main/java/io/mifos/group/listener/GroupDefinitionEventListener.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2017 The Mifos Initiative
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.mifos.group.listener;
+
+import io.mifos.core.lang.config.TenantHeaderFilter;
+import io.mifos.core.test.listener.EventRecorder;
+import io.mifos.group.api.v1.EventConstants;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.jms.annotation.JmsListener;
+import org.springframework.messaging.handler.annotation.Header;
+import org.springframework.stereotype.Component;
+
+@SuppressWarnings("unused")
+@Component
+public class GroupDefinitionEventListener {
+
+ private final EventRecorder eventRecorder;
+
+ @Autowired
+ public GroupDefinitionEventListener(final EventRecorder eventRecorder) {
+ super();
+ this.eventRecorder = eventRecorder;
+ }
+
+ @JmsListener(
+ subscription = EventConstants.DESTINATION,
+ destination = EventConstants.DESTINATION,
+ selector = EventConstants.SELECTOR_POST_GROUP_DEFINITION
+ )
+ public void onGroupDefinitionCreated(@Header(TenantHeaderFilter.TENANT_HEADER) final String tenant,
+ final String payload) {
+ this.eventRecorder.event(tenant, EventConstants.POST_GROUP_DEFINITION, payload, String.class);
+ }
+}
diff --git a/component-test/src/main/java/io/mifos/group/listener/GroupEventListener.java b/component-test/src/main/java/io/mifos/group/listener/GroupEventListener.java
new file mode 100644
index 0000000..32446b3
--- /dev/null
+++ b/component-test/src/main/java/io/mifos/group/listener/GroupEventListener.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright 2017 The Mifos Initiative
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.mifos.group.listener;
+
+import io.mifos.core.lang.config.TenantHeaderFilter;
+import io.mifos.core.test.listener.EventRecorder;
+import io.mifos.group.api.v1.EventConstants;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.jms.annotation.JmsListener;
+import org.springframework.messaging.handler.annotation.Header;
+import org.springframework.stereotype.Component;
+
+@SuppressWarnings("unused")
+@Component
+public class GroupEventListener {
+
+ private final EventRecorder eventRecorder;
+
+ @Autowired
+ public GroupEventListener(final EventRecorder eventRecorder) {
+ super();
+ this.eventRecorder = eventRecorder;
+ }
+
+ @JmsListener(
+ subscription = EventConstants.DESTINATION,
+ destination = EventConstants.DESTINATION,
+ selector = EventConstants.SELECTOR_POST_GROUP
+ )
+ public void onGroupCreated(@Header(TenantHeaderFilter.TENANT_HEADER) final String tenant,
+ final String payload) {
+ this.eventRecorder.event(tenant, EventConstants.POST_GROUP, payload, String.class);
+ }
+
+ @JmsListener(
+ subscription = EventConstants.DESTINATION,
+ destination = EventConstants.DESTINATION,
+ selector = EventConstants.SELECTOR_ACTIVATE_GROUP
+ )
+ public void onGroupActivated(@Header(TenantHeaderFilter.TENANT_HEADER) final String tenant,
+ final String payload) {
+ this.eventRecorder.event(tenant, EventConstants.ACTIVATE_GROUP, payload, String.class);
+ }
+
+ @JmsListener(
+ subscription = EventConstants.DESTINATION,
+ destination = EventConstants.DESTINATION,
+ selector = EventConstants.SELECTOR_PUT_GROUP
+ )
+ public void onGroupUpdated(@Header(TenantHeaderFilter.TENANT_HEADER) final String tenant,
+ final String payload) {
+ this.eventRecorder.event(tenant, EventConstants.PUT_GROUP, payload, String.class);
+ }
+}
diff --git a/component-test/src/main/java/io/mifos/group/listener/MigrationEventListener.java b/component-test/src/main/java/io/mifos/group/listener/MigrationEventListener.java
new file mode 100644
index 0000000..df7c8fb
--- /dev/null
+++ b/component-test/src/main/java/io/mifos/group/listener/MigrationEventListener.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2017 The Mifos Initiative
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.mifos.group.listener;
+
+import io.mifos.core.lang.config.TenantHeaderFilter;
+import io.mifos.core.test.listener.EventRecorder;
+import io.mifos.group.api.v1.EventConstants;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.jms.annotation.JmsListener;
+import org.springframework.messaging.handler.annotation.Header;
+import org.springframework.stereotype.Component;
+
+@SuppressWarnings("unused")
+@Component
+public class MigrationEventListener {
+
+ private final EventRecorder eventRecorder;
+
+ @Autowired
+ public MigrationEventListener(final EventRecorder eventRecorder) {
+ super();
+ this.eventRecorder = eventRecorder;
+ }
+
+ @JmsListener(
+ subscription = EventConstants.DESTINATION,
+ destination = EventConstants.DESTINATION,
+ selector = EventConstants.SELECTOR_INITIALIZE
+ )
+ public void onInitialization(@Header(TenantHeaderFilter.TENANT_HEADER) final String tenant,
+ final String payload) {
+ this.eventRecorder.event(tenant, EventConstants.INITIALIZE, payload, String.class);
+ }
+}
diff --git a/component-test/src/main/java/io/mifos/group/util/GroupDefinitionGenerator.java b/component-test/src/main/java/io/mifos/group/util/GroupDefinitionGenerator.java
new file mode 100644
index 0000000..7024152
--- /dev/null
+++ b/component-test/src/main/java/io/mifos/group/util/GroupDefinitionGenerator.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2017 The Mifos Initiative
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.mifos.group.util;
+
+import io.mifos.group.api.v1.domain.Cycle;
+import io.mifos.group.api.v1.domain.GroupDefinition;
+import org.apache.commons.lang3.RandomStringUtils;
+
+public class GroupDefinitionGenerator {
+
+ private GroupDefinitionGenerator() {
+ super();
+ }
+
+ public static GroupDefinition createRandomGroupDefinition() {
+ final GroupDefinition groupDefinition = new GroupDefinition();
+ groupDefinition.setIdentifier(RandomStringUtils.randomAlphanumeric(32));
+ groupDefinition.setDescription(RandomStringUtils.randomAlphabetic(2048));
+ groupDefinition.setMinimalSize(10);
+ groupDefinition.setMaximalSize(30);
+ final Cycle cycle = new Cycle();
+ cycle.setNumberOfMeetings(25);
+ cycle.setFrequency(Cycle.Frequency.WEEKLY.name());
+ cycle.setAdjustment(Cycle.Adjustment.NEXT_BUSINESS_DAY.name());
+ groupDefinition.setCycle(cycle);
+ return groupDefinition;
+ }
+}
diff --git a/component-test/src/main/java/io/mifos/group/util/GroupGenerator.java b/component-test/src/main/java/io/mifos/group/util/GroupGenerator.java
new file mode 100644
index 0000000..d00bde4
--- /dev/null
+++ b/component-test/src/main/java/io/mifos/group/util/GroupGenerator.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2017 The Mifos Initiative
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.mifos.group.util;
+
+import io.mifos.group.api.v1.domain.Address;
+import io.mifos.group.api.v1.domain.Group;
+import org.apache.commons.lang3.RandomStringUtils;
+
+import java.util.Arrays;
+import java.util.HashSet;
+
+public class GroupGenerator {
+
+ private GroupGenerator() {
+ super();
+ }
+
+ public static Group createRandomGroup(final String definitionIdentifier) {
+ final Group group = new Group();
+ group.setIdentifier(RandomStringUtils.randomAlphanumeric(32));
+ group.setGroupDefinitionIdentifier(definitionIdentifier);
+ group.setName(RandomStringUtils.randomAlphanumeric(256));
+ group.setOffice(RandomStringUtils.randomAlphanumeric(32));
+ group.setAssignedEmployee(RandomStringUtils.randomAlphanumeric(32));
+ group.setLeaders(new HashSet<>(Arrays.asList(
+ RandomStringUtils.randomAlphanumeric(32), RandomStringUtils.randomAlphanumeric(32)
+ )));
+ group.setMembers(new HashSet<>(Arrays.asList(
+ RandomStringUtils.randomAlphanumeric(32), RandomStringUtils.randomAlphanumeric(32),
+ RandomStringUtils.randomAlphanumeric(32), RandomStringUtils.randomAlphanumeric(32),
+ RandomStringUtils.randomAlphanumeric(32), RandomStringUtils.randomAlphanumeric(32),
+ RandomStringUtils.randomAlphanumeric(32), RandomStringUtils.randomAlphanumeric(32),
+ RandomStringUtils.randomAlphanumeric(32), RandomStringUtils.randomAlphanumeric(32)
+ )));
+ group.setWeekday(Group.Weekday.WEDNESDAY.getValue());
+ final Address address = new Address();
+ address.setStreet(RandomStringUtils.randomAlphanumeric(256));
+ address.setCity(RandomStringUtils.randomAlphanumeric(256));
+ address.setRegion(RandomStringUtils.randomAlphanumeric(256));
+ address.setPostalCode(RandomStringUtils.randomAlphanumeric(2));
+ address.setCountry(RandomStringUtils.randomAlphanumeric(256));
+ address.setCountryCode(RandomStringUtils.randomAlphanumeric(2));
+ group.setAddress(address);
+ return group;
+ }
+}
diff --git a/component-test/src/main/resources/logback.xml b/component-test/src/main/resources/logback.xml
new file mode 100644
index 0000000..e0b1e5e
--- /dev/null
+++ b/component-test/src/main/resources/logback.xml
@@ -0,0 +1,28 @@
+<!--
+
+ Copyright 2017 The Mifos Initiative
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+-->
+<configuration>
+ <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
+ <encoder>
+ <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
+ </encoder>
+ </appender>
+
+ <root level="INFO">
+ <appender-ref ref="STDOUT"/>
+ </root>
+</configuration>
diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 0000000..c733004
Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000..4aca373
--- /dev/null
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,6 @@
+#Thu Mar 16 14:47:20 CET 2017
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-3.4.1-bin.zip
diff --git a/gradlew b/gradlew
new file mode 100644
index 0000000..4453cce
--- /dev/null
+++ b/gradlew
@@ -0,0 +1,172 @@
+#!/usr/bin/env sh
+
+##############################################################################
+##
+## Gradle start up script for UN*X
+##
+##############################################################################
+
+# Attempt to set APP_HOME
+# Resolve links: $0 may be a link
+PRG="$0"
+# Need this for relative symlinks.
+while [ -h "$PRG" ] ; do
+ ls=`ls -ld "$PRG"`
+ link=`expr "$ls" : '.*-> \(.*\)$'`
+ if expr "$link" : '/.*' > /dev/null; then
+ PRG="$link"
+ else
+ PRG=`dirname "$PRG"`"/$link"
+ fi
+done
+SAVED="`pwd`"
+cd "`dirname \"$PRG\"`/" >/dev/null
+APP_HOME="`pwd -P`"
+cd "$SAVED" >/dev/null
+
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$0"`
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS=""
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD="maximum"
+
+warn ( ) {
+ echo "$*"
+}
+
+die ( ) {
+ echo
+ echo "$*"
+ echo
+ exit 1
+}
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+nonstop=false
+case "`uname`" in
+ CYGWIN* )
+ cygwin=true
+ ;;
+ Darwin* )
+ darwin=true
+ ;;
+ MINGW* )
+ msys=true
+ ;;
+ NONSTOP* )
+ nonstop=true
+ ;;
+esac
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD="$JAVA_HOME/jre/sh/java"
+ else
+ JAVACMD="$JAVA_HOME/bin/java"
+ fi
+ if [ ! -x "$JAVACMD" ] ; then
+ die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+else
+ JAVACMD="java"
+ which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
+ MAX_FD_LIMIT=`ulimit -H -n`
+ if [ $? -eq 0 ] ; then
+ if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
+ MAX_FD="$MAX_FD_LIMIT"
+ fi
+ ulimit -n $MAX_FD
+ if [ $? -ne 0 ] ; then
+ warn "Could not set maximum file descriptor limit: $MAX_FD"
+ fi
+ else
+ warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
+ fi
+fi
+
+# For Darwin, add options to specify how the application appears in the dock
+if $darwin; then
+ GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
+fi
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin ; then
+ APP_HOME=`cygpath --path --mixed "$APP_HOME"`
+ CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+ JAVACMD=`cygpath --unix "$JAVACMD"`
+
+ # We build the pattern for arguments to be converted via cygpath
+ ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
+ SEP=""
+ for dir in $ROOTDIRSRAW ; do
+ ROOTDIRS="$ROOTDIRS$SEP$dir"
+ SEP="|"
+ done
+ OURCYGPATTERN="(^($ROOTDIRS))"
+ # Add a user-defined pattern to the cygpath arguments
+ if [ "$GRADLE_CYGPATTERN" != "" ] ; then
+ OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
+ fi
+ # Now convert the arguments - kludge to limit ourselves to /bin/sh
+ i=0
+ for arg in "$@" ; do
+ CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
+ CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
+
+ if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
+ eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
+ else
+ eval `echo args$i`="\"$arg\""
+ fi
+ i=$((i+1))
+ done
+ case $i in
+ (0) set -- ;;
+ (1) set -- "$args0" ;;
+ (2) set -- "$args0" "$args1" ;;
+ (3) set -- "$args0" "$args1" "$args2" ;;
+ (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+ (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+ (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+ (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+ (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+ (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+ esac
+fi
+
+# Escape application args
+save ( ) {
+ for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
+ echo " "
+}
+APP_ARGS=$(save "$@")
+
+# Collect all arguments for the java command, following the shell quoting and substitution rules
+eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
+
+# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
+if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
+ cd "$(dirname "$0")"
+fi
+
+exec "$JAVACMD" "$@"
diff --git a/gradlew.bat b/gradlew.bat
new file mode 100644
index 0000000..f955316
--- /dev/null
+++ b/gradlew.bat
@@ -0,0 +1,84 @@
+@if "%DEBUG%" == "" @echo off
+@rem ##########################################################################
+@rem
+@rem Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+set DIRNAME=%~dp0
+if "%DIRNAME%" == "" set DIRNAME=.
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS=
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if "%ERRORLEVEL%" == "0" goto init
+
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto init
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:init
+@rem Get command-line arguments, handling Windows variants
+
+if not "%OS%" == "Windows_NT" goto win9xME_args
+
+:win9xME_args
+@rem Slurp the command line arguments.
+set CMD_LINE_ARGS=
+set _SKIP=2
+
+:win9xME_args_slurp
+if "x%~1" == "x" goto execute
+
+set CMD_LINE_ARGS=%*
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
+
+:end
+@rem End local scope for the variables with windows NT shell
+if "%ERRORLEVEL%"=="0" goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
+exit /b 1
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/service/build.gradle b/service/build.gradle
new file mode 100644
index 0000000..09e8a22
--- /dev/null
+++ b/service/build.gradle
@@ -0,0 +1,63 @@
+buildscript {
+ ext {
+ springBootVersion = '1.4.1.RELEASE'
+ }
+
+ repositories {
+ jcenter()
+ }
+
+ dependencies {
+ classpath ("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
+ }
+}
+
+plugins {
+ id "com.github.hierynomus.license" version "0.13.1"
+}
+
+apply from: '../shared.gradle'
+
+apply plugin: 'spring-boot'
+
+springBoot {
+ executable = true
+ classifier = 'boot'
+}
+
+dependencies {
+ compile(
+ [group: 'org.springframework.cloud', name: 'spring-cloud-starter-config'],
+ [group: 'org.springframework.cloud', name: 'spring-cloud-starter-eureka'],
+ [group: 'org.springframework.boot', name: 'spring-boot-starter-jetty'],
+ [group: 'io.mifos.group', name: 'api', version: project.version],
+ [group: 'io.mifos.anubis', name: 'library', version: versions.frameworkanubis],
+ [group: 'com.google.code.gson', name: 'gson'],
+ [group: 'io.mifos.core', name: 'lang', version: versions.frameworklang],
+ [group: 'io.mifos.core', name: 'async', version: versions.frameworkasync],
+ [group: 'io.mifos.core', name: 'cassandra', version: versions.frameworkcassandra],
+ [group: 'io.mifos.core', name: 'mariadb', version: versions.frameworkmariadb],
+ [group: 'io.mifos.core', name: 'command', version: versions.frameworkcommand],
+ [group: 'org.hibernate', name: 'hibernate-validator', version: versions.validator]
+ )
+}
+
+publishToMavenLocal.dependsOn bootRepackage
+
+publishing {
+ publications {
+ service(MavenPublication) {
+ from components.java
+ groupId project.group
+ artifactId project.name
+ version project.version
+ }
+ bootService(MavenPublication) {
+ // "boot" jar
+ artifact ("$buildDir/libs/$project.name-$version-boot.jar")
+ groupId project.group
+ artifactId ("$project.name-boot")
+ version project.version
+ }
+ }
+}
diff --git a/service/settings.gradle b/service/settings.gradle
new file mode 100644
index 0000000..1ed471d
--- /dev/null
+++ b/service/settings.gradle
@@ -0,0 +1 @@
+rootProject.name = 'service'
diff --git a/service/src/main/java/io/mifos/group/service/GroupApplication.java b/service/src/main/java/io/mifos/group/service/GroupApplication.java
new file mode 100644
index 0000000..1dac37d
--- /dev/null
+++ b/service/src/main/java/io/mifos/group/service/GroupApplication.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2016 The Mifos Initiative.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.mifos.group.service;
+
+import org.springframework.boot.SpringApplication;
+
+public class GroupApplication {
+
+ public GroupApplication() {
+ super();
+ }
+
+ public static void main(String[] args) {
+ SpringApplication.run(GroupConfiguration.class, args);
+ }
+}
diff --git a/service/src/main/java/io/mifos/group/service/GroupConfiguration.java b/service/src/main/java/io/mifos/group/service/GroupConfiguration.java
new file mode 100644
index 0000000..0ecceeb
--- /dev/null
+++ b/service/src/main/java/io/mifos/group/service/GroupConfiguration.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright 2016 The Mifos Initiative.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.mifos.group.service;
+
+import io.mifos.anubis.config.EnableAnubis;
+import io.mifos.core.async.config.EnableAsync;
+import io.mifos.core.cassandra.config.EnableCassandra;
+import io.mifos.core.command.config.EnableCommandProcessing;
+import io.mifos.core.lang.config.EnableServiceException;
+import io.mifos.core.lang.config.EnableTenantContext;
+import io.mifos.core.mariadb.config.EnableMariaDB;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
+import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.ComponentScan;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
+
+@SuppressWarnings("WeakerAccess")
+@Configuration
+@EnableAutoConfiguration
+@EnableDiscoveryClient
+@EnableAsync
+@EnableTenantContext
+@EnableCassandra
+@EnableMariaDB
+@EnableCommandProcessing
+@EnableAnubis
+@EnableServiceException
+@ComponentScan({
+ "io.mifos.group.service.rest",
+ "io.mifos.group.service.internal.service",
+ "io.mifos.group.service.internal.repository",
+ "io.mifos.group.service.internal.command.handler"
+})
+@EnableJpaRepositories({
+ "io.mifos.group.service.internal.repository"
+})
+public class GroupConfiguration {
+
+ public GroupConfiguration() {
+ super();
+ }
+
+ @Bean(name = ServiceConstants.LOGGER_NAME)
+ public Logger logger() {
+ return LoggerFactory.getLogger(ServiceConstants.LOGGER_NAME);
+ }
+}
diff --git a/service/src/main/java/io/mifos/group/service/ServiceConstants.java b/service/src/main/java/io/mifos/group/service/ServiceConstants.java
new file mode 100644
index 0000000..07ae9a0
--- /dev/null
+++ b/service/src/main/java/io/mifos/group/service/ServiceConstants.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2016 The Mifos Initiative.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.mifos.group.service;
+
+public interface ServiceConstants {
+ String LOGGER_NAME = "group-logger";
+}
diff --git a/service/src/main/java/io/mifos/group/service/internal/command/ActivateGroupCommand.java b/service/src/main/java/io/mifos/group/service/internal/command/ActivateGroupCommand.java
new file mode 100644
index 0000000..6e03951
--- /dev/null
+++ b/service/src/main/java/io/mifos/group/service/internal/command/ActivateGroupCommand.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2016 The Mifos Initiative.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.mifos.group.service.internal.command;
+
+import io.mifos.group.api.v1.domain.GroupCommand;
+
+public class ActivateGroupCommand {
+
+ private final String identifier;
+ private final GroupCommand groupCommand;
+
+ public ActivateGroupCommand(final String identifier, final GroupCommand groupCommand) {
+ super();
+ this.identifier = identifier;
+ this.groupCommand = groupCommand;
+ }
+
+ public String identifier() {
+ return this.identifier;
+ }
+
+ public GroupCommand groupCommand() {
+ return this.groupCommand;
+ }
+}
diff --git a/service/src/main/java/io/mifos/group/service/internal/command/CloseGroupCommand.java b/service/src/main/java/io/mifos/group/service/internal/command/CloseGroupCommand.java
new file mode 100644
index 0000000..e599458
--- /dev/null
+++ b/service/src/main/java/io/mifos/group/service/internal/command/CloseGroupCommand.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2016 The Mifos Initiative.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.mifos.group.service.internal.command;
+
+import io.mifos.group.api.v1.domain.GroupCommand;
+
+public class CloseGroupCommand {
+
+ private final String identifier;
+ private final GroupCommand groupCommand;
+
+ public CloseGroupCommand(final String identifier, final GroupCommand groupCommand) {
+ super();
+ this.identifier = identifier;
+ this.groupCommand = groupCommand;
+ }
+
+ public String identifier() {
+ return this.identifier;
+ }
+
+ public GroupCommand groupCommand() {
+ return this.groupCommand;
+ }
+}
diff --git a/service/src/main/java/io/mifos/group/service/internal/command/CreateGroupCommand.java b/service/src/main/java/io/mifos/group/service/internal/command/CreateGroupCommand.java
new file mode 100644
index 0000000..88515db
--- /dev/null
+++ b/service/src/main/java/io/mifos/group/service/internal/command/CreateGroupCommand.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2016 The Mifos Initiative.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.mifos.group.service.internal.command;
+
+import io.mifos.group.api.v1.domain.Group;
+
+public class CreateGroupCommand {
+
+ private final Group group;
+
+ public CreateGroupCommand(final Group group) {
+ super();
+ this.group = group;
+ }
+
+ public Group group() {
+ return this.group;
+ }
+}
diff --git a/service/src/main/java/io/mifos/group/service/internal/command/CreateGroupDefinitionCommand.java b/service/src/main/java/io/mifos/group/service/internal/command/CreateGroupDefinitionCommand.java
new file mode 100644
index 0000000..5c85f8d
--- /dev/null
+++ b/service/src/main/java/io/mifos/group/service/internal/command/CreateGroupDefinitionCommand.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2016 The Mifos Initiative.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.mifos.group.service.internal.command;
+
+import io.mifos.group.api.v1.domain.GroupDefinition;
+
+public class CreateGroupDefinitionCommand {
+
+ private final GroupDefinition groupDefinition;
+
+ public CreateGroupDefinitionCommand(final GroupDefinition groupDefinition) {
+ super();
+ this.groupDefinition = groupDefinition;
+ }
+
+ public GroupDefinition groupDefinition() {
+ return this.groupDefinition;
+ }
+}
diff --git a/service/src/main/java/io/mifos/group/service/internal/command/InitializeServiceCommand.java b/service/src/main/java/io/mifos/group/service/internal/command/InitializeServiceCommand.java
new file mode 100644
index 0000000..a5e28da
--- /dev/null
+++ b/service/src/main/java/io/mifos/group/service/internal/command/InitializeServiceCommand.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright 2016 The Mifos Initiative.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.mifos.group.service.internal.command;
+
+public class InitializeServiceCommand {
+
+ public InitializeServiceCommand() {
+ super();
+ }
+}
diff --git a/service/src/main/java/io/mifos/group/service/internal/command/ReopenGroupCommand.java b/service/src/main/java/io/mifos/group/service/internal/command/ReopenGroupCommand.java
new file mode 100644
index 0000000..0e83570
--- /dev/null
+++ b/service/src/main/java/io/mifos/group/service/internal/command/ReopenGroupCommand.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2016 The Mifos Initiative.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.mifos.group.service.internal.command;
+
+import io.mifos.group.api.v1.domain.GroupCommand;
+
+public class ReopenGroupCommand {
+
+ private final String identifier;
+ private final GroupCommand groupCommand;
+
+ public ReopenGroupCommand(final String identifier, final GroupCommand groupCommand) {
+ super();
+ this.identifier = identifier;
+ this.groupCommand = groupCommand;
+ }
+
+ public String identifier() {
+ return this.identifier;
+ }
+
+ public GroupCommand groupCommand() {
+ return this.groupCommand;
+ }
+}
diff --git a/service/src/main/java/io/mifos/group/service/internal/command/SignOffMeetingCommand.java b/service/src/main/java/io/mifos/group/service/internal/command/SignOffMeetingCommand.java
new file mode 100644
index 0000000..0144b32
--- /dev/null
+++ b/service/src/main/java/io/mifos/group/service/internal/command/SignOffMeetingCommand.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2016 The Mifos Initiative.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.mifos.group.service.internal.command;
+
+import io.mifos.group.api.v1.domain.SignOffMeeting;
+
+public class SignOffMeetingCommand {
+
+ private final String groupIdentifier;
+ private final SignOffMeeting signOffMeeting;
+
+ public SignOffMeetingCommand(final String groupIdentifier, final SignOffMeeting signOffMeeting) {
+ super();
+ this.groupIdentifier = groupIdentifier;
+ this.signOffMeeting = signOffMeeting;
+ }
+
+ public String groupIdentifier() {
+ return this.groupIdentifier;
+ }
+
+ public SignOffMeeting signOffMeeting() {
+ return this.signOffMeeting;
+ }
+}
diff --git a/service/src/main/java/io/mifos/group/service/internal/command/UpdateAssignedEmployeeCommand.java b/service/src/main/java/io/mifos/group/service/internal/command/UpdateAssignedEmployeeCommand.java
new file mode 100644
index 0000000..e664ac1
--- /dev/null
+++ b/service/src/main/java/io/mifos/group/service/internal/command/UpdateAssignedEmployeeCommand.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2016 The Mifos Initiative.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.mifos.group.service.internal.command;
+
+public class UpdateAssignedEmployeeCommand {
+
+ private final String identifier;
+ private final String employeeIdentifier;
+
+ public UpdateAssignedEmployeeCommand(final String identifier, final String employeeIdentifier) {
+ super();
+ this.identifier = identifier;
+ this.employeeIdentifier = employeeIdentifier;
+ }
+
+ public String identifier() {
+ return this.identifier;
+ }
+
+ public String employeeIdentifier() {
+ return this.employeeIdentifier;
+ }
+}
diff --git a/service/src/main/java/io/mifos/group/service/internal/command/UpdateLeadersCommand.java b/service/src/main/java/io/mifos/group/service/internal/command/UpdateLeadersCommand.java
new file mode 100644
index 0000000..7167aeb
--- /dev/null
+++ b/service/src/main/java/io/mifos/group/service/internal/command/UpdateLeadersCommand.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2016 The Mifos Initiative.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.mifos.group.service.internal.command;
+
+import java.util.Set;
+
+public class UpdateLeadersCommand {
+
+ private final String identifier;
+ private final Set<String> customerIdentifiers;
+
+ public UpdateLeadersCommand(final String identifier, final Set<String> customerIdentifiers) {
+ super();
+ this.identifier = identifier;
+ this.customerIdentifiers = customerIdentifiers;
+ }
+
+ public String identifier() {
+ return this.identifier;
+ }
+
+ public Set<String> customerIdentifiers() {
+ return this.customerIdentifiers;
+ }
+}
diff --git a/service/src/main/java/io/mifos/group/service/internal/command/UpdateMembersCommand.java b/service/src/main/java/io/mifos/group/service/internal/command/UpdateMembersCommand.java
new file mode 100644
index 0000000..935257a
--- /dev/null
+++ b/service/src/main/java/io/mifos/group/service/internal/command/UpdateMembersCommand.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2016 The Mifos Initiative.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.mifos.group.service.internal.command;
+
+import java.util.Set;
+
+public class UpdateMembersCommand {
+
+ private final String identifier;
+ private final Set<String> customerIdentifiers;
+
+ public UpdateMembersCommand(final String identifier, final Set<String> customerIdentifiers) {
+ super();
+ this.identifier = identifier;
+ this.customerIdentifiers = customerIdentifiers;
+ }
+
+ public String identifier() {
+ return this.identifier;
+ }
+
+ public Set<String> customerIdentifiers() {
+ return this.customerIdentifiers;
+ }
+}
diff --git a/service/src/main/java/io/mifos/group/service/internal/command/handler/GroupAggregate.java b/service/src/main/java/io/mifos/group/service/internal/command/handler/GroupAggregate.java
new file mode 100644
index 0000000..49074ce
--- /dev/null
+++ b/service/src/main/java/io/mifos/group/service/internal/command/handler/GroupAggregate.java
@@ -0,0 +1,332 @@
+/*
+ * Copyright 2016 The Mifos Initiative.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.mifos.group.service.internal.command.handler;
+
+import io.mifos.core.api.util.UserContextHolder;
+import io.mifos.core.command.annotation.Aggregate;
+import io.mifos.core.command.annotation.CommandHandler;
+import io.mifos.core.command.annotation.EventEmitter;
+import io.mifos.core.lang.DateConverter;
+import io.mifos.core.lang.ServiceException;
+import io.mifos.group.service.internal.command.ActivateGroupCommand;
+import io.mifos.group.service.internal.command.CloseGroupCommand;
+import io.mifos.group.service.internal.command.CreateGroupCommand;
+import io.mifos.group.service.internal.command.CreateGroupDefinitionCommand;
+import io.mifos.group.service.internal.command.ReopenGroupCommand;
+import io.mifos.group.service.internal.command.SignOffMeetingCommand;
+import io.mifos.group.service.internal.command.UpdateAssignedEmployeeCommand;
+import io.mifos.group.service.internal.command.UpdateLeadersCommand;
+import io.mifos.group.service.internal.command.UpdateMembersCommand;
+import io.mifos.group.service.internal.repository.AddressEntity;
+import io.mifos.group.service.internal.repository.AddressRepository;
+import io.mifos.group.service.internal.repository.AttendeeEntity;
+import io.mifos.group.service.internal.repository.AttendeeRepository;
+import io.mifos.group.service.internal.repository.GroupCommandEntity;
+import io.mifos.group.service.internal.repository.GroupCommandRepository;
+import io.mifos.group.service.internal.repository.GroupDefinitionEntity;
+import io.mifos.group.service.internal.repository.GroupDefinitionRepository;
+import io.mifos.group.service.internal.repository.GroupEntity;
+import io.mifos.group.service.internal.repository.GroupRepository;
+import io.mifos.group.service.internal.repository.MeetingEntity;
+import io.mifos.group.service.internal.repository.MeetingRepository;
+import io.mifos.group.api.v1.EventConstants;
+import io.mifos.group.api.v1.domain.*;
+import io.mifos.group.service.internal.mapper.AddressMapper;
+import io.mifos.group.service.internal.mapper.GroupCommandMapper;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.transaction.annotation.Transactional;
+import org.springframework.util.StringUtils;
+
+import java.time.Clock;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.temporal.ChronoField;
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+@SuppressWarnings("unused")
+@Aggregate
+public class GroupAggregate {
+
+ private final GroupDefinitionRepository groupDefinitionRepository;
+ private final GroupRepository groupRepository;
+ private final GroupCommandRepository groupCommandRepository;
+ private final MeetingRepository meetingRepository;
+ private final AttendeeRepository attendeeRepository;
+ private final AddressRepository addressRepository;
+
+ @Autowired
+ public GroupAggregate(final GroupDefinitionRepository groupDefinitionRepository,
+ final GroupRepository groupRepository,
+ final GroupCommandRepository groupCommandRepository,
+ final MeetingRepository meetingRepository,
+ final AttendeeRepository attendeeRepository,
+ final AddressRepository addressRepository) {
+ super();
+ this.groupDefinitionRepository = groupDefinitionRepository;
+ this.groupRepository = groupRepository;
+ this.groupCommandRepository = groupCommandRepository;
+ this.meetingRepository = meetingRepository;
+ this.attendeeRepository = attendeeRepository;
+ this.addressRepository = addressRepository;
+ }
+
+ @Transactional
+ @CommandHandler
+ @EventEmitter(selectorName = EventConstants.SELECTOR_NAME, selectorValue = EventConstants.POST_GROUP_DEFINITION)
+ public String createDefinition(final CreateGroupDefinitionCommand createGroupDefinitionCommand) {
+ final GroupDefinition groupDefinition = createGroupDefinitionCommand.groupDefinition();
+ final GroupDefinitionEntity groupDefinitionEntity = new GroupDefinitionEntity();
+ groupDefinitionEntity.setIdentifier(groupDefinition.getIdentifier());
+ groupDefinitionEntity.setDescription(groupDefinition.getDescription());
+ groupDefinitionEntity.setMinimalSize(groupDefinition.getMinimalSize());
+ groupDefinitionEntity.setMaximalSize(groupDefinition.getMaximalSize());
+ final Cycle cycle = groupDefinition.getCycle();
+ groupDefinitionEntity.setNumberOfMeetings(cycle.getNumberOfMeetings());
+ groupDefinitionEntity.setFrequency(cycle.getFrequency());
+ groupDefinitionEntity.setAdjustment(cycle.getAdjustment());
+ groupDefinitionEntity.setCreatedBy(UserContextHolder.checkedGetUser());
+ groupDefinitionEntity.setCreatedOn(LocalDateTime.now(Clock.systemUTC()));
+ this.groupDefinitionRepository.save(groupDefinitionEntity);
+
+ return groupDefinition.getIdentifier();
+ }
+
+ @Transactional
+ @CommandHandler
+ @EventEmitter(selectorName = EventConstants.SELECTOR_NAME, selectorValue = EventConstants.POST_GROUP)
+ public String createGroup(final CreateGroupCommand createGroupCommand) {
+ final Group group = createGroupCommand.group();
+ final GroupDefinitionEntity groupDefinitionEntity =
+ this.groupDefinitionRepository.findByIdentifier(group.getGroupDefinitionIdentifier())
+ .orElseThrow(
+ () -> ServiceException.notFound("Group definition {0} not found.", group.getGroupDefinitionIdentifier())
+ );
+
+ final AddressEntity savedAddress = this.addressRepository.save(AddressMapper.map(group.getAddress()));
+
+ final GroupEntity groupEntity = new GroupEntity();
+ groupEntity.setGroupDefinition(groupDefinitionEntity);
+ groupEntity.setIdentifier(group.getIdentifier());
+ groupEntity.setName(group.getName());
+ groupEntity.setLeaders(StringUtils.collectionToCommaDelimitedString(group.getLeaders()));
+ groupEntity.setMembers(StringUtils.collectionToCommaDelimitedString(group.getMembers()));
+ groupEntity.setOffice(group.getOffice());
+ groupEntity.setAddressEntity(savedAddress);
+ groupEntity.setAssignedEmployee(group.getAssignedEmployee());
+ groupEntity.setWeekday(group.getWeekday());
+ groupEntity.setGroupStatus(Group.Status.PENDING.name());
+ groupEntity.setCreatedBy(UserContextHolder.checkedGetUser());
+ groupEntity.setCreatedOn(LocalDateTime.now(Clock.systemUTC()));
+ this.groupRepository.save(groupEntity);
+ return group.getIdentifier();
+ }
+
+ @Transactional
+ @CommandHandler
+ @EventEmitter(selectorName = EventConstants.SELECTOR_NAME, selectorValue = EventConstants.ACTIVATE_GROUP)
+ public String activateGroup(final ActivateGroupCommand activateGroupCommand) {
+ this.groupRepository.findByIdentifier(activateGroupCommand.identifier())
+ .ifPresent(groupEntity -> {
+ final GroupEntity savedGroupEntity = this.processCommandInternally(groupEntity, activateGroupCommand.groupCommand());
+ this.createMeetingSchedule(groupEntity.getGroupDefinition(), savedGroupEntity);
+ });
+ return activateGroupCommand.identifier();
+ }
+
+ @Transactional
+ @CommandHandler
+ @EventEmitter(selectorName = EventConstants.SELECTOR_NAME, selectorValue = EventConstants.CLOSE_GROUP)
+ public String closeGroup(final CloseGroupCommand closeGroupCommand) {
+ this.groupRepository.findByIdentifier(closeGroupCommand.identifier())
+ .ifPresent(groupEntity -> {
+ final List<MeetingEntity> currentMeetings =
+ this.meetingRepository.findByGroupEntityAndCurrentCycleOrderByMeetingSequenceDesc(groupEntity, groupEntity.getCurrentCycle());
+ if (currentMeetings.stream().anyMatch(meetingEntity -> meetingEntity.getHeldOn() == null)) {
+ throw ServiceException.conflict("Not all meetings for group {0} signed off.", closeGroupCommand.identifier());
+ }
+ this.processCommandInternally(groupEntity, closeGroupCommand.groupCommand());
+ });
+ return closeGroupCommand.identifier();
+ }
+
+ @Transactional
+ @CommandHandler
+ @EventEmitter(selectorName = EventConstants.SELECTOR_NAME, selectorValue = EventConstants.REOPEN_GROUP)
+ public String reopenGroup(final ReopenGroupCommand reopenGroupCommand) {
+ this.groupRepository.findByIdentifier(reopenGroupCommand.identifier())
+ .ifPresent(groupEntity -> {
+ final GroupEntity savedGroupEntity = this.processCommandInternally(groupEntity, reopenGroupCommand.groupCommand());
+ this.createMeetingSchedule(groupEntity.getGroupDefinition(), savedGroupEntity);
+ });
+ return reopenGroupCommand.identifier();
+ }
+
+ @Transactional
+ @CommandHandler
+ @EventEmitter(selectorName = EventConstants.SELECTOR_NAME, selectorValue = EventConstants.PUT_GROUP)
+ public String updateLeaders(final UpdateLeadersCommand updateLeadersCommand) {
+ this.groupRepository.findByIdentifier(updateLeadersCommand.identifier())
+ .ifPresent(groupEntity -> {
+ groupEntity.setLeaders(StringUtils.collectionToCommaDelimitedString(updateLeadersCommand.customerIdentifiers()));
+ groupEntity.setLastModifiedBy(UserContextHolder.checkedGetUser());
+ groupEntity.setLastModifiedOn(LocalDateTime.now(Clock.systemUTC()));
+ this.groupRepository.save(groupEntity);
+ });
+ return updateLeadersCommand.identifier();
+ }
+
+ @Transactional
+ @CommandHandler
+ @EventEmitter(selectorName = EventConstants.SELECTOR_NAME, selectorValue = EventConstants.PUT_GROUP)
+ public String updateMembers(final UpdateMembersCommand updateMembersCommand) {
+ this.groupRepository.findByIdentifier(updateMembersCommand.identifier())
+ .ifPresent(groupEntity -> {
+ groupEntity.setMembers(StringUtils.collectionToCommaDelimitedString(updateMembersCommand.customerIdentifiers()));
+ groupEntity.setLastModifiedBy(UserContextHolder.checkedGetUser());
+ groupEntity.setLastModifiedOn(LocalDateTime.now(Clock.systemUTC()));
+ this.groupRepository.save(groupEntity);
+ });
+ return updateMembersCommand.identifier();
+ }
+
+ @Transactional
+ @CommandHandler
+ @EventEmitter(selectorName = EventConstants.SELECTOR_NAME, selectorValue = EventConstants.PUT_GROUP)
+ public String updateAssignedEmployee(final UpdateAssignedEmployeeCommand updateAssignedEmployeeCommand) {
+ this.groupRepository.findByIdentifier(updateAssignedEmployeeCommand.identifier())
+ .ifPresent(groupEntity -> {
+ groupEntity.setAssignedEmployee(updateAssignedEmployeeCommand.employeeIdentifier());
+ groupEntity.setLastModifiedBy(UserContextHolder.checkedGetUser());
+ groupEntity.setLastModifiedOn(LocalDateTime.now(Clock.systemUTC()));
+ this.groupRepository.save(groupEntity);
+ });
+ return updateAssignedEmployeeCommand.identifier();
+ }
+
+ @Transactional
+ @CommandHandler
+ @EventEmitter(selectorName = EventConstants.SELECTOR_NAME, selectorValue = EventConstants.PUT_GROUP)
+ public String signOffMeeting(final SignOffMeetingCommand signOffMeetingCommand) {
+ this.groupRepository.findByIdentifier(signOffMeetingCommand.groupIdentifier())
+ .ifPresent(groupEntity -> {
+ final SignOffMeeting signOffMeeting = signOffMeetingCommand.signOffMeeting();
+ this.meetingRepository
+ .findByGroupEntityAndCurrentCycleAndMeetingSequence(groupEntity,
+ signOffMeeting.getCycle(), signOffMeeting.getSequence())
+ .ifPresent(meetingEntity -> {
+ meetingEntity.setDuration(signOffMeeting.getDuration());
+ meetingEntity.setHeldOn(LocalDate.now(Clock.systemUTC()));
+ this.meetingRepository.save(meetingEntity);
+
+ final List<AttendeeEntity> attendeeEntities = this.attendeeRepository.findByMeeting(meetingEntity);
+ attendeeEntities.forEach(attendeeEntity -> signOffMeeting.getAttendees()
+ .stream()
+ .filter(attendee -> attendee.getCustomerIdentifier().equals(attendeeEntity.getCustomerIdentifier()))
+ .findFirst()
+ .ifPresent(attendee -> {
+ attendeeEntity.setStatus(attendee.getStatus());
+ this.attendeeRepository.save(attendeeEntity);
+ }));
+ });
+ });
+ return signOffMeetingCommand.groupIdentifier();
+ }
+
+ private void createMeetingSchedule(final GroupDefinitionEntity groupDefinitionEntity, final GroupEntity groupEntity) {
+ final Integer numberOfMeetings = groupDefinitionEntity.getNumberOfMeetings();
+ final Cycle.Frequency frequency = Cycle.Frequency.valueOf(groupDefinitionEntity.getFrequency());
+ final Cycle.Adjustment adjustment = Cycle.Adjustment.valueOf(groupDefinitionEntity.getAdjustment());
+ final Group.Weekday weekday = Group.Weekday.from(groupEntity.getWeekday());
+
+ final Set<String> members = StringUtils.commaDelimitedListToSet(groupEntity.getMembers());
+
+ LocalDate meeting = LocalDate.now(Clock.systemUTC());
+ if (frequency != Cycle.Frequency.DAILY) {
+ meeting = meeting.with(ChronoField.DAY_OF_WEEK, weekday.getValue());
+ }
+
+ for (int i = 0; i < numberOfMeetings; i++) {
+ switch (frequency) {
+ case DAILY:
+ meeting = meeting.plusDays(1L);
+ break;
+ case WEEKLY:
+ meeting = meeting.plusWeeks(1L);
+ break;
+ case FORTNIGHTLY:
+ meeting = meeting.plusWeeks(2L);
+ break;
+ case MONTHLY:
+ meeting.plusMonths(1L);
+ break;
+ }
+
+ final MeetingEntity meetingEntity = new MeetingEntity();
+ meetingEntity.setGroupEntity(groupEntity);
+ meetingEntity.setCurrentCycle(groupEntity.getCurrentCycle());
+ meetingEntity.setMeetingSequence((i + 1));
+ meetingEntity.setScheduledFor(meeting);
+ meetingEntity.setCreatedBy(UserContextHolder.checkedGetUser());
+ meetingEntity.setCreatedOn(LocalDateTime.now(Clock.systemUTC()));
+ final MeetingEntity savedMeeting = this.meetingRepository.save(meetingEntity);
+
+ this.attendeeRepository.save(
+ members
+ .stream()
+ .map(member -> {
+ final AttendeeEntity attendeeEntity = new AttendeeEntity();
+ attendeeEntity.setMeeting(savedMeeting);
+ attendeeEntity.setCustomerIdentifier(member);
+ attendeeEntity.setStatus(Attendee.Status.EXPECTED.name());
+ return attendeeEntity;
+ })
+ .collect(Collectors.toList())
+ );
+ }
+ }
+
+ private GroupEntity processCommandInternally(final GroupEntity groupEntity, final GroupCommand groupCommand) {
+ this.saveGroupCommand(groupEntity, groupCommand);
+
+ final GroupCommand.Action action = GroupCommand.Action.valueOf(groupCommand.getAction());
+ switch (action) {
+ case ACTIVATE:
+ groupEntity.setGroupStatus(Group.Status.ACTIVE.name());
+ groupEntity.setCurrentCycle(groupEntity.getCurrentCycle() + 1);
+ break;
+ case CLOSE:
+ groupEntity.setGroupStatus(Group.Status.CLOSED.name());
+ break;
+ case REOPEN:
+ groupEntity.setGroupStatus(Group.Status.PENDING.name());
+ groupEntity.setCurrentCycle(groupEntity.getCurrentCycle() + 1);
+ break;
+ default:
+ throw ServiceException.badRequest("Unsupported command {}.", action.name());
+ }
+ groupEntity.setLastModifiedBy(groupCommand.getCreatedBy());
+ groupEntity.setLastModifiedOn(DateConverter.fromIsoString(groupCommand.getCreatedOn()));
+ return this.groupRepository.save(groupEntity);
+ }
+
+ private void saveGroupCommand(final GroupEntity groupEntity, final GroupCommand groupCommand) {
+ final GroupCommandEntity groupCommandEntity = GroupCommandMapper.map(groupCommand);
+ groupCommandEntity.setGroup(groupEntity);
+ this.groupCommandRepository.save(groupCommandEntity);
+ }
+}
diff --git a/service/src/main/java/io/mifos/group/service/internal/command/handler/MigrationAggregate.java b/service/src/main/java/io/mifos/group/service/internal/command/handler/MigrationAggregate.java
new file mode 100644
index 0000000..3d93d3b
--- /dev/null
+++ b/service/src/main/java/io/mifos/group/service/internal/command/handler/MigrationAggregate.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2016 The Mifos Initiative.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.mifos.group.service.internal.command.handler;
+
+import io.mifos.core.command.annotation.Aggregate;
+import io.mifos.core.command.annotation.CommandHandler;
+import io.mifos.core.command.annotation.EventEmitter;
+import io.mifos.core.mariadb.domain.FlywayFactoryBean;
+import io.mifos.group.api.v1.EventConstants;
+import io.mifos.group.service.ServiceConstants;
+import io.mifos.group.service.internal.command.InitializeServiceCommand;
+import org.slf4j.Logger;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.transaction.annotation.Transactional;
+
+import javax.sql.DataSource;
+
+@SuppressWarnings("unused")
+@Aggregate
+public class MigrationAggregate {
+
+ private final Logger logger;
+ private final DataSource dataSource;
+ private final FlywayFactoryBean flywayFactoryBean;
+
+ @Autowired
+ public MigrationAggregate(@Qualifier(ServiceConstants.LOGGER_NAME) final Logger logger,
+ final DataSource dataSource,
+ final FlywayFactoryBean flywayFactoryBean) {
+ super();
+ this.logger = logger;
+ this.dataSource = dataSource;
+ this.flywayFactoryBean = flywayFactoryBean;
+ }
+
+ @Transactional
+ @CommandHandler
+ @EventEmitter(selectorName = EventConstants.SELECTOR_NAME, selectorValue = EventConstants.INITIALIZE)
+ public String initialize(final InitializeServiceCommand initializeServiceCommand) {
+ this.logger.debug("Start service migration.");
+ this.flywayFactoryBean.create(this.dataSource).migrate();
+ return EventConstants.INITIALIZE;
+ }
+}
diff --git a/service/src/main/java/io/mifos/group/service/internal/mapper/AddressMapper.java b/service/src/main/java/io/mifos/group/service/internal/mapper/AddressMapper.java
new file mode 100644
index 0000000..7ff897a
--- /dev/null
+++ b/service/src/main/java/io/mifos/group/service/internal/mapper/AddressMapper.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2016 The Mifos Initiative.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.mifos.group.service.internal.mapper;
+
+import io.mifos.group.service.internal.repository.AddressEntity;
+import io.mifos.group.api.v1.domain.Address;
+
+public class AddressMapper {
+
+ private AddressMapper() {
+ super();
+ }
+
+ public static Address map(final AddressEntity addressEntity) {
+ final Address address = new Address();
+ address.setStreet(addressEntity.getStreet());
+ address.setCity(addressEntity.getCity());
+ address.setRegion(addressEntity.getRegion());
+ address.setPostalCode(addressEntity.getPostalCode());
+ address.setCountry(addressEntity.getCountry());
+ address.setCountryCode(addressEntity.getCountryCode());
+ return address;
+ }
+
+ public static AddressEntity map(final Address address) {
+ final AddressEntity addressEntity = new AddressEntity();
+ addressEntity.setStreet(address.getStreet());
+ addressEntity.setCity(address.getCity());
+ addressEntity.setRegion(address.getRegion());
+ addressEntity.setPostalCode(address.getPostalCode());
+ addressEntity.setCountry(address.getCountry());
+ addressEntity.setCountryCode(address.getCountryCode());
+ return addressEntity;
+ }
+}
diff --git a/service/src/main/java/io/mifos/group/service/internal/mapper/AttendeeMapper.java b/service/src/main/java/io/mifos/group/service/internal/mapper/AttendeeMapper.java
new file mode 100644
index 0000000..58cc7bb
--- /dev/null
+++ b/service/src/main/java/io/mifos/group/service/internal/mapper/AttendeeMapper.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2016 The Mifos Initiative.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.mifos.group.service.internal.mapper;
+
+import io.mifos.group.api.v1.domain.Attendee;
+import io.mifos.group.service.internal.repository.AttendeeEntity;
+
+public class AttendeeMapper {
+
+ private AttendeeMapper() {
+ super();
+ }
+
+ public static Attendee map(final AttendeeEntity attendeeEntity) {
+ final Attendee attendee = new Attendee();
+ attendee.setCustomerIdentifier(attendeeEntity.getCustomerIdentifier());
+ attendee.setStatus(attendeeEntity.getStatus());
+ return attendee;
+ }
+}
diff --git a/service/src/main/java/io/mifos/group/service/internal/mapper/GroupCommandMapper.java b/service/src/main/java/io/mifos/group/service/internal/mapper/GroupCommandMapper.java
new file mode 100644
index 0000000..c1bbca0
--- /dev/null
+++ b/service/src/main/java/io/mifos/group/service/internal/mapper/GroupCommandMapper.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2016 The Mifos Initiative.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.mifos.group.service.internal.mapper;
+
+import io.mifos.core.api.util.UserContextHolder;
+import io.mifos.core.lang.DateConverter;
+import io.mifos.group.service.internal.repository.GroupCommandEntity;
+import io.mifos.group.api.v1.domain.GroupCommand;
+
+import java.time.Clock;
+import java.time.LocalDateTime;
+
+public class GroupCommandMapper {
+
+ private GroupCommandMapper() {
+ super();
+ }
+
+ public static GroupCommand map(final GroupCommandEntity groupCommandEntity) {
+ final GroupCommand groupCommand = new GroupCommand();
+ groupCommand.setAction(groupCommandEntity.getAction());
+ groupCommand.setNote(groupCommandEntity.getNote());
+ groupCommand.setCreatedBy(groupCommandEntity.getCreatedBy());
+ groupCommand.setCreatedOn(DateConverter.toIsoString(groupCommandEntity.getCreatedOn()));
+ return groupCommand;
+ }
+
+ public static GroupCommandEntity map(final GroupCommand groupCommand) {
+ final GroupCommandEntity groupCommandEntity = new GroupCommandEntity();
+ groupCommandEntity.setAction(groupCommand.getAction());
+ groupCommandEntity.setNote(groupCommand.getNote());
+ groupCommandEntity.setCreatedBy(UserContextHolder.checkedGetUser());
+ groupCommandEntity.setCreatedOn(LocalDateTime.now(Clock.systemUTC()));
+ return groupCommandEntity;
+ }
+}
diff --git a/service/src/main/java/io/mifos/group/service/internal/mapper/GroupDefinitionMapper.java b/service/src/main/java/io/mifos/group/service/internal/mapper/GroupDefinitionMapper.java
new file mode 100644
index 0000000..88cf2c4
--- /dev/null
+++ b/service/src/main/java/io/mifos/group/service/internal/mapper/GroupDefinitionMapper.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2016 The Mifos Initiative.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.mifos.group.service.internal.mapper;
+
+import io.mifos.core.lang.DateConverter;
+import io.mifos.group.service.internal.repository.GroupDefinitionEntity;
+import io.mifos.group.api.v1.domain.Cycle;
+import io.mifos.group.api.v1.domain.GroupDefinition;
+
+public class GroupDefinitionMapper {
+
+ private GroupDefinitionMapper() {
+ super();
+ }
+
+ public static GroupDefinition map(final GroupDefinitionEntity groupDefinitionEntity) {
+ final GroupDefinition groupDefinition = new GroupDefinition();
+ groupDefinition.setIdentifier(groupDefinitionEntity.getIdentifier());
+ groupDefinition.setDescription(groupDefinitionEntity.getDescription());
+ groupDefinition.setMinimalSize(groupDefinitionEntity.getMinimalSize());
+ groupDefinition.setMaximalSize(groupDefinitionEntity.getMaximalSize());
+ groupDefinition.setCreateOn(DateConverter.toIsoString(groupDefinitionEntity.getCreatedOn()));
+ groupDefinition.setCreatedBy(groupDefinitionEntity.getCreatedBy());
+
+ if (groupDefinitionEntity.getLastModifiedOn() != null) {
+ groupDefinition.setLastModifiedOn(DateConverter.toIsoString(groupDefinitionEntity.getLastModifiedOn()));
+ groupDefinition.setLastModifiedBy(groupDefinitionEntity.getLastModifiedBy());
+ }
+
+ final Cycle cycle = new Cycle();
+ cycle.setNumberOfMeetings(groupDefinitionEntity.getNumberOfMeetings());
+ cycle.setFrequency(groupDefinitionEntity.getFrequency());
+ cycle.setAdjustment(groupDefinitionEntity.getAdjustment());
+ groupDefinition.setCycle(cycle);
+
+ return groupDefinition;
+ }
+}
diff --git a/service/src/main/java/io/mifos/group/service/internal/mapper/GroupMapper.java b/service/src/main/java/io/mifos/group/service/internal/mapper/GroupMapper.java
new file mode 100644
index 0000000..a345c2a
--- /dev/null
+++ b/service/src/main/java/io/mifos/group/service/internal/mapper/GroupMapper.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2016 The Mifos Initiative.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.mifos.group.service.internal.mapper;
+
+import io.mifos.core.lang.DateConverter;
+import io.mifos.group.service.internal.repository.GroupEntity;
+import io.mifos.group.api.v1.domain.Group;
+import org.springframework.util.StringUtils;
+
+public class GroupMapper {
+
+ private GroupMapper() {
+ super();
+ }
+
+ public static Group map(final GroupEntity groupEntity) {
+ final Group group = new Group();
+ group.setIdentifier(groupEntity.getIdentifier());
+ group.setGroupDefinitionIdentifier(groupEntity.getGroupDefinition().getIdentifier());
+ group.setName(groupEntity.getName());
+ group.setLeaders(StringUtils.commaDelimitedListToSet(groupEntity.getLeaders()));
+ group.setMembers(StringUtils.commaDelimitedListToSet(groupEntity.getMembers()));
+ group.setOffice(groupEntity.getOffice());
+ group.setAssignedEmployee(groupEntity.getAssignedEmployee());
+ group.setWeekday(groupEntity.getWeekday());
+ group.setStatus(groupEntity.getGroupStatus());
+ group.setCreatedOn(DateConverter.toIsoString(groupEntity.getCreatedOn()));
+ group.setCreatedBy(groupEntity.getCreatedBy());
+ if (groupEntity.getLastModifiedOn() != null) {
+ group.setLastModifiedOn(DateConverter.toIsoString(groupEntity.getLastModifiedOn()));
+ group.setLastModifiedBy(groupEntity.getLastModifiedBy());
+ }
+ return group;
+ }
+}
diff --git a/service/src/main/java/io/mifos/group/service/internal/mapper/MeetingMapper.java b/service/src/main/java/io/mifos/group/service/internal/mapper/MeetingMapper.java
new file mode 100644
index 0000000..9e67548
--- /dev/null
+++ b/service/src/main/java/io/mifos/group/service/internal/mapper/MeetingMapper.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2016 The Mifos Initiative.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.mifos.group.service.internal.mapper;
+
+import io.mifos.core.lang.DateConverter;
+import io.mifos.group.api.v1.domain.Meeting;
+import io.mifos.group.service.internal.repository.MeetingEntity;
+
+public class MeetingMapper {
+
+ private MeetingMapper() {
+ super();
+ }
+
+ public static Meeting map(final MeetingEntity meetingEntity) {
+ final Meeting meeting = new Meeting();
+ meeting.setCurrentCycle(meetingEntity.getCurrentCycle());
+ meeting.setMeetingSequence(meetingEntity.getMeetingSequence());
+ meeting.setScheduledFor(DateConverter.toIsoString(meetingEntity.getScheduledFor()));
+ if (meetingEntity.getHeldOn() != null) {
+ meeting.setHeldOn(DateConverter.toIsoString(meetingEntity.getHeldOn()));
+ }
+ if (meetingEntity.getCreatedBy() != null) {
+ meeting.setCreatedBy(meetingEntity.getCreatedBy());
+ meeting.setCreatedOn(DateConverter.toIsoString(meetingEntity.getCreatedOn()));
+ }
+ meeting.setDuration(meetingEntity.getDuration());
+ return meeting;
+ }
+}
diff --git a/service/src/main/java/io/mifos/group/service/internal/repository/AddressEntity.java b/service/src/main/java/io/mifos/group/service/internal/repository/AddressEntity.java
new file mode 100644
index 0000000..f456da2
--- /dev/null
+++ b/service/src/main/java/io/mifos/group/service/internal/repository/AddressEntity.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright 2016 The Mifos Initiative.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.mifos.group.service.internal.repository;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.GeneratedValue;
+import javax.persistence.GenerationType;
+import javax.persistence.Id;
+import javax.persistence.Table;
+
+@Entity
+@Table(name = "ptah_addresses")
+public class AddressEntity {
+
+ @Id
+ @GeneratedValue(strategy = GenerationType.IDENTITY)
+ @Column(name = "id")
+ private Long id;
+ @Column(name = "street")
+ private String street;
+ @Column(name = "city")
+ private String city;
+ @Column(name = "postal_code")
+ private String postalCode;
+ @Column(name = "region")
+ private String region;
+ @Column(name = "country_code")
+ private String countryCode;
+ @Column(name = "country")
+ private String country;
+
+ public AddressEntity() {
+ super();
+ }
+
+ public Long getId() {
+ return this.id;
+ }
+
+ public void setId(final Long id) {
+ this.id = id;
+ }
+
+ public String getStreet() {
+ return this.street;
+ }
+
+ public void setStreet(final String street) {
+ this.street = street;
+ }
+
+ public String getCity() {
+ return this.city;
+ }
+
+ public void setCity(final String city) {
+ this.city = city;
+ }
+
+ public String getPostalCode() {
+ return this.postalCode;
+ }
+
+ public void setPostalCode(final String postalCode) {
+ this.postalCode = postalCode;
+ }
+
+ public String getRegion() {
+ return this.region;
+ }
+
+ public void setRegion(final String region) {
+ this.region = region;
+ }
+
+ public String getCountryCode() {
+ return this.countryCode;
+ }
+
+ public void setCountryCode(final String countryCode) {
+ this.countryCode = countryCode;
+ }
+
+ public String getCountry() {
+ return this.country;
+ }
+
+ public void setCountry(final String country) {
+ this.country = country;
+ }
+}
diff --git a/service/src/main/java/io/mifos/group/service/internal/repository/AddressRepository.java b/service/src/main/java/io/mifos/group/service/internal/repository/AddressRepository.java
new file mode 100644
index 0000000..b3dfdfb
--- /dev/null
+++ b/service/src/main/java/io/mifos/group/service/internal/repository/AddressRepository.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright 2016 The Mifos Initiative.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.mifos.group.service.internal.repository;
+
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.stereotype.Repository;
+
+@Repository
+public interface AddressRepository extends JpaRepository<AddressEntity, Long> {
+}
diff --git a/service/src/main/java/io/mifos/group/service/internal/repository/AttendeeEntity.java b/service/src/main/java/io/mifos/group/service/internal/repository/AttendeeEntity.java
new file mode 100644
index 0000000..f0b37db
--- /dev/null
+++ b/service/src/main/java/io/mifos/group/service/internal/repository/AttendeeEntity.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright 2016 The Mifos Initiative.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.mifos.group.service.internal.repository;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.FetchType;
+import javax.persistence.GeneratedValue;
+import javax.persistence.GenerationType;
+import javax.persistence.Id;
+import javax.persistence.JoinColumn;
+import javax.persistence.ManyToOne;
+import javax.persistence.Table;
+
+@Entity
+@Table(name = "ptah_attendees")
+public class AttendeeEntity {
+
+ @Id
+ @GeneratedValue(strategy = GenerationType.IDENTITY)
+ @Column(name = "id", nullable = false)
+ private Long id;
+ @ManyToOne(fetch = FetchType.LAZY)
+ @JoinColumn(name = "meeting_id", nullable = false)
+ private MeetingEntity meeting;
+ @Column(name = "customer_identifier", nullable = false)
+ private String customerIdentifier;
+ @Column(name = "a_status", nullable = false)
+ private String status;
+
+ public AttendeeEntity() {
+ super();
+ }
+
+ public Long getId() {
+ return this.id;
+ }
+
+ public void setId(final Long id) {
+ this.id = id;
+ }
+
+ public MeetingEntity getMeeting() {
+ return this.meeting;
+ }
+
+ public void setMeeting(final MeetingEntity meeting) {
+ this.meeting = meeting;
+ }
+
+ public String getCustomerIdentifier() {
+ return this.customerIdentifier;
+ }
+
+ public void setCustomerIdentifier(final String customerIdentifier) {
+ this.customerIdentifier = customerIdentifier;
+ }
+
+ public String getStatus() {
+ return this.status;
+ }
+
+ public void setStatus(final String status) {
+ this.status = status;
+ }
+}
diff --git a/service/src/main/java/io/mifos/group/service/internal/repository/AttendeeRepository.java b/service/src/main/java/io/mifos/group/service/internal/repository/AttendeeRepository.java
new file mode 100644
index 0000000..b718aa6
--- /dev/null
+++ b/service/src/main/java/io/mifos/group/service/internal/repository/AttendeeRepository.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2016 The Mifos Initiative.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.mifos.group.service.internal.repository;
+
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.stereotype.Repository;
+
+import java.util.List;
+
+@Repository
+public interface AttendeeRepository extends JpaRepository<AttendeeEntity, Long> {
+
+ List<AttendeeEntity> findByMeeting(final MeetingEntity meetingEntity);
+}
diff --git a/service/src/main/java/io/mifos/group/service/internal/repository/GroupCommandEntity.java b/service/src/main/java/io/mifos/group/service/internal/repository/GroupCommandEntity.java
new file mode 100644
index 0000000..332c02e
--- /dev/null
+++ b/service/src/main/java/io/mifos/group/service/internal/repository/GroupCommandEntity.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright 2016 The Mifos Initiative.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.mifos.group.service.internal.repository;
+
+import io.mifos.core.mariadb.util.LocalDateTimeConverter;
+
+import javax.persistence.Column;
+import javax.persistence.Convert;
+import javax.persistence.Entity;
+import javax.persistence.FetchType;
+import javax.persistence.GeneratedValue;
+import javax.persistence.GenerationType;
+import javax.persistence.Id;
+import javax.persistence.JoinColumn;
+import javax.persistence.ManyToOne;
+import javax.persistence.Table;
+import java.time.LocalDateTime;
+
+@Entity
+@Table(name = "ptah_group_commands")
+public class GroupCommandEntity {
+
+ @Id
+ @GeneratedValue(strategy = GenerationType.IDENTITY)
+ @Column(name = "id", nullable = false)
+ private Long id;
+ @ManyToOne(fetch = FetchType.LAZY)
+ @JoinColumn(name = "group_id", nullable = false)
+ private GroupEntity group;
+ @Column(name = "a_action")
+ private String action;
+ @Column(name = "note")
+ private String note;
+ @Column(name = "created_on", nullable = false)
+ @Convert(converter = LocalDateTimeConverter.class)
+ private LocalDateTime createdOn;
+ @Column(name = "created_by", nullable = false)
+ private String createdBy;
+
+ public GroupCommandEntity() {
+ super();
+ }
+
+ public Long getId() {
+ return this.id;
+ }
+
+ public void setId(final Long id) {
+ this.id = id;
+ }
+
+ public GroupEntity getGroup() {
+ return this.group;
+ }
+
+ public void setGroup(final GroupEntity group) {
+ this.group = group;
+ }
+
+ public String getAction() {
+ return this.action;
+ }
+
+ public void setAction(final String action) {
+ this.action = action;
+ }
+
+ public String getNote() {
+ return this.note;
+ }
+
+ public void setNote(final String note) {
+ this.note = note;
+ }
+
+ public LocalDateTime getCreatedOn() {
+ return this.createdOn;
+ }
+
+ public void setCreatedOn(final LocalDateTime createdOn) {
+ this.createdOn = createdOn;
+ }
+
+ public String getCreatedBy() {
+ return this.createdBy;
+ }
+
+ public void setCreatedBy(final String createdBy) {
+ this.createdBy = createdBy;
+ }
+}
diff --git a/service/src/main/java/io/mifos/group/service/internal/repository/GroupCommandRepository.java b/service/src/main/java/io/mifos/group/service/internal/repository/GroupCommandRepository.java
new file mode 100644
index 0000000..9313514
--- /dev/null
+++ b/service/src/main/java/io/mifos/group/service/internal/repository/GroupCommandRepository.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2016 The Mifos Initiative.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.mifos.group.service.internal.repository;
+
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.stereotype.Repository;
+
+import java.util.List;
+
+@Repository
+public interface GroupCommandRepository extends JpaRepository<GroupCommandEntity, Long> {
+
+ List<GroupCommandEntity> findByGroup(final GroupEntity group);
+}
diff --git a/service/src/main/java/io/mifos/group/service/internal/repository/GroupDefinitionEntity.java b/service/src/main/java/io/mifos/group/service/internal/repository/GroupDefinitionEntity.java
new file mode 100644
index 0000000..926e832
--- /dev/null
+++ b/service/src/main/java/io/mifos/group/service/internal/repository/GroupDefinitionEntity.java
@@ -0,0 +1,161 @@
+/*
+ * Copyright 2016 The Mifos Initiative.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.mifos.group.service.internal.repository;
+
+import io.mifos.core.mariadb.util.LocalDateTimeConverter;
+
+import javax.persistence.Column;
+import javax.persistence.Convert;
+import javax.persistence.Entity;
+import javax.persistence.GeneratedValue;
+import javax.persistence.GenerationType;
+import javax.persistence.Id;
+import javax.persistence.Table;
+import java.time.LocalDateTime;
+
+@Entity
+@Table(name = "ptah_group_definitions")
+public class GroupDefinitionEntity {
+
+ @Id
+ @GeneratedValue(strategy = GenerationType.IDENTITY)
+ @Column(name = "id", nullable = false)
+ private Long id;
+ @Column(name = "identifier", nullable = false)
+ private String identifier;
+ @Column(name = "description")
+ private String description;
+ @Column(name = "minimal_size", nullable = false)
+ private Integer minimalSize;
+ @Column(name = "maximal_size", nullable = false)
+ private Integer maximalSize;
+ @Column(name = "number_of_meetings", nullable = false)
+ private Integer numberOfMeetings;
+ @Column(name = "frequency", nullable = false)
+ private String frequency;
+ @Column(name = "adjustment")
+ private String adjustment;
+ @Column(name = "created_on", nullable = false)
+ @Convert(converter = LocalDateTimeConverter.class)
+ private LocalDateTime createdOn;
+ @Column(name = "created_by", nullable = false)
+ private String createdBy;
+ @Column(name = "last_modified_on")
+ @Convert(converter = LocalDateTimeConverter.class)
+ private LocalDateTime lastModifiedOn;
+ @Column(name = "last_modified_by")
+ private String lastModifiedBy;
+
+ public GroupDefinitionEntity() {
+ super();
+ }
+
+ public Long getId() {
+ return this.id;
+ }
+
+ public void setId(final Long id) {
+ this.id = id;
+ }
+
+ public String getIdentifier() {
+ return this.identifier;
+ }
+
+ public void setIdentifier(final String identifier) {
+ this.identifier = identifier;
+ }
+
+ public String getDescription() {
+ return this.description;
+ }
+
+ public void setDescription(final String description) {
+ this.description = description;
+ }
+
+ public Integer getMinimalSize() {
+ return this.minimalSize;
+ }
+
+ public void setMinimalSize(final Integer minimalSize) {
+ this.minimalSize = minimalSize;
+ }
+
+ public Integer getMaximalSize() {
+ return this.maximalSize;
+ }
+
+ public void setMaximalSize(final Integer maximalSize) {
+ this.maximalSize = maximalSize;
+ }
+
+ public Integer getNumberOfMeetings() {
+ return this.numberOfMeetings;
+ }
+
+ public void setNumberOfMeetings(final Integer numberOfMeetings) {
+ this.numberOfMeetings = numberOfMeetings;
+ }
+
+ public String getFrequency() {
+ return this.frequency;
+ }
+
+ public void setFrequency(final String frequency) {
+ this.frequency = frequency;
+ }
+
+ public String getAdjustment() {
+ return this.adjustment;
+ }
+
+ public void setAdjustment(final String adjustment) {
+ this.adjustment = adjustment;
+ }
+
+ public LocalDateTime getCreatedOn() {
+ return this.createdOn;
+ }
+
+ public void setCreatedOn(final LocalDateTime createdOn) {
+ this.createdOn = createdOn;
+ }
+
+ public String getCreatedBy() {
+ return this.createdBy;
+ }
+
+ public void setCreatedBy(final String createdBy) {
+ this.createdBy = createdBy;
+ }
+
+ public LocalDateTime getLastModifiedOn() {
+ return this.lastModifiedOn;
+ }
+
+ public void setLastModifiedOn(final LocalDateTime lastModifiedOn) {
+ this.lastModifiedOn = lastModifiedOn;
+ }
+
+ public String getLastModifiedBy() {
+ return this.lastModifiedBy;
+ }
+
+ public void setLastModifiedBy(final String lastModifiedBy) {
+ this.lastModifiedBy = lastModifiedBy;
+ }
+}
diff --git a/service/src/main/java/io/mifos/group/service/internal/repository/GroupDefinitionRepository.java b/service/src/main/java/io/mifos/group/service/internal/repository/GroupDefinitionRepository.java
new file mode 100644
index 0000000..e274b95
--- /dev/null
+++ b/service/src/main/java/io/mifos/group/service/internal/repository/GroupDefinitionRepository.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2016 The Mifos Initiative.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.mifos.group.service.internal.repository;
+
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.stereotype.Repository;
+
+import java.util.Optional;
+
+@Repository
+public interface GroupDefinitionRepository extends JpaRepository<GroupDefinitionEntity, Long> {
+
+ Optional<GroupDefinitionEntity> findByIdentifier(final String identifier);
+}
diff --git a/service/src/main/java/io/mifos/group/service/internal/repository/GroupEntity.java b/service/src/main/java/io/mifos/group/service/internal/repository/GroupEntity.java
new file mode 100644
index 0000000..da5a423
--- /dev/null
+++ b/service/src/main/java/io/mifos/group/service/internal/repository/GroupEntity.java
@@ -0,0 +1,207 @@
+/*
+ * Copyright 2016 The Mifos Initiative.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.mifos.group.service.internal.repository;
+
+import io.mifos.core.mariadb.util.LocalDateTimeConverter;
+
+import javax.persistence.Column;
+import javax.persistence.Convert;
+import javax.persistence.Entity;
+import javax.persistence.FetchType;
+import javax.persistence.GeneratedValue;
+import javax.persistence.GenerationType;
+import javax.persistence.Id;
+import javax.persistence.JoinColumn;
+import javax.persistence.ManyToOne;
+import javax.persistence.OneToOne;
+import javax.persistence.Table;
+import java.time.LocalDateTime;
+
+@Entity
+@Table(name = "ptah_groups")
+public class GroupEntity {
+
+ @Id
+ @GeneratedValue(strategy = GenerationType.IDENTITY)
+ @Column(name = "id", nullable = false)
+ private Long id;
+ @Column(name = "identifier", nullable = false)
+ private String identifier;
+ @ManyToOne(fetch = FetchType.EAGER)
+ @JoinColumn(name = "group_definition_id", nullable = false)
+ private GroupDefinitionEntity groupDefinition;
+ @Column(name = "a_name", nullable = false)
+ private String name;
+ @Column(name = "leaders")
+ private String leaders;
+ @Column(name = "members", nullable = false)
+ private String members;
+ @Column(name = "office", nullable = false)
+ private String office;
+ @OneToOne(fetch = FetchType.LAZY)
+ @JoinColumn(name = "address_id", nullable = false)
+ private AddressEntity addressEntity;
+ @Column(name = "assigned_employee", nullable = false)
+ private String assignedEmployee;
+ @Column(name = "weekday", nullable = false)
+ private Integer weekday;
+ @Column(name = "group_status", nullable = false)
+ private String groupStatus;
+ @Column(name = "current_cycle", nullable = false)
+ private Integer currentCycle = Integer.valueOf(0);
+ @Column(name = "created_on", nullable = false)
+ @Convert(converter = LocalDateTimeConverter.class)
+ private LocalDateTime createdOn;
+ @Column(name = "created_by", nullable = false)
+ private String createdBy;
+ @Column(name = "last_modified_on")
+ @Convert(converter = LocalDateTimeConverter.class)
+ private LocalDateTime lastModifiedOn;
+ @Column(name = "last_modified_by")
+ private String lastModifiedBy;
+
+ public GroupEntity() {
+ super();
+ }
+
+ public Long getId() {
+ return this.id;
+ }
+
+ public void setId(final Long id) {
+ this.id = id;
+ }
+
+ public String getIdentifier() {
+ return this.identifier;
+ }
+
+ public void setIdentifier(final String identifier) {
+ this.identifier = identifier;
+ }
+
+ public GroupDefinitionEntity getGroupDefinition() {
+ return this.groupDefinition;
+ }
+
+ public void setGroupDefinition(final GroupDefinitionEntity groupDefinition) {
+ this.groupDefinition = groupDefinition;
+ }
+
+ public String getName() {
+ return this.name;
+ }
+
+ public void setName(final String name) {
+ this.name = name;
+ }
+
+ public String getLeaders() {
+ return this.leaders;
+ }
+
+ public void setLeaders(final String leaders) {
+ this.leaders = leaders;
+ }
+
+ public String getMembers() {
+ return this.members;
+ }
+
+ public void setMembers(final String members) {
+ this.members = members;
+ }
+
+ public String getOffice() {
+ return this.office;
+ }
+
+ public void setOffice(final String office) {
+ this.office = office;
+ }
+
+ public String getAssignedEmployee() {
+ return this.assignedEmployee;
+ }
+
+ public void setAssignedEmployee(final String assignedEmployee) {
+ this.assignedEmployee = assignedEmployee;
+ }
+
+ public AddressEntity getAddressEntity() {
+ return this.addressEntity;
+ }
+
+ public void setAddressEntity(final AddressEntity addressEntity) {
+ this.addressEntity = addressEntity;
+ }
+
+ public Integer getWeekday() {
+ return this.weekday;
+ }
+
+ public void setWeekday(final Integer weekday) {
+ this.weekday = weekday;
+ }
+
+ public String getGroupStatus() {
+ return this.groupStatus;
+ }
+
+ public void setGroupStatus(final String groupStatus) {
+ this.groupStatus = groupStatus;
+ }
+
+ public Integer getCurrentCycle() {
+ return this.currentCycle;
+ }
+
+ public void setCurrentCycle(final Integer currentCycle) {
+ this.currentCycle = currentCycle;
+ }
+
+ public LocalDateTime getCreatedOn() {
+ return this.createdOn;
+ }
+
+ public void setCreatedOn(final LocalDateTime createdOn) {
+ this.createdOn = createdOn;
+ }
+
+ public String getCreatedBy() {
+ return this.createdBy;
+ }
+
+ public void setCreatedBy(final String createdBy) {
+ this.createdBy = createdBy;
+ }
+
+ public LocalDateTime getLastModifiedOn() {
+ return this.lastModifiedOn;
+ }
+
+ public void setLastModifiedOn(final LocalDateTime lastModifiedOn) {
+ this.lastModifiedOn = lastModifiedOn;
+ }
+
+ public String getLastModifiedBy() {
+ return this.lastModifiedBy;
+ }
+
+ public void setLastModifiedBy(final String lastModifiedBy) {
+ this.lastModifiedBy = lastModifiedBy;
+ }
+}
diff --git a/service/src/main/java/io/mifos/group/service/internal/repository/GroupRepository.java b/service/src/main/java/io/mifos/group/service/internal/repository/GroupRepository.java
new file mode 100644
index 0000000..7398865
--- /dev/null
+++ b/service/src/main/java/io/mifos/group/service/internal/repository/GroupRepository.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2016 The Mifos Initiative.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.mifos.group.service.internal.repository;
+
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.Pageable;
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.stereotype.Repository;
+
+import java.util.Optional;
+
+@Repository
+public interface GroupRepository extends JpaRepository<GroupEntity, Long> {
+
+ Optional<GroupEntity> findByIdentifier(final String identifier);
+
+ Page<GroupEntity> findByAssignedEmployee(final String employee, final Pageable pageable);
+}
diff --git a/service/src/main/java/io/mifos/group/service/internal/repository/MeetingEntity.java b/service/src/main/java/io/mifos/group/service/internal/repository/MeetingEntity.java
new file mode 100644
index 0000000..2733c5a
--- /dev/null
+++ b/service/src/main/java/io/mifos/group/service/internal/repository/MeetingEntity.java
@@ -0,0 +1,138 @@
+/*
+ * Copyright 2016 The Mifos Initiative.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.mifos.group.service.internal.repository;
+
+import io.mifos.core.mariadb.util.LocalDateConverter;
+import io.mifos.core.mariadb.util.LocalDateTimeConverter;
+
+import javax.persistence.Column;
+import javax.persistence.Convert;
+import javax.persistence.Entity;
+import javax.persistence.FetchType;
+import javax.persistence.GeneratedValue;
+import javax.persistence.GenerationType;
+import javax.persistence.Id;
+import javax.persistence.JoinColumn;
+import javax.persistence.ManyToOne;
+import javax.persistence.Table;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+
+@Entity
+@Table(name = "ptah_meetings")
+public class MeetingEntity {
+
+ @Id
+ @GeneratedValue(strategy = GenerationType.IDENTITY)
+ @Column(name = "id", nullable = false)
+ private Long id;
+ @Column(name = "meeting_sequence", nullable = false)
+ private Integer meetingSequence;
+ @ManyToOne(fetch = FetchType.LAZY)
+ @JoinColumn(name = "group_id", nullable = false)
+ private GroupEntity groupEntity;
+ @Column(name = "current_cycle", nullable = false)
+ private Integer currentCycle;
+ @Column(name = "scheduled_for", nullable = false)
+ @Convert(converter = LocalDateConverter.class)
+ private LocalDate scheduledFor;
+ @Column(name = "held_on", nullable = true)
+ @Convert(converter = LocalDateConverter.class)
+ private LocalDate heldOn;
+ @Column(name = "duration", nullable = true)
+ private Long duration;
+ @Column(name = "created_on", nullable = false)
+ @Convert(converter = LocalDateTimeConverter.class)
+ private LocalDateTime createdOn;
+ @Column(name = "created_by", nullable = false)
+ private String createdBy;
+
+ public MeetingEntity() {
+ super();
+ }
+
+ public Long getId() {
+ return this.id;
+ }
+
+ public void setId(final Long id) {
+ this.id = id;
+ }
+
+ public Integer getMeetingSequence() {
+ return this.meetingSequence;
+ }
+
+ public void setMeetingSequence(final Integer meetingSequence) {
+ this.meetingSequence = meetingSequence;
+ }
+
+ public GroupEntity getGroupEntity() {
+ return this.groupEntity;
+ }
+
+ public void setGroupEntity(final GroupEntity groupEntity) {
+ this.groupEntity = groupEntity;
+ }
+
+ public Integer getCurrentCycle() {
+ return this.currentCycle;
+ }
+
+ public void setCurrentCycle(final Integer currentCycle) {
+ this.currentCycle = currentCycle;
+ }
+
+ public LocalDate getScheduledFor() {
+ return this.scheduledFor;
+ }
+
+ public void setScheduledFor(final LocalDate scheduledFor) {
+ this.scheduledFor = scheduledFor;
+ }
+
+ public LocalDate getHeldOn() {
+ return this.heldOn;
+ }
+
+ public void setHeldOn(final LocalDate heldOn) {
+ this.heldOn = heldOn;
+ }
+
+ public Long getDuration() {
+ return this.duration;
+ }
+
+ public void setDuration(final Long duration) {
+ this.duration = duration;
+ }
+
+ public LocalDateTime getCreatedOn() {
+ return this.createdOn;
+ }
+
+ public void setCreatedOn(final LocalDateTime createdOn) {
+ this.createdOn = createdOn;
+ }
+
+ public String getCreatedBy() {
+ return this.createdBy;
+ }
+
+ public void setCreatedBy(final String createdBy) {
+ this.createdBy = createdBy;
+ }
+}
diff --git a/service/src/main/java/io/mifos/group/service/internal/repository/MeetingRepository.java b/service/src/main/java/io/mifos/group/service/internal/repository/MeetingRepository.java
new file mode 100644
index 0000000..6875ba7
--- /dev/null
+++ b/service/src/main/java/io/mifos/group/service/internal/repository/MeetingRepository.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2016 The Mifos Initiative.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.mifos.group.service.internal.repository;
+
+import io.mifos.core.mariadb.util.LocalDateConverter;
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.stereotype.Repository;
+
+import javax.persistence.Convert;
+import java.time.LocalDate;
+import java.util.List;
+import java.util.Optional;
+
+@Repository
+public interface MeetingRepository extends JpaRepository<MeetingEntity, Long> {
+
+ @Convert(converter = LocalDateConverter.class)
+ List<MeetingEntity> findTopByGroupEntityAndScheduledForAfter(final GroupEntity groupEntity, final LocalDate date);
+
+ List<MeetingEntity> findByGroupEntityAndCurrentCycleOrderByMeetingSequenceDesc(
+ final GroupEntity groupEntity, final Integer currentCycle);
+
+ List<MeetingEntity> findByGroupEntityOrderByCurrentCycleDescMeetingSequenceDesc(final GroupEntity groupEntity);
+
+ Optional<MeetingEntity> findByGroupEntityAndCurrentCycleAndMeetingSequence(final GroupEntity groupEntity, final Integer cycle, final Integer sequence);
+}
diff --git a/service/src/main/java/io/mifos/group/service/internal/service/GroupDefinitionService.java b/service/src/main/java/io/mifos/group/service/internal/service/GroupDefinitionService.java
new file mode 100644
index 0000000..2bc03cb
--- /dev/null
+++ b/service/src/main/java/io/mifos/group/service/internal/service/GroupDefinitionService.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2016 The Mifos Initiative.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.mifos.group.service.internal.service;
+
+import io.mifos.group.api.v1.domain.GroupDefinition;
+import io.mifos.group.service.ServiceConstants;
+import io.mifos.group.service.internal.mapper.GroupDefinitionMapper;
+import io.mifos.group.service.internal.repository.GroupDefinitionRepository;
+import org.slf4j.Logger;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.stereotype.Service;
+
+import java.util.List;
+import java.util.Optional;
+import java.util.stream.Collectors;
+
+@Service
+public class GroupDefinitionService {
+
+ private final Logger logger;
+ private final GroupDefinitionRepository groupDefinitionRepository;
+
+ @Autowired
+ public GroupDefinitionService(@Qualifier(ServiceConstants.LOGGER_NAME) final Logger logger,
+ final GroupDefinitionRepository groupDefinitionRepository) {
+ super();
+ this.logger = logger;
+ this.groupDefinitionRepository = groupDefinitionRepository;
+ }
+
+ public Optional<GroupDefinition> findByIdentifier(final String identifier) {
+ return this.groupDefinitionRepository.findByIdentifier(identifier).map(GroupDefinitionMapper::map);
+ }
+
+ public List<GroupDefinition> fetchAllGroupDefinitions() {
+ return this.groupDefinitionRepository.findAll()
+ .stream()
+ .map(GroupDefinitionMapper::map)
+ .collect(Collectors.toList());
+ }
+}
diff --git a/service/src/main/java/io/mifos/group/service/internal/service/GroupService.java b/service/src/main/java/io/mifos/group/service/internal/service/GroupService.java
new file mode 100644
index 0000000..76ce37e
--- /dev/null
+++ b/service/src/main/java/io/mifos/group/service/internal/service/GroupService.java
@@ -0,0 +1,134 @@
+/*
+ * Copyright 2016 The Mifos Initiative.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.mifos.group.service.internal.service;
+
+import io.mifos.core.lang.ServiceException;
+import io.mifos.group.service.internal.repository.AttendeeRepository;
+import io.mifos.group.service.internal.repository.GroupRepository;
+import io.mifos.group.api.v1.domain.Group;
+import io.mifos.group.api.v1.domain.GroupCommand;
+import io.mifos.group.api.v1.domain.GroupPage;
+import io.mifos.group.api.v1.domain.Meeting;
+import io.mifos.group.service.ServiceConstants;
+import io.mifos.group.service.internal.mapper.AddressMapper;
+import io.mifos.group.service.internal.mapper.AttendeeMapper;
+import io.mifos.group.service.internal.mapper.GroupCommandMapper;
+import io.mifos.group.service.internal.mapper.GroupMapper;
+import io.mifos.group.service.internal.mapper.MeetingMapper;
+import io.mifos.group.service.internal.repository.GroupCommandRepository;
+import io.mifos.group.service.internal.repository.GroupEntity;
+import io.mifos.group.service.internal.repository.MeetingEntity;
+import io.mifos.group.service.internal.repository.MeetingRepository;
+import org.slf4j.Logger;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.Pageable;
+import org.springframework.stereotype.Service;
+
+import java.time.Clock;
+import java.time.LocalDate;
+import java.util.List;
+import java.util.Optional;
+import java.util.stream.Collectors;
+
+@Service
+public class GroupService {
+
+ private final Logger logger;
+ private final GroupRepository groupRepository;
+ private final GroupCommandRepository groupCommandRepository;
+ private final MeetingRepository meetingRepository;
+ private final AttendeeRepository attendeeRepository;
+
+ @Autowired
+ public GroupService(@Qualifier(ServiceConstants.LOGGER_NAME) final Logger logger,
+ final GroupRepository groupRepository,
+ final GroupCommandRepository groupCommandRepository,
+ final MeetingRepository meetingRepository,
+ final AttendeeRepository attendeeRepository) {
+ super();
+ this.logger = logger;
+ this.groupRepository = groupRepository;
+ this.groupCommandRepository = groupCommandRepository;
+ this.meetingRepository = meetingRepository;
+ this.attendeeRepository = attendeeRepository;
+ }
+
+ public Optional<Group> findByIdentifier(final String identifier) {
+ final Optional<GroupEntity> optionalGroup = this.groupRepository.findByIdentifier(identifier);
+ if (optionalGroup.isPresent()) {
+ final GroupEntity groupEntity = optionalGroup.get();
+ final Group group = GroupMapper.map(groupEntity);
+ group.setAddress(AddressMapper.map(groupEntity.getAddressEntity()));
+ return Optional.of(group);
+ } else {
+ return Optional.empty();
+ }
+ }
+
+ public GroupPage fetchGroups(final String employee, final Pageable pageable) {
+ final Page<GroupEntity> page;
+ if (employee != null) {
+ page = this.groupRepository.findByAssignedEmployee(employee, pageable);
+ } else {
+ page = this.groupRepository.findAll(pageable);
+ }
+
+ final GroupPage groupPage = new GroupPage();
+ groupPage.setGroups(page.map(GroupMapper::map).getContent());
+ groupPage.setTotalPages(page.getTotalPages());
+ groupPage.setTotalElements(page.getTotalElements());
+
+ return groupPage;
+ }
+
+ public List<GroupCommand> findCommandsByIdentifier(final String identifier) {
+ final GroupEntity groupEntity =
+ this.groupRepository.findByIdentifier(identifier)
+ .orElseThrow(() -> ServiceException.notFound("Group {0} not found.", identifier));
+ return this.groupCommandRepository.findByGroup(groupEntity)
+ .stream()
+ .map(GroupCommandMapper::map)
+ .collect(Collectors.toList());
+ }
+
+ public List<Meeting> findMeetings(final String identifier, final Boolean upcoming) {
+ final GroupEntity groupEntity = this.groupRepository.findByIdentifier(identifier)
+ .orElseThrow(() -> ServiceException.notFound("Group {0} not found.", identifier));
+
+ final List<MeetingEntity> meetings;
+ if (upcoming) {
+ meetings = this.meetingRepository.findTopByGroupEntityAndScheduledForAfter(groupEntity, LocalDate.now(Clock.systemUTC()));
+ } else {
+ meetings = this.meetingRepository.findByGroupEntityOrderByCurrentCycleDescMeetingSequenceDesc(groupEntity);
+ }
+
+ return meetings
+ .stream()
+ .map(meetingEntity -> {
+ final Meeting meeting = MeetingMapper.map(meetingEntity);
+ meeting.setGroupIdentifier(groupEntity.getIdentifier());
+ meeting.setAttendees(
+ this.attendeeRepository.findByMeeting(meetingEntity)
+ .stream().map(AttendeeMapper::map).collect(Collectors.toSet())
+ );
+ meeting.setLocation(AddressMapper.map(groupEntity.getAddressEntity()));
+ return meeting;
+ })
+ .collect(Collectors.toList());
+ }
+}
diff --git a/service/src/main/java/io/mifos/group/service/rest/GroupDefinitionRestController.java b/service/src/main/java/io/mifos/group/service/rest/GroupDefinitionRestController.java
new file mode 100644
index 0000000..7b78586
--- /dev/null
+++ b/service/src/main/java/io/mifos/group/service/rest/GroupDefinitionRestController.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright 2016 The Mifos Initiative.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.mifos.group.service.rest;
+
+import io.mifos.anubis.annotation.AcceptedTokenType;
+import io.mifos.anubis.annotation.Permittable;
+import io.mifos.core.command.gateway.CommandGateway;
+import io.mifos.core.lang.ServiceException;
+import io.mifos.group.service.ServiceConstants;
+import io.mifos.group.service.internal.command.CreateGroupDefinitionCommand;
+import io.mifos.group.service.internal.service.GroupDefinitionService;
+import io.mifos.group.api.v1.domain.GroupDefinition;
+import org.slf4j.Logger;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.http.MediaType;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestMethod;
+import org.springframework.web.bind.annotation.ResponseBody;
+import org.springframework.web.bind.annotation.RestController;
+
+import javax.validation.Valid;
+import java.util.List;
+
+@RestController
+@RequestMapping("/definitions")
+public class GroupDefinitionRestController {
+
+ private final Logger logger;
+ private final CommandGateway commandGateway;
+ private final GroupDefinitionService groupDefinitionService;
+
+ @Autowired
+ public GroupDefinitionRestController(@Qualifier(ServiceConstants.LOGGER_NAME) final Logger logger,
+ final CommandGateway commandGateway,
+ final GroupDefinitionService groupDefinitionService) {
+ super();
+ this.logger = logger;
+ this.commandGateway = commandGateway;
+ this.groupDefinitionService = groupDefinitionService;
+ }
+
+ @Permittable(AcceptedTokenType.TENANT)
+ @RequestMapping(
+ method = RequestMethod.POST,
+ consumes = MediaType.APPLICATION_JSON_VALUE,
+ produces = MediaType.APPLICATION_JSON_VALUE
+ )
+ public
+ @ResponseBody
+ ResponseEntity<Void> createDefinition(@RequestBody @Valid final GroupDefinition groupDefinition) {
+ this.groupDefinitionService.findByIdentifier(groupDefinition.getIdentifier())
+ .ifPresent(gd -> {
+ throw ServiceException.conflict("Group definition {0} already exists.", gd.getIdentifier());
+ });
+
+ this.commandGateway.process(new CreateGroupDefinitionCommand(groupDefinition));
+ return ResponseEntity.accepted().build();
+ }
+
+ @Permittable(AcceptedTokenType.TENANT)
+ @RequestMapping(
+ method = RequestMethod.GET,
+ consumes = MediaType.ALL_VALUE,
+ produces = MediaType.APPLICATION_JSON_VALUE
+ )
+ public
+ @ResponseBody
+ ResponseEntity<List<GroupDefinition>> fetchAllGroupDefinitions() {
+ return ResponseEntity.ok(this.groupDefinitionService.fetchAllGroupDefinitions());
+ }
+
+ @Permittable(AcceptedTokenType.TENANT)
+ @RequestMapping(
+ value = "/{identifier}",
+ method = RequestMethod.GET,
+ consumes = MediaType.ALL_VALUE,
+ produces = MediaType.APPLICATION_JSON_VALUE
+ )
+ public
+ @ResponseBody
+ ResponseEntity<GroupDefinition> findGroupDefinitionByIdentifier(
+ @PathVariable("identifier") final String identifier) {
+ return this.groupDefinitionService.findByIdentifier(identifier)
+ .map(ResponseEntity::ok)
+ .orElseThrow(() -> ServiceException.notFound("Group definition {0} not found.", identifier));
+ }
+}
diff --git a/service/src/main/java/io/mifos/group/service/rest/GroupRestController.java b/service/src/main/java/io/mifos/group/service/rest/GroupRestController.java
new file mode 100644
index 0000000..7aad78b
--- /dev/null
+++ b/service/src/main/java/io/mifos/group/service/rest/GroupRestController.java
@@ -0,0 +1,269 @@
+/*
+ * Copyright 2016 The Mifos Initiative.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.mifos.group.service.rest;
+
+import io.mifos.anubis.annotation.AcceptedTokenType;
+import io.mifos.anubis.annotation.Permittable;
+import io.mifos.core.command.gateway.CommandGateway;
+import io.mifos.core.lang.ServiceException;
+import io.mifos.group.service.internal.command.SignOffMeetingCommand;
+import io.mifos.group.service.internal.command.UpdateLeadersCommand;
+import io.mifos.group.service.internal.command.UpdateMembersCommand;
+import io.mifos.group.service.internal.service.GroupDefinitionService;
+import io.mifos.group.service.internal.service.GroupService;
+import io.mifos.group.api.v1.domain.AssignedEmployeeHolder;
+import io.mifos.group.api.v1.domain.Group;
+import io.mifos.group.api.v1.domain.GroupCommand;
+import io.mifos.group.api.v1.domain.GroupPage;
+import io.mifos.group.api.v1.domain.Meeting;
+import io.mifos.group.api.v1.domain.SignOffMeeting;
+import io.mifos.group.service.ServiceConstants;
+import io.mifos.group.service.internal.command.ActivateGroupCommand;
+import io.mifos.group.service.internal.command.CloseGroupCommand;
+import io.mifos.group.service.internal.command.CreateGroupCommand;
+import io.mifos.group.service.internal.command.ReopenGroupCommand;
+import io.mifos.group.service.internal.command.UpdateAssignedEmployeeCommand;
+import org.slf4j.Logger;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.data.domain.PageRequest;
+import org.springframework.data.domain.Pageable;
+import org.springframework.data.domain.Sort;
+import org.springframework.http.MediaType;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestMethod;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.ResponseBody;
+import org.springframework.web.bind.annotation.RestController;
+
+import javax.validation.Valid;
+import java.util.List;
+import java.util.Set;
+
+@RestController
+@RequestMapping("/groups")
+public class GroupRestController {
+
+ private final Logger logger;
+ private final CommandGateway commandGateway;
+ private final GroupService groupService;
+ private final GroupDefinitionService groupDefinitionService;
+
+
+ @Autowired
+ public GroupRestController(@Qualifier(ServiceConstants.LOGGER_NAME) final Logger logger,
+ final CommandGateway commandGateway,
+ final GroupService groupService,
+ final GroupDefinitionService groupDefinitionService) {
+ super();
+ this.logger = logger;
+ this.commandGateway = commandGateway;
+ this.groupService = groupService;
+ this.groupDefinitionService = groupDefinitionService;
+ }
+
+ @Permittable(AcceptedTokenType.TENANT)
+ @RequestMapping(
+ method = RequestMethod.POST,
+ consumes = MediaType.APPLICATION_JSON_VALUE,
+ produces = MediaType.APPLICATION_JSON_VALUE
+ )
+ public
+ @ResponseBody
+ ResponseEntity<Void> createGroup(@RequestBody @Valid final Group group) {
+ this.groupService.findByIdentifier(group.getIdentifier())
+ .ifPresent(g -> {
+ throw ServiceException.conflict("Group {0} already exists.", g.getIdentifier());
+ });
+
+ if (!this.groupDefinitionService.findByIdentifier(group.getGroupDefinitionIdentifier()).isPresent()) {
+ throw ServiceException.notFound("Unknown group definition {0}.", group.getGroupDefinitionIdentifier());
+ }
+
+ this.commandGateway.process(new CreateGroupCommand(group));
+ return ResponseEntity.accepted().build();
+ }
+
+ @Permittable(AcceptedTokenType.TENANT)
+ @RequestMapping(
+ method = RequestMethod.GET,
+ consumes = MediaType.ALL_VALUE,
+ produces = MediaType.APPLICATION_JSON_VALUE
+ )
+ public
+ @ResponseBody
+ ResponseEntity<GroupPage> fetchGroups(
+ @RequestParam("employee") final String employee,
+ @RequestParam("page") final Integer page,
+ @RequestParam("size") final Integer size,
+ @RequestParam("sortColumn") final String sortColumn,
+ @RequestParam("sortDirection") final String sortDirection) {
+ return ResponseEntity.ok(
+ this.groupService.fetchGroups(employee, this.createPageRequest(page, size, sortColumn, sortDirection))
+ );
+ }
+
+ @Permittable(AcceptedTokenType.TENANT)
+ @RequestMapping(
+ value = "/{identifier}",
+ method = RequestMethod.GET,
+ consumes = MediaType.ALL_VALUE,
+ produces = MediaType.APPLICATION_JSON_VALUE
+ )
+ public
+ @ResponseBody
+ ResponseEntity<Group> findByIdentifier(@PathVariable("identifier") final String identifier) {
+ return this.groupService.findByIdentifier(identifier)
+ .map(ResponseEntity::ok)
+ .orElseThrow(() -> ServiceException.notFound("Group {0} not found.", identifier));
+ }
+
+ @RequestMapping(
+ value = "/{identifier}/commands",
+ method = RequestMethod.POST,
+ produces = MediaType.APPLICATION_JSON_VALUE,
+ consumes = MediaType.APPLICATION_JSON_VALUE
+ )
+ public
+ @ResponseBody
+ ResponseEntity<Void> processGroupCommand(@PathVariable("identifier") final String identifier, @RequestBody final GroupCommand groupCommand) {
+ this.groupService.findByIdentifier(identifier).orElseThrow(() -> ServiceException.notFound("Group {0} not found.", identifier));
+ final GroupCommand.Action action = GroupCommand.Action.valueOf(groupCommand.getAction());
+ switch (action) {
+ case ACTIVATE:
+ this.commandGateway.process(new ActivateGroupCommand(identifier, groupCommand));
+ break;
+ case CLOSE:
+ this.commandGateway.process(new CloseGroupCommand(identifier, groupCommand));
+ break;
+ case REOPEN:
+ this.commandGateway.process(new ReopenGroupCommand(identifier, groupCommand));
+ break;
+ default:
+ throw ServiceException.badRequest("Unsupported command {0}.", action.name());
+ }
+ return ResponseEntity.accepted().build();
+ }
+
+ @RequestMapping(
+ value = "/{identifier}/commands",
+ method = RequestMethod.GET,
+ produces = MediaType.APPLICATION_JSON_VALUE,
+ consumes = MediaType.ALL_VALUE
+ )
+ public
+ @ResponseBody
+ ResponseEntity<List<GroupCommand>> getGroupCommands(@PathVariable("identifier") final String identifier) {
+ return ResponseEntity.ok(this.groupService.findCommandsByIdentifier(identifier));
+ }
+
+ @RequestMapping(
+ value = "/{identifier}/leaders",
+ method = RequestMethod.PUT,
+ produces = MediaType.APPLICATION_JSON_VALUE,
+ consumes = MediaType.APPLICATION_JSON_VALUE
+ )
+ public
+ @ResponseBody
+ ResponseEntity<Void> updateLeaders(@PathVariable("identifier") final String identifier,
+ @RequestBody final Set<String> customerIdentifiers) {
+ this.groupService.findByIdentifier(identifier)
+ .orElseThrow(() -> ServiceException.notFound("Group {0} not found.", identifier));
+
+ this.commandGateway.process(new UpdateLeadersCommand(identifier, customerIdentifiers));
+
+ return ResponseEntity.accepted().build();
+ }
+
+ @RequestMapping(
+ value = "/{identifier}/members",
+ method = RequestMethod.PUT,
+ produces = MediaType.APPLICATION_JSON_VALUE,
+ consumes = MediaType.APPLICATION_JSON_VALUE
+ )
+ public
+ @ResponseBody
+ ResponseEntity<Void> updateMembers(@PathVariable("identifier") final String identifier,
+ @RequestBody final Set<String> customerIdentifiers) {
+ this.groupService.findByIdentifier(identifier)
+ .orElseThrow(() -> ServiceException.notFound("Group {0} not found.", identifier));
+
+ this.commandGateway.process(new UpdateMembersCommand(identifier, customerIdentifiers));
+
+ return ResponseEntity.accepted().build();
+ }
+
+ @RequestMapping(
+ value = "/{identifier}/employee",
+ method = RequestMethod.PUT,
+ produces = MediaType.APPLICATION_JSON_VALUE,
+ consumes = MediaType.APPLICATION_JSON_VALUE
+ )
+ public
+ @ResponseBody
+ ResponseEntity<Void> updateAssignedEmployee(@PathVariable("identifier") final String identifier,
+ @RequestBody final AssignedEmployeeHolder assignedEmployeeHolder) {
+
+ this.groupService.findByIdentifier(identifier)
+ .orElseThrow(() -> ServiceException.notFound("Group {0} not found.", identifier));
+
+ this.commandGateway.process(new UpdateAssignedEmployeeCommand(identifier, assignedEmployeeHolder.getIdentifier()));
+
+ return ResponseEntity.accepted().build();
+ }
+
+ @RequestMapping(
+ value = "/{identifier}/meetings",
+ method = RequestMethod.GET,
+ produces = MediaType.APPLICATION_JSON_VALUE,
+ consumes = MediaType.ALL_VALUE
+ )
+ public
+ @ResponseBody
+ ResponseEntity<List<Meeting>> fetchMeetings(@PathVariable("identifier") final String groupIdentifier,
+ @RequestParam(value = "upcoming", required = false, defaultValue = "false") final Boolean upcoming) {
+ this.groupService.findByIdentifier(groupIdentifier)
+ .orElseThrow(() -> ServiceException.notFound("Group {0} not found.", groupIdentifier));
+ return ResponseEntity.ok(this.groupService.findMeetings(groupIdentifier, upcoming));
+ }
+
+ @RequestMapping(
+ value = "/{identifier}/meetings",
+ method = RequestMethod.PUT,
+ produces = MediaType.APPLICATION_JSON_VALUE,
+ consumes = MediaType.APPLICATION_JSON_VALUE
+ )
+ public
+ ResponseEntity<Void> closeMeeting(@PathVariable("identifier") final String groupIdentifier,
+ @RequestBody final SignOffMeeting signOffMeeting) {
+ this.groupService.findByIdentifier(groupIdentifier)
+ .orElseThrow(() -> ServiceException.notFound("Group {0} not found.", groupIdentifier));
+
+ this.commandGateway.process(new SignOffMeetingCommand(groupIdentifier, signOffMeeting));
+ return ResponseEntity.accepted().build();
+ }
+
+ private Pageable createPageRequest(final Integer page, final Integer size, final String sortColumn, final String sortDirection) {
+ final Integer pageToUse = page != null ? page : 0;
+ final Integer sizeToUse = size != null ? size : 20;
+ final String sortColumnToUse = sortColumn != null ? sortColumn : "identifier";
+ final Sort.Direction direction = sortDirection != null ? Sort.Direction.valueOf(sortDirection.toUpperCase()) : Sort.Direction.ASC;
+ return new PageRequest(pageToUse, sizeToUse, direction, sortColumnToUse);
+ }
+}
diff --git a/service/src/main/java/io/mifos/group/service/rest/MigrationRestController.java b/service/src/main/java/io/mifos/group/service/rest/MigrationRestController.java
new file mode 100644
index 0000000..e656b2a
--- /dev/null
+++ b/service/src/main/java/io/mifos/group/service/rest/MigrationRestController.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright 2016 The Mifos Initiative.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.mifos.group.service.rest;
+
+import io.mifos.anubis.annotation.AcceptedTokenType;
+import io.mifos.anubis.annotation.Permittable;
+import io.mifos.core.command.gateway.CommandGateway;
+import io.mifos.group.service.ServiceConstants;
+import io.mifos.group.service.internal.command.InitializeServiceCommand;
+import org.slf4j.Logger;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.http.MediaType;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestMethod;
+import org.springframework.web.bind.annotation.ResponseBody;
+import org.springframework.web.bind.annotation.RestController;
+
+@RestController
+@RequestMapping("/initialize")
+public class MigrationRestController {
+
+ private final Logger logger;
+ private final CommandGateway commandGateway;
+
+ @Autowired
+ public MigrationRestController(@Qualifier(ServiceConstants.LOGGER_NAME) final Logger logger,
+ final CommandGateway commandGateway) {
+ super();
+ this.logger = logger;
+ this.commandGateway = commandGateway;
+ }
+
+ @Permittable(AcceptedTokenType.SYSTEM)
+ @RequestMapping(
+ method = RequestMethod.POST,
+ consumes = MediaType.ALL_VALUE,
+ produces = MediaType.APPLICATION_JSON_VALUE
+ )
+ public
+ @ResponseBody
+ ResponseEntity<Void> initialize() throws InterruptedException {
+ this.commandGateway.process(new InitializeServiceCommand());
+ return ResponseEntity.accepted().build();
+ }
+}
diff --git a/service/src/main/resources/application.yml b/service/src/main/resources/application.yml
new file mode 100644
index 0000000..fbbfa68
--- /dev/null
+++ b/service/src/main/resources/application.yml
@@ -0,0 +1,67 @@
+
+#
+# Copyright 2016 The Mifos Initiative.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+spring:
+ cloud:
+ discovery:
+ enabled: false
+ config:
+ enabled: false
+
+eureka:
+ client:
+ serviceUrl:
+ defaultZone: http://localhost:8761/eureka/
+
+server:
+ port: 8081
+ contextPath: /group/v1/*
+
+cassandra:
+ clusterName: staging_cluster
+ contactPoints: 127.0.0.1:9042,127.0.0.2:9042,127.0.0.3:9042
+ keyspace: seshat
+ cl:
+ read: LOCAL_QUORUM
+ write: LOCAL_QUORUM
+ delete: LOCAL_QUORUM
+
+mariadb:
+ driverClass: org.mariadb.jdbc.Driver
+ database: seshat
+ host: localhost
+ port: 3306
+ user: root
+ password: mysql
+
+bonecp:
+ idleMaxAgeInMinutes: 240
+ idleConnectionTestPeriodInMinutes: 60
+ maxConnectionsPerPartition: 10
+ minConnectionsPerPartition: 1
+ partitionCount: 2
+ acquireIncrement: 5
+ statementsCacheSize: 100
+
+async:
+ corePoolSize: 32
+ maxPoolSize: 16384
+ queueCapacity: 0
+ threadName: async-processor-
+
+flyway:
+ enabled: false
diff --git a/service/src/main/resources/bootstrap.yml b/service/src/main/resources/bootstrap.yml
new file mode 100644
index 0000000..93080e3
--- /dev/null
+++ b/service/src/main/resources/bootstrap.yml
@@ -0,0 +1,19 @@
+#
+# Copyright 2016 The Mifos Initiative.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+spring:
+ application:
+ name: group-api
diff --git a/service/src/main/resources/db/migrations/mariadb/V1__initial_setup.sql b/service/src/main/resources/db/migrations/mariadb/V1__initial_setup.sql
new file mode 100644
index 0000000..ab45c4b
--- /dev/null
+++ b/service/src/main/resources/db/migrations/mariadb/V1__initial_setup.sql
@@ -0,0 +1,100 @@
+--
+-- Copyright 2016 The Mifos Initiative.
+--
+-- Licensed under the Apache License, Version 2.0 (the "License");
+-- you may not use this file except in compliance with the License.
+-- You may obtain a copy of the License at
+--
+-- http://www.apache.org/licenses/LICENSE-2.0
+--
+-- Unless required by applicable law or agreed to in writing, software
+-- distributed under the License is distributed on an "AS IS" BASIS,
+-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+-- See the License for the specific language governing permissions and
+-- limitations under the License.
+--
+
+CREATE TABLE ptah_addresses (
+ id BIGINT NOT NULL AUTO_INCREMENT,
+ street VARCHAR(256) NOT NULL,
+ city VARCHAR(256) NOT NULL,
+ postal_code VARCHAR(32) NULL,
+ region VARCHAR(256) NULL,
+ country_code VARCHAR(2) NOT NULL,
+ country VARCHAR(256) NOT NULL,
+ CONSTRAINT ptah_addresses_pk PRIMARY KEY (id)
+);
+
+CREATE TABLE ptah_group_definitions (
+ id BIGINT NOT NULL AUTO_INCREMENT,
+ identifier VARCHAR(32) NOT NULL,
+ description VARCHAR(2048) NULL,
+ minimal_size INTEGER NOT NULL,
+ maximal_size INTEGER NOT NULL,
+ number_of_meetings INTEGER NOT NULL,
+ frequency VARCHAR(32) NOT NULL,
+ adjustment VARCHAR(32) NULL,
+ created_on TIMESTAMP(3) NOT NULL,
+ created_by VARCHAR(32) NOT NULL,
+ last_modified_on TIMESTAMP(3) NULL,
+ last_modified_by VARCHAR(32) NULL,
+ CONSTRAINT ptah_group_definitions_pk PRIMARY KEY (id),
+ CONSTRAINT ptah_group_definitions_identifier_uq UNIQUE (identifier)
+);
+
+CREATE TABLE ptah_groups (
+ id BIGINT NOT NULL AUTO_INCREMENT,
+ identifier VARCHAR(32) NOT NULL,
+ group_definition_id BIGINT NOT NULL,
+ a_name VARCHAR(256) NOT NULL,
+ leaders VARCHAR(512) NULL,
+ members VARCHAR(2048) NOT NULL,
+ office VARCHAR(32) NOT NULL,
+ assigned_employee VARCHAR(32) NOT NULL,
+ weekday INTEGER NOT NULL,
+ group_status VARCHAR(32) NOT NULL,
+ current_cycle BIGINT NOT NULL ,
+ address_id BIGINT NOT NULL,
+ created_on TIMESTAMP(3) NOT NULL,
+ created_by VARCHAR(32) NOT NULL,
+ last_modified_on TIMESTAMP(3) NULL,
+ last_modified_by VARCHAR(32) NULL,
+ CONSTRAINT ptah_groups_pk PRIMARY KEY (id),
+ CONSTRAINT ptah_groups_identifier_uq UNIQUE (identifier),
+ CONSTRAINT ptah_groups_definitions_fk FOREIGN KEY (group_definition_id) REFERENCES ptah_group_definitions (id),
+ CONSTRAINT ptah_group_addresses_fk FOREIGN KEY (address_id) REFERENCES ptah_addresses (id)
+);
+
+CREATE TABLE ptah_group_commands (
+ id BIGINT NOT NULL AUTO_INCREMENT,
+ group_id BIGINT NOT NULL,
+ a_action VARCHAR(32) NOT NULL,
+ note VARCHAR(256) NOT NULL,
+ created_on TIMESTAMP(3) NOT NULL,
+ created_by VARCHAR(32) NOT NULL,
+ CONSTRAINT ptah_group_commands_pk PRIMARY KEY (id),
+ CONSTRAINT ptah_group_commands_definitions_fk FOREIGN KEY (group_id) REFERENCES ptah_groups (id)
+);
+
+CREATE TABLE ptah_meetings (
+ id BIGINT NOT NULL AUTO_INCREMENT,
+ group_id BIGINT NOT NULL,
+ meeting_sequence BIGINT NOT NULL,
+ current_cycle BIGINT NOT NULL,
+ scheduled_for DATE NOT NULL,
+ held_on DATE NULL,
+ duration BIGINT NULL,
+ created_on TIMESTAMP(3) NOT NULL,
+ created_by VARCHAR(32) NOT NULL,
+ CONSTRAINT ptah_meetings_pk PRIMARY KEY (id),
+ CONSTRAINT ptah_group_meetings_fk FOREIGN KEY (group_id) REFERENCES ptah_groups (id)
+);
+
+CREATE TABLE ptah_attendees (
+ id BIGINT NOT NULL AUTO_INCREMENT,
+ meeting_id BIGINT NOT NULL,
+ customer_identifier VARCHAR(32) NOT NULL,
+ a_status VARCHAR(256) NOT NULL,
+ CONSTRAINT ptah_attendees_pk PRIMARY KEY (id),
+ CONSTRAINT ptah_meeting_attendees_fk FOREIGN KEY (meeting_id) REFERENCES ptah_attendees (id)
+);
diff --git a/settings.gradle b/settings.gradle
new file mode 100644
index 0000000..c82a6db
--- /dev/null
+++ b/settings.gradle
@@ -0,0 +1,5 @@
+rootProject.name = 'group'
+
+includeBuild 'api'
+includeBuild 'service'
+includeBuild 'component-test'
diff --git a/shared.gradle b/shared.gradle
new file mode 100644
index 0000000..c54ad58
--- /dev/null
+++ b/shared.gradle
@@ -0,0 +1,64 @@
+group 'io.mifos.group'
+version '0.1.0-BUILD-SNAPSHOT'
+
+ext.versions = [
+ frameworkapi : '0.1.0-BUILD-SNAPSHOT',
+ frameworklang : '0.1.0-BUILD-SNAPSHOT',
+ frameworkasync : '0.1.0-BUILD-SNAPSHOT',
+ frameworkcassandra : '0.1.0-BUILD-SNAPSHOT',
+ frameworkmariadb : '0.1.0-BUILD-SNAPSHOT',
+ frameworkcommand : '0.1.0-BUILD-SNAPSHOT',
+ frameworktest: '0.1.0-BUILD-SNAPSHOT',
+ frameworkanubis: '0.1.0-BUILD-SNAPSHOT',
+ validator : '5.3.0.Final'
+]
+
+apply plugin: 'java'
+apply plugin: 'idea'
+apply plugin: 'maven-publish'
+apply plugin: 'io.spring.dependency-management'
+
+sourceCompatibility = JavaVersion.VERSION_1_8
+targetCompatibility = JavaVersion.VERSION_1_8
+
+repositories {
+ jcenter()
+ mavenLocal()
+}
+
+dependencyManagement {
+ imports {
+ mavenBom 'io.spring.platform:platform-bom:Athens-RELEASE'
+ mavenBom 'org.springframework.cloud:spring-cloud-dependencies:Camden.SR1'
+ }
+}
+
+// override certain dependency provided by Spring platform using newer releases
+ext['cassandra.version'] = '3.6'
+ext['cassandra-driver.version'] = '3.1.2'
+ext['activemq.version'] = '5.13.2'
+ext['spring-data-releasetrain.version'] = 'Gosling-SR2A'
+
+dependencies {
+ compile(
+ [group: 'com.google.code.findbugs', name: 'jsr305']
+ )
+
+ testCompile(
+ [group: 'org.springframework.boot', name: 'spring-boot-starter-test']
+ )
+}
+
+license {
+ header rootProject.file('../HEADER')
+ strictCheck true
+ mapping {
+ java = 'SLASHSTAR_STYLE'
+ xml = 'XML_STYLE'
+ yml = 'SCRIPT_STYLE'
+ yaml = 'SCRIPT_STYLE'
+ uxf = 'XML_STYLE'
+ }
+ ext.year = Calendar.getInstance().get(Calendar.YEAR)
+ ext.name = 'The Mifos Initiative'
+}