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/17 07:34:00 UTC
[fineract-cn-cheques] 01/44: 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-cheques.git
commit 6828d67961e46261fd42b34df55fa1aae7e6fd98
Author: mgeiss <mg...@mifos.org>
AuthorDate: Mon Aug 21 16:06:55 2017 +0200
initial commit
---
.gitignore | 17 ++
HEADER | 13 +
README.md | 30 +++
api/build.gradle | 39 +++
api/settings.gradle | 1 +
.../io/mifos/cheque/api/v1/EventConstants.java | 35 +++
.../mifos/cheque/api/v1/PermittableGroupIds.java | 22 ++
.../mifos/cheque/api/v1/client/ChequeManager.java | 87 +++++++
.../mifos/cheque/api/v1/client/IssuingCount.java | 56 +++++
.../java/io/mifos/cheque/api/v1/domain/Action.java | 21 ++
.../java/io/mifos/cheque/api/v1/domain/Cheque.java | 117 +++++++++
.../api/v1/domain/ChequeProcessingCommand.java | 36 +++
.../cheque/api/v1/domain/ChequeTransaction.java | 50 ++++
.../java/io/mifos/cheque/api/v1/domain/MICR.java | 56 +++++
.../java/io/mifos/cheque/api/v1/domain/State.java | 22 ++
build.gradle | 36 +++
component-test/build.gradle | 38 +++
component-test/settings.gradle | 1 +
.../java/io/mifos/cheque/AbstractChequeTest.java | 118 +++++++++
.../src/main/java/io/mifos/cheque/Fixture.java | 49 ++++
.../java/io/mifos/cheque/SuiteTestEnvironment.java | 41 ++++
.../src/main/java/io/mifos/cheque/TestCheques.java | 210 ++++++++++++++++
.../java/io/mifos/cheque/TestIssuingCheques.java | 99 ++++++++
.../src/main/java/io/mifos/cheque/TestSuite.java | 30 +++
.../mifos/cheque/listener/ChequeEventListener.java | 84 +++++++
.../listener/ManagementEventMigrationListener.java | 53 +++++
component-test/src/main/resources/logback-test.xml | 32 +++
gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 54212 bytes
gradle/wrapper/gradle-wrapper.properties | 6 +
gradlew | 172 +++++++++++++
gradlew.bat | 84 +++++++
service/build.gradle | 67 ++++++
service/settings.gradle | 1 +
.../io/mifos/cheque/service/ChequeApplication.java | 29 +++
.../mifos/cheque/service/ChequeConfiguration.java | 82 +++++++
.../io/mifos/cheque/service/ServiceConstants.java | 22 ++
.../command/ApproveChequeTransactionCommand.java | 29 +++
.../command/CancelChequeTransactionCommand.java | 29 +++
.../internal/command/ChequeTransactionCommand.java | 37 +++
.../internal/command/IssueChequesCommand.java | 42 ++++
.../service/internal/command/MigrationCommand.java | 22 ++
.../internal/command/handler/ChequeAggregate.java | 265 +++++++++++++++++++++
.../command/handler/ManagementAggregate.java | 63 +++++
.../cheque/service/internal/format/MICRParser.java | 60 +++++
.../service/internal/mapper/ChequeMapper.java | 70 ++++++
.../service/internal/repository/ChequeEntity.java | 202 ++++++++++++++++
.../internal/repository/ChequeRepository.java | 30 +++
.../internal/repository/IssuedChequeEntity.java | 111 +++++++++
.../repository/IssuedChequeRepository.java | 25 ++
.../specification/ChequeSpecification.java | 45 ++++
.../service/internal/service/ChequeService.java | 67 ++++++
.../internal/service/helper/AccountingService.java | 110 +++++++++
.../internal/service/helper/DepositService.java | 78 ++++++
.../service/helper/OrganizationService.java | 48 ++++
.../rest/ChequeManagementRestController.java | 61 +++++
.../cheque/service/rest/ChequeRestController.java | 150 ++++++++++++
.../rest/ChequeTransactionRestController.java | 81 +++++++
service/src/main/resources/application.yml | 66 +++++
service/src/main/resources/bootstrap.yml | 19 ++
.../db/migrations/mariadb/V1__initial_setup.sql | 48 ++++
service/src/main/resources/logback.xml | 55 +++++
.../service/internal/format/TestMICRParser.java | 125 ++++++++++
settings.gradle | 6 +
shared.gradle | 73 ++++++
64 files changed, 3873 insertions(+)
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..1d62734
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,17 @@
+.gradle
+.idea
+build/
+target/
+out/
+
+# Ignore Gradle GUI config
+gradle-app.setting
+
+# Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored)
+!gradle-wrapper.jar
+
+*.iml
+
+*.log
+
+*.toDelete
diff --git a/HEADER b/HEADER
new file mode 100644
index 0000000..88f1afc
--- /dev/null
+++ b/HEADER
@@ -0,0 +1,13 @@
+Copyright ${year} ${company}.
+
+All Rights Reserved.
+
+NOTICE: All information contained herein is, and remains
+the property of ${company} and its suppliers, if any.
+The intellectual and technical concepts contained herein
+are proprietary to ${company} and its suppliers and may
+be covered by U.S. and Foreign Patents, patents in process,
+and are protected by trade secret or copyright law.
+Dissemination of this information or reproduction of this material
+is strictly forbidden unless prior written permission is obtained
+${company}.
\ No newline at end of file
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..8ac2e2b
--- /dev/null
+++ b/README.md
@@ -0,0 +1,30 @@
+# Mifos I/O Deposit Account 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 a services to create deposit accounts, e.g. checking, savings, and shares.
+
+## Abstract
+Mifos I/O is an application framework for digital financial services, a system to support nationwide and cross-national financial transactions and help to level and speed the creation of an inclusive, interconnected digital economy for every nation in the world.
+
+## 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
+Copyright 2017 Kuelap, Inc.
+
+All Rights Reserved.
diff --git a/api/build.gradle b/api/build.gradle
new file mode 100644
index 0000000..17c6648
--- /dev/null
+++ b/api/build.gradle
@@ -0,0 +1,39 @@
+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],
+ [group: 'org.hibernate', name: 'hibernate-validator-annotation-processor', version: versions.validator]
+ )
+
+ testCompile(
+ [group: 'io.mifos.core', name: 'test', version: versions.frameworktest],
+ )
+}
+
+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..7c8e3dc
--- /dev/null
+++ b/api/settings.gradle
@@ -0,0 +1 @@
+rootProject.name = 'api'
\ No newline at end of file
diff --git a/api/src/main/java/io/mifos/cheque/api/v1/EventConstants.java b/api/src/main/java/io/mifos/cheque/api/v1/EventConstants.java
new file mode 100644
index 0000000..74573b5
--- /dev/null
+++ b/api/src/main/java/io/mifos/cheque/api/v1/EventConstants.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2017 Kuelap, Inc.
+ *
+ * All Rights Reserved.
+ *
+ * NOTICE: All information contained herein is, and remains
+ * the property of Kuelap, Inc and its suppliers, if any.
+ * The intellectual and technical concepts contained herein
+ * are proprietary to Kuelap, Inc and its suppliers and may
+ * be covered by U.S. and Foreign Patents, patents in process,
+ * and are protected by trade secret or copyright law.
+ * Dissemination of this information or reproduction of this material
+ * is strictly forbidden unless prior written permission is obtained
+ * Kuelap, Inc.
+ */
+package io.mifos.cheque.api.v1;
+
+@SuppressWarnings("unused")
+public interface EventConstants {
+
+ String DESTINATION = "cheques-v1";
+ String SELECTOR_NAME = "action";
+
+ String INITIALIZE = "initialize";
+ String SELECTOR_INITIALIZE = SELECTOR_NAME + " = '" + INITIALIZE + "'";
+
+ String ISSUE_CHEQUES = "issue-cheques";
+ String SELECTOR_ISSUE_CHEQUES = SELECTOR_NAME + " = '" + ISSUE_CHEQUES + "'";
+ String CHEQUE_TRANSACTION = "cheque-transaction";
+ String SELECTOR_CHEQUE_TRANSACTION = SELECTOR_NAME + " = '" + CHEQUE_TRANSACTION + "'";
+ String CHEQUE_TRANSACTION_APPROVED = "cheque-transaction-approved";
+ String SELECTOR_CHEQUE_TRANSACTION_APPROVED = SELECTOR_NAME + " = '" + CHEQUE_TRANSACTION_APPROVED + "'";
+ String CHEQUE_TRANSACTION_CANCELED = "cheque-transaction-canceled";
+ String SELECTOR_CHEQUE_TRANSACTION_CANCELED = SELECTOR_NAME + " = '" + CHEQUE_TRANSACTION_CANCELED + "'";
+}
diff --git a/api/src/main/java/io/mifos/cheque/api/v1/PermittableGroupIds.java b/api/src/main/java/io/mifos/cheque/api/v1/PermittableGroupIds.java
new file mode 100644
index 0000000..5c9c599
--- /dev/null
+++ b/api/src/main/java/io/mifos/cheque/api/v1/PermittableGroupIds.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright 2017 Kuelap, Inc.
+ *
+ * All Rights Reserved.
+ *
+ * NOTICE: All information contained herein is, and remains
+ * the property of Kuelap, Inc and its suppliers, if any.
+ * The intellectual and technical concepts contained herein
+ * are proprietary to Kuelap, Inc and its suppliers and may
+ * be covered by U.S. and Foreign Patents, patents in process,
+ * and are protected by trade secret or copyright law.
+ * Dissemination of this information or reproduction of this material
+ * is strictly forbidden unless prior written permission is obtained
+ * Kuelap, Inc.
+ */
+package io.mifos.cheque.api.v1;
+
+@SuppressWarnings("unused")
+public interface PermittableGroupIds {
+ String CHEQUE_MANAGEMENT = "cheques__v1__management";
+ String CHEQUE_TRANSACTION = "cheques__v1__transaction";
+}
diff --git a/api/src/main/java/io/mifos/cheque/api/v1/client/ChequeManager.java b/api/src/main/java/io/mifos/cheque/api/v1/client/ChequeManager.java
new file mode 100644
index 0000000..781793a
--- /dev/null
+++ b/api/src/main/java/io/mifos/cheque/api/v1/client/ChequeManager.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright 2017 Kuelap, Inc.
+ *
+ * All Rights Reserved.
+ *
+ * NOTICE: All information contained herein is, and remains
+ * the property of Kuelap, Inc and its suppliers, if any.
+ * The intellectual and technical concepts contained herein
+ * are proprietary to Kuelap, Inc and its suppliers and may
+ * be covered by U.S. and Foreign Patents, patents in process,
+ * and are protected by trade secret or copyright law.
+ * Dissemination of this information or reproduction of this material
+ * is strictly forbidden unless prior written permission is obtained
+ * Kuelap, Inc.
+ */
+package io.mifos.cheque.api.v1.client;
+
+import io.mifos.cheque.api.v1.domain.Cheque;
+import io.mifos.cheque.api.v1.domain.ChequeProcessingCommand;
+import io.mifos.cheque.api.v1.domain.ChequeTransaction;
+import io.mifos.core.api.util.CustomFeignClientsConfiguration;
+import org.springframework.cloud.netflix.feign.FeignClient;
+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 javax.validation.Valid;
+import java.util.List;
+
+@SuppressWarnings("unused")
+@FeignClient(value = "cheques-v1", path = "/cheques/v1", configuration = CustomFeignClientsConfiguration.class)
+public interface ChequeManager {
+
+ @RequestMapping(
+ value = "/cheques/",
+ method = RequestMethod.POST,
+ produces = {MediaType.APPLICATION_JSON_VALUE},
+ consumes = {MediaType.APPLICATION_JSON_VALUE}
+ )
+ String issue(@RequestBody @Valid final IssuingCount issuingCount);
+
+ @RequestMapping(
+ value = "/cheques/",
+ method = RequestMethod.GET,
+ produces = {MediaType.ALL_VALUE},
+ consumes = {MediaType.APPLICATION_JSON_VALUE}
+ )
+ List<Cheque> fetch(@RequestParam(value = "state", required = false, defaultValue = "ALL") final String state,
+ @RequestParam(value = "account", required = false) final String accountIdentifier);
+
+ /**
+ * The identifier is created by concatenating the MICR parts with the character <code>~</code>.
+ * Sample:<br/>
+ * {cheque-no}~{branch-sort-code}~{account-no}<br/>
+ * 246~13579~20030011<br/>
+ *
+ * @param identifier concatenated MICR
+ * @return
+ */
+ @RequestMapping(
+ value = "/cheques/{identifier}",
+ method = RequestMethod.GET,
+ produces = {MediaType.ALL_VALUE},
+ consumes = {MediaType.APPLICATION_JSON_VALUE}
+ )
+ Cheque get(@PathVariable("identifier") final String identifier);
+
+ @RequestMapping(
+ value = "/cheques/{identifier}/commands",
+ method = RequestMethod.POST,
+ produces = {MediaType.ALL_VALUE},
+ consumes = {MediaType.APPLICATION_JSON_VALUE}
+ )
+ void process(@PathVariable("identifier") final String identifier,
+ @RequestBody @Valid final ChequeProcessingCommand chequeProcessingCommand);
+
+ @RequestMapping(
+ value = "/transactions/",
+ method = RequestMethod.POST,
+ produces = {MediaType.ALL_VALUE},
+ consumes = {MediaType.APPLICATION_JSON_VALUE}
+ )
+ void process(@RequestBody @Valid final ChequeTransaction chequeTransaction);
+}
diff --git a/api/src/main/java/io/mifos/cheque/api/v1/client/IssuingCount.java b/api/src/main/java/io/mifos/cheque/api/v1/client/IssuingCount.java
new file mode 100644
index 0000000..af37b0d
--- /dev/null
+++ b/api/src/main/java/io/mifos/cheque/api/v1/client/IssuingCount.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright 2017 Kuelap, Inc.
+ *
+ * All Rights Reserved.
+ *
+ * NOTICE: All information contained herein is, and remains
+ * the property of Kuelap, Inc and its suppliers, if any.
+ * The intellectual and technical concepts contained herein
+ * are proprietary to Kuelap, Inc and its suppliers and may
+ * be covered by U.S. and Foreign Patents, patents in process,
+ * and are protected by trade secret or copyright law.
+ * Dissemination of this information or reproduction of this material
+ * is strictly forbidden unless prior written permission is obtained
+ * Kuelap, Inc.
+ */
+package io.mifos.cheque.api.v1.client;
+
+import javax.validation.constraints.Min;
+import javax.validation.constraints.NotNull;
+
+public class IssuingCount {
+
+ private String accountIdentifier;
+ private Integer start;
+ @NotNull
+ @Min(1)
+ private Integer amount;
+
+ public IssuingCount() {
+ super();
+ }
+
+ public String getAccountIdentifier() {
+ return this.accountIdentifier;
+ }
+
+ public void setAccountIdentifier(final String accountIdentifier) {
+ this.accountIdentifier = accountIdentifier;
+ }
+
+ public Integer getStart() {
+ return this.start;
+ }
+
+ public void setStart(final Integer start) {
+ this.start = start;
+ }
+
+ public Integer getAmount() {
+ return this.amount;
+ }
+
+ public void setAmount(final Integer amount) {
+ this.amount = amount;
+ }
+}
diff --git a/api/src/main/java/io/mifos/cheque/api/v1/domain/Action.java b/api/src/main/java/io/mifos/cheque/api/v1/domain/Action.java
new file mode 100644
index 0000000..0c73f44
--- /dev/null
+++ b/api/src/main/java/io/mifos/cheque/api/v1/domain/Action.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright 2017 Kuelap, Inc.
+ *
+ * All Rights Reserved.
+ *
+ * NOTICE: All information contained herein is, and remains
+ * the property of Kuelap, Inc and its suppliers, if any.
+ * The intellectual and technical concepts contained herein
+ * are proprietary to Kuelap, Inc and its suppliers and may
+ * be covered by U.S. and Foreign Patents, patents in process,
+ * and are protected by trade secret or copyright law.
+ * Dissemination of this information or reproduction of this material
+ * is strictly forbidden unless prior written permission is obtained
+ * Kuelap, Inc.
+ */
+package io.mifos.cheque.api.v1.domain;
+
+public enum Action {
+ APPROVE,
+ CANCEL
+}
diff --git a/api/src/main/java/io/mifos/cheque/api/v1/domain/Cheque.java b/api/src/main/java/io/mifos/cheque/api/v1/domain/Cheque.java
new file mode 100644
index 0000000..2e54522
--- /dev/null
+++ b/api/src/main/java/io/mifos/cheque/api/v1/domain/Cheque.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright 2017 Kuelap, Inc.
+ *
+ * All Rights Reserved.
+ *
+ * NOTICE: All information contained herein is, and remains
+ * the property of Kuelap, Inc and its suppliers, if any.
+ * The intellectual and technical concepts contained herein
+ * are proprietary to Kuelap, Inc and its suppliers and may
+ * be covered by U.S. and Foreign Patents, patents in process,
+ * and are protected by trade secret or copyright law.
+ * Dissemination of this information or reproduction of this material
+ * is strictly forbidden unless prior written permission is obtained
+ * Kuelap, Inc.
+ */
+package io.mifos.cheque.api.v1.domain;
+
+import org.hibernate.validator.constraints.NotEmpty;
+
+import javax.validation.Valid;
+import javax.validation.constraints.NotNull;
+
+public class Cheque {
+
+ @Valid
+ private MICR micr;
+ @NotEmpty
+ private String drawee;
+ @NotEmpty
+ private String drawer;
+ @NotEmpty
+ private String payee;
+ @NotEmpty
+ private String amount;
+ @NotEmpty
+ private String dateIssued;
+ private Boolean openCheque;
+ private State state;
+ @NotNull
+ private String journalEntryIdentifier;
+
+ public Cheque() {
+ super();
+ }
+
+ public MICR getMicr() {
+ return this.micr;
+ }
+
+ public void setMicr(final MICR micr) {
+ this.micr = micr;
+ }
+
+ public String getDrawee() {
+ return this.drawee;
+ }
+
+ public void setDrawee(final String drawee) {
+ this.drawee = drawee;
+ }
+
+ public String getDrawer() {
+ return this.drawer;
+ }
+
+ public void setDrawer(final String drawer) {
+ this.drawer = drawer;
+ }
+
+ public String getPayee() {
+ return this.payee;
+ }
+
+ public void setPayee(final String payee) {
+ this.payee = payee;
+ }
+
+ public String getAmount() {
+ return this.amount;
+ }
+
+ public void setAmount(final String amount) {
+ this.amount = amount;
+ }
+
+ public String getDateIssued() {
+ return this.dateIssued;
+ }
+
+ public void setDateIssued(final String dateIssued) {
+ this.dateIssued = dateIssued;
+ }
+
+ public Boolean isOpenCheque() {
+ return this.openCheque;
+ }
+
+ public void setOpenCheque(final Boolean openCheque) {
+ this.openCheque = openCheque;
+ }
+
+ public String getState() {
+ return this.state != null ? this.state.name() : null;
+ }
+
+ public void setState(final String state) {
+ this.state = State.valueOf(state);
+ }
+
+ public String getJournalEntryIdentifier() {
+ return this.journalEntryIdentifier;
+ }
+
+ public void setJournalEntryIdentifier(final String journalEntryIdentifier) {
+ this.journalEntryIdentifier = journalEntryIdentifier;
+ }
+}
diff --git a/api/src/main/java/io/mifos/cheque/api/v1/domain/ChequeProcessingCommand.java b/api/src/main/java/io/mifos/cheque/api/v1/domain/ChequeProcessingCommand.java
new file mode 100644
index 0000000..95d57fe
--- /dev/null
+++ b/api/src/main/java/io/mifos/cheque/api/v1/domain/ChequeProcessingCommand.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2017 Kuelap, Inc.
+ *
+ * All Rights Reserved.
+ *
+ * NOTICE: All information contained herein is, and remains
+ * the property of Kuelap, Inc and its suppliers, if any.
+ * The intellectual and technical concepts contained herein
+ * are proprietary to Kuelap, Inc and its suppliers and may
+ * be covered by U.S. and Foreign Patents, patents in process,
+ * and are protected by trade secret or copyright law.
+ * Dissemination of this information or reproduction of this material
+ * is strictly forbidden unless prior written permission is obtained
+ * Kuelap, Inc.
+ */
+package io.mifos.cheque.api.v1.domain;
+
+import javax.validation.constraints.NotNull;
+
+public class ChequeProcessingCommand {
+
+ @NotNull
+ private Action action;
+
+ public ChequeProcessingCommand() {
+ super();
+ }
+
+ public String getAction() {
+ return this.action.name();
+ }
+
+ public void setAction(final String action) {
+ this.action = Action.valueOf(action);
+ }
+}
diff --git a/api/src/main/java/io/mifos/cheque/api/v1/domain/ChequeTransaction.java b/api/src/main/java/io/mifos/cheque/api/v1/domain/ChequeTransaction.java
new file mode 100644
index 0000000..bcdc5e0
--- /dev/null
+++ b/api/src/main/java/io/mifos/cheque/api/v1/domain/ChequeTransaction.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2017 Kuelap, Inc.
+ *
+ * All Rights Reserved.
+ *
+ * NOTICE: All information contained herein is, and remains
+ * the property of Kuelap, Inc and its suppliers, if any.
+ * The intellectual and technical concepts contained herein
+ * are proprietary to Kuelap, Inc and its suppliers and may
+ * be covered by U.S. and Foreign Patents, patents in process,
+ * and are protected by trade secret or copyright law.
+ * Dissemination of this information or reproduction of this material
+ * is strictly forbidden unless prior written permission is obtained
+ * Kuelap, Inc.
+ */
+package io.mifos.cheque.api.v1.domain;
+
+import org.hibernate.validator.constraints.NotEmpty;
+
+import javax.validation.Valid;
+import javax.validation.constraints.NotNull;
+
+public class ChequeTransaction {
+
+ @NotNull
+ @Valid
+ private Cheque cheque;
+ @NotEmpty
+ private String creditorAccountNumber;
+
+ public ChequeTransaction() {
+ super();
+ }
+
+ public Cheque getCheque() {
+ return this.cheque;
+ }
+
+ public void setCheque(final Cheque cheque) {
+ this.cheque = cheque;
+ }
+
+ public String getCreditorAccountNumber() {
+ return this.creditorAccountNumber;
+ }
+
+ public void setCreditorAccountNumber(final String creditorAccountNumber) {
+ this.creditorAccountNumber = creditorAccountNumber;
+ }
+}
diff --git a/api/src/main/java/io/mifos/cheque/api/v1/domain/MICR.java b/api/src/main/java/io/mifos/cheque/api/v1/domain/MICR.java
new file mode 100644
index 0000000..05a1a32
--- /dev/null
+++ b/api/src/main/java/io/mifos/cheque/api/v1/domain/MICR.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright 2017 Kuelap, Inc.
+ *
+ * All Rights Reserved.
+ *
+ * NOTICE: All information contained herein is, and remains
+ * the property of Kuelap, Inc and its suppliers, if any.
+ * The intellectual and technical concepts contained herein
+ * are proprietary to Kuelap, Inc and its suppliers and may
+ * be covered by U.S. and Foreign Patents, patents in process,
+ * and are protected by trade secret or copyright law.
+ * Dissemination of this information or reproduction of this material
+ * is strictly forbidden unless prior written permission is obtained
+ * Kuelap, Inc.
+ */
+package io.mifos.cheque.api.v1.domain;
+
+import io.mifos.core.lang.validation.constraints.ValidIdentifier;
+
+public class MICR {
+
+ @ValidIdentifier(maxLength = 8)
+ private String chequeNumber;
+ @ValidIdentifier(maxLength = 11)
+ private String branchSortCode;
+ @ValidIdentifier(maxLength = 34)
+ private String accountNumber;
+
+ public MICR() {
+ super();
+ }
+
+ public String getChequeNumber() {
+ return this.chequeNumber;
+ }
+
+ public void setChequeNumber(final String chequeNumber) {
+ this.chequeNumber = chequeNumber;
+ }
+
+ public String getBranchSortCode() {
+ return this.branchSortCode;
+ }
+
+ public void setBranchSortCode(final String branchSortCode) {
+ this.branchSortCode = branchSortCode;
+ }
+
+ public String getAccountNumber() {
+ return this.accountNumber;
+ }
+
+ public void setAccountNumber(final String accountNumber) {
+ this.accountNumber = accountNumber;
+ }
+}
diff --git a/api/src/main/java/io/mifos/cheque/api/v1/domain/State.java b/api/src/main/java/io/mifos/cheque/api/v1/domain/State.java
new file mode 100644
index 0000000..449344f
--- /dev/null
+++ b/api/src/main/java/io/mifos/cheque/api/v1/domain/State.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright 2017 Kuelap, Inc.
+ *
+ * All Rights Reserved.
+ *
+ * NOTICE: All information contained herein is, and remains
+ * the property of Kuelap, Inc and its suppliers, if any.
+ * The intellectual and technical concepts contained herein
+ * are proprietary to Kuelap, Inc and its suppliers and may
+ * be covered by U.S. and Foreign Patents, patents in process,
+ * and are protected by trade secret or copyright law.
+ * Dissemination of this information or reproduction of this material
+ * is strictly forbidden unless prior written permission is obtained
+ * Kuelap, Inc.
+ */
+package io.mifos.cheque.api.v1.domain;
+
+public enum State {
+ PENDING,
+ PROCESSED,
+ CANCELED
+}
diff --git a/build.gradle b/build.gradle
new file mode 100644
index 0000000..b30bd55
--- /dev/null
+++ b/build.gradle
@@ -0,0 +1,36 @@
+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')
+}
+
+task licenseFormat {
+ group 'all'
+ dependsOn gradle.includedBuild('api').task(':licenseFormat')
+ dependsOn gradle.includedBuild('service').task(':licenseFormat')
+ dependsOn gradle.includedBuild('component-test').task(':licenseFormat')
+}
diff --git a/component-test/build.gradle b/component-test/build.gradle
new file mode 100644
index 0000000..9e5047e
--- /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.kuelap.cheques', name: 'api', version: project.version],
+ [group: 'io.kuelap.cheques', name: 'service', version: project.version],
+ [group: 'io.mifos.anubis', name: 'test', version: versions.frameworkanubis],
+ [group: 'io.mifos.core', name: 'api', version: versions.frameworkapi],
+ [group: 'io.mifos.core', name: 'test', version: versions.frameworktest],
+ [group: 'io.mifos.core', name: 'lang', version: versions.frameworklang],
+ [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..07867cb
--- /dev/null
+++ b/component-test/settings.gradle
@@ -0,0 +1 @@
+rootProject.name = 'component-test'
\ No newline at end of file
diff --git a/component-test/src/main/java/io/mifos/cheque/AbstractChequeTest.java b/component-test/src/main/java/io/mifos/cheque/AbstractChequeTest.java
new file mode 100644
index 0000000..177df2c
--- /dev/null
+++ b/component-test/src/main/java/io/mifos/cheque/AbstractChequeTest.java
@@ -0,0 +1,118 @@
+/*
+ * Copyright 2017 Kuelap, Inc.
+ *
+ * All Rights Reserved.
+ *
+ * NOTICE: All information contained herein is, and remains
+ * the property of Kuelap, Inc and its suppliers, if any.
+ * The intellectual and technical concepts contained herein
+ * are proprietary to Kuelap, Inc and its suppliers and may
+ * be covered by U.S. and Foreign Patents, patents in process,
+ * and are protected by trade secret or copyright law.
+ * Dissemination of this information or reproduction of this material
+ * is strictly forbidden unless prior written permission is obtained
+ * Kuelap, Inc.
+ */
+package io.mifos.cheque;
+
+import io.mifos.anubis.test.v1.TenantApplicationSecurityEnvironmentTestRule;
+import io.mifos.cheque.api.v1.EventConstants;
+import io.mifos.cheque.api.v1.client.ChequeManager;
+import io.mifos.cheque.service.ChequeConfiguration;
+import io.mifos.core.api.context.AutoUserContext;
+import io.mifos.core.lang.ApplicationName;
+import io.mifos.core.test.fixture.TenantDataStoreContextTestRule;
+import io.mifos.core.test.listener.EnableEventRecording;
+import io.mifos.core.test.listener.EventRecorder;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.ClassRule;
+import org.junit.Rule;
+import org.junit.runner.RunWith;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Qualifier;
+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,
+ classes = {AbstractChequeTest.TestConfiguration.class}
+)
+public abstract class AbstractChequeTest extends SuiteTestEnvironment {
+ private static final String TEST_USER = "shed";
+ public static final String TEST_LOGGER = "test-logger";
+
+ @ClassRule
+ public final static TenantDataStoreContextTestRule tenantDataStoreContext =
+ TenantDataStoreContextTestRule.forRandomTenantName(cassandraInitializer, mariaDBInitializer);
+
+ @Rule
+ public final TenantApplicationSecurityEnvironmentTestRule tenantApplicationSecurityEnvironment
+ = new TenantApplicationSecurityEnvironmentTestRule(testEnvironment, this::waitForInitialize);
+
+ @Autowired
+ @Qualifier(TEST_LOGGER)
+ protected Logger logger;
+
+ @Autowired
+ ChequeManager chequeManager;
+
+ @Autowired
+ private ApplicationName applicationName;
+
+ @Autowired
+ EventRecorder eventRecorder;
+
+ private AutoUserContext autoUserContext;
+
+ AbstractChequeTest() {
+ super();
+ }
+
+ @Before
+ public void prepTest() throws Exception {
+ this.autoUserContext = this.tenantApplicationSecurityEnvironment.createAutoUserContext(AbstractChequeTest.TEST_USER);
+ }
+
+ @After
+ public void cleanTest() throws Exception {
+ this.autoUserContext.close();
+ }
+
+ public boolean waitForInitialize() {
+ try {
+ final String version = this.applicationName.getVersionString();
+ this.logger.info("Waiting on initialize event for version: {}.", version);
+ return this.eventRecorder.wait(EventConstants.INITIALIZE, version);
+ } catch (final InterruptedException e) {
+ throw new IllegalStateException(e);
+ }
+ }
+
+ @Configuration
+ @EnableEventRecording
+ @EnableFeignClients(basePackages = {"io.mifos.cheque.api.v1"})
+ @RibbonClient(name = APP_NAME)
+ @Import({ChequeConfiguration.class})
+ @ComponentScan("io.mifos.cheque.listener")
+ public static class TestConfiguration {
+ public TestConfiguration() {
+ super();
+ }
+
+ @Bean(name= TEST_LOGGER)
+ public Logger logger() {
+ return LoggerFactory.getLogger(TEST_LOGGER);
+ }
+ }
+}
+
diff --git a/component-test/src/main/java/io/mifos/cheque/Fixture.java b/component-test/src/main/java/io/mifos/cheque/Fixture.java
new file mode 100644
index 0000000..2eda2b5
--- /dev/null
+++ b/component-test/src/main/java/io/mifos/cheque/Fixture.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2017 Kuelap, Inc.
+ *
+ * All Rights Reserved.
+ *
+ * NOTICE: All information contained herein is, and remains
+ * the property of Kuelap, Inc and its suppliers, if any.
+ * The intellectual and technical concepts contained herein
+ * are proprietary to Kuelap, Inc and its suppliers and may
+ * be covered by U.S. and Foreign Patents, patents in process,
+ * and are protected by trade secret or copyright law.
+ * Dissemination of this information or reproduction of this material
+ * is strictly forbidden unless prior written permission is obtained
+ * Kuelap, Inc.
+ */
+package io.mifos.cheque;
+
+import io.mifos.cheque.api.v1.domain.Cheque;
+import io.mifos.cheque.api.v1.domain.MICR;
+import io.mifos.core.lang.DateConverter;
+import org.apache.commons.lang3.RandomStringUtils;
+
+import java.time.Clock;
+import java.time.LocalDate;
+
+public class Fixture {
+
+ private Fixture() {
+ super();
+ }
+
+ public static Cheque createRandomCheque() {
+ final MICR micr = new MICR();
+ micr.setChequeNumber(RandomStringUtils.randomNumeric(2));
+ micr.setBranchSortCode(RandomStringUtils.randomAlphanumeric(11));
+ micr.setAccountNumber(RandomStringUtils.randomAlphanumeric(34));
+
+ final Cheque cheque = new Cheque();
+ cheque.setMicr(micr);
+ cheque.setDrawee(RandomStringUtils.randomAlphanumeric(2048));
+ cheque.setDrawer(RandomStringUtils.randomAlphanumeric(256));
+ cheque.setPayee(RandomStringUtils.randomAlphanumeric(256));
+ cheque.setAmount(Double.valueOf(5000.00).toString());
+ cheque.setDateIssued(DateConverter.toIsoString(LocalDate.now(Clock.systemUTC())));
+ cheque.setJournalEntryIdentifier(RandomStringUtils.randomAlphanumeric(2200));
+
+ return cheque;
+ }
+}
diff --git a/component-test/src/main/java/io/mifos/cheque/SuiteTestEnvironment.java b/component-test/src/main/java/io/mifos/cheque/SuiteTestEnvironment.java
new file mode 100644
index 0000000..5f73420
--- /dev/null
+++ b/component-test/src/main/java/io/mifos/cheque/SuiteTestEnvironment.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2017 Kuelap, Inc.
+ *
+ * All Rights Reserved.
+ *
+ * NOTICE: All information contained herein is, and remains
+ * the property of Kuelap, Inc and its suppliers, if any.
+ * The intellectual and technical concepts contained herein
+ * are proprietary to Kuelap, Inc and its suppliers and may
+ * be covered by U.S. and Foreign Patents, patents in process,
+ * and are protected by trade secret or copyright law.
+ * Dissemination of this information or reproduction of this material
+ * is strictly forbidden unless prior written permission is obtained
+ * Kuelap, Inc.
+ */
+package io.mifos.cheque;
+
+
+import io.mifos.core.test.env.TestEnvironment;
+import io.mifos.core.test.fixture.cassandra.CassandraInitializer;
+import io.mifos.core.test.fixture.mariadb.MariaDBInitializer;
+import org.junit.ClassRule;
+import org.junit.rules.RuleChain;
+import org.junit.rules.RunExternalResourceOnce;
+import org.junit.rules.TestRule;
+
+/**
+ * @author Myrle Krantz
+ */
+public class SuiteTestEnvironment {
+ static final String APP_NAME = "cheques-v1";
+ static final TestEnvironment testEnvironment = new TestEnvironment(APP_NAME);
+ static final CassandraInitializer cassandraInitializer = new CassandraInitializer();
+ static final MariaDBInitializer mariaDBInitializer = new MariaDBInitializer();
+
+ @ClassRule
+ public static TestRule orderClassRules = RuleChain
+ .outerRule(new RunExternalResourceOnce(testEnvironment))
+ .around(new RunExternalResourceOnce(cassandraInitializer))
+ .around(new RunExternalResourceOnce(mariaDBInitializer));
+}
diff --git a/component-test/src/main/java/io/mifos/cheque/TestCheques.java b/component-test/src/main/java/io/mifos/cheque/TestCheques.java
new file mode 100644
index 0000000..78fd545
--- /dev/null
+++ b/component-test/src/main/java/io/mifos/cheque/TestCheques.java
@@ -0,0 +1,210 @@
+/*
+ * Copyright 2017 Kuelap, Inc.
+ *
+ * All Rights Reserved.
+ *
+ * NOTICE: All information contained herein is, and remains
+ * the property of Kuelap, Inc and its suppliers, if any.
+ * The intellectual and technical concepts contained herein
+ * are proprietary to Kuelap, Inc and its suppliers and may
+ * be covered by U.S. and Foreign Patents, patents in process,
+ * and are protected by trade secret or copyright law.
+ * Dissemination of this information or reproduction of this material
+ * is strictly forbidden unless prior written permission is obtained
+ * Kuelap, Inc.
+ */
+package io.mifos.cheque;
+
+import com.google.common.collect.Sets;
+import io.mifos.accounting.api.v1.domain.Creditor;
+import io.mifos.accounting.api.v1.domain.Debtor;
+import io.mifos.accounting.api.v1.domain.JournalEntry;
+import io.mifos.cheque.api.v1.EventConstants;
+import io.mifos.cheque.api.v1.client.IssuingCount;
+import io.mifos.cheque.api.v1.domain.Action;
+import io.mifos.cheque.api.v1.domain.Cheque;
+import io.mifos.cheque.api.v1.domain.ChequeProcessingCommand;
+import io.mifos.cheque.api.v1.domain.ChequeTransaction;
+import io.mifos.cheque.api.v1.domain.State;
+import io.mifos.cheque.service.internal.format.MICRParser;
+import io.mifos.cheque.service.internal.service.helper.AccountingService;
+import io.mifos.cheque.service.internal.service.helper.DepositService;
+import io.mifos.cheque.service.internal.service.helper.OrganizationService;
+import org.apache.commons.lang3.RandomStringUtils;
+import org.junit.Assert;
+import org.junit.Test;
+import org.mockito.Matchers;
+import org.mockito.Mockito;
+import org.springframework.boot.test.mock.mockito.MockBean;
+
+import java.util.Collections;
+import java.util.UUID;
+
+public class TestCheques extends AbstractChequeTest {
+
+ @MockBean
+ private OrganizationService organizationServiceSpy;
+
+ @MockBean
+ private DepositService depositServiceSpy;
+
+ @MockBean
+ private AccountingService accountingServiceSpy;
+
+ public TestCheques() {
+ super();
+ }
+
+ @Test
+ public void shouldProcessChequeOnUs() throws Exception {
+ final Cheque randomCheque = Fixture.createRandomCheque();
+
+ final IssuingCount issuingCount = new IssuingCount();
+ issuingCount.setAccountIdentifier(randomCheque.getMicr().getAccountNumber());
+ issuingCount.setAmount(100);
+
+ Mockito
+ .doAnswer(invocation -> Collections.emptyList())
+ .when(this.depositServiceSpy).getIssueChequeCharges(randomCheque.getMicr().getAccountNumber());
+
+ super.chequeManager.issue(issuingCount);
+
+ Assert.assertTrue(
+ super.eventRecorder.wait(EventConstants.ISSUE_CHEQUES, randomCheque.getMicr().getAccountNumber())
+ );
+
+ Mockito
+ .doAnswer(invocation -> true)
+ .when(this.organizationServiceSpy).officeExistsByBranchSortCode(randomCheque.getMicr().getBranchSortCode());
+
+ Mockito
+ .doAnswer(invocation -> true)
+ .when(this.accountingServiceSpy).accountExists(randomCheque.getMicr().getAccountNumber());
+
+
+ final ChequeTransaction chequeTransaction = new ChequeTransaction();
+ chequeTransaction.setCheque(randomCheque);
+ chequeTransaction.setCreditorAccountNumber(RandomStringUtils.randomAlphanumeric(34));
+ super.chequeManager.process(chequeTransaction);
+
+ Assert.assertTrue(
+ super.eventRecorder.wait(EventConstants.CHEQUE_TRANSACTION, MICRParser.toIdentifier(randomCheque.getMicr()))
+ );
+
+ final Cheque fetchedCheque = super.chequeManager.get(MICRParser.toIdentifier(randomCheque.getMicr()));
+ Assert.assertNotNull(fetchedCheque);
+ Assert.assertEquals(State.PROCESSED.name(), fetchedCheque.getState());
+
+ Mockito
+ .verify(this.accountingServiceSpy, Mockito.times(1))
+ .processJournalEntry(Matchers.any(JournalEntry.class));
+ }
+
+ @Test
+ public void shouldApproveChequeNotOnUs() throws Exception {
+ final Cheque randomCheque = Fixture.createRandomCheque();
+
+ Mockito
+ .doAnswer(invocation -> false)
+ .when(this.organizationServiceSpy).officeExistsByBranchSortCode(randomCheque.getMicr().getBranchSortCode());
+
+ Mockito
+ .doAnswer(invocation -> false)
+ .when(this.accountingServiceSpy).accountExists(randomCheque.getMicr().getAccountNumber());
+
+
+ final ChequeTransaction chequeTransaction = new ChequeTransaction();
+ chequeTransaction.setCheque(randomCheque);
+ chequeTransaction.setCreditorAccountNumber(RandomStringUtils.randomAlphanumeric(34));
+ super.chequeManager.process(chequeTransaction);
+
+ Assert.assertTrue(
+ super.eventRecorder.wait(EventConstants.CHEQUE_TRANSACTION, MICRParser.toIdentifier(randomCheque.getMicr()))
+ );
+
+ final Cheque fetchedCheque = super.chequeManager.get(MICRParser.toIdentifier(randomCheque.getMicr()));
+ Assert.assertNotNull(fetchedCheque);
+ Assert.assertEquals(State.PENDING.name(), fetchedCheque.getState());
+
+ final ChequeProcessingCommand chequeProcessingCommand = new ChequeProcessingCommand();
+ chequeProcessingCommand.setAction(Action.APPROVE.name());
+
+ super.chequeManager.process(MICRParser.toIdentifier(fetchedCheque.getMicr()), chequeProcessingCommand);
+
+ Assert.assertTrue(
+ super.eventRecorder.wait(EventConstants.CHEQUE_TRANSACTION_APPROVED,
+ MICRParser.toIdentifier(fetchedCheque.getMicr()))
+ );
+
+ final Cheque approvedCheque = super.chequeManager.get(MICRParser.toIdentifier(randomCheque.getMicr()));
+ Assert.assertNotNull(approvedCheque);
+ Assert.assertEquals(State.PROCESSED.name(), approvedCheque.getState());
+
+ Mockito
+ .verify(this.accountingServiceSpy, Mockito.times(1))
+ .processJournalEntry(Matchers.any(JournalEntry.class));
+ }
+
+ @Test
+ public void shouldCancelChequeNotOnUs() throws Exception {
+ final Cheque randomCheque = Fixture.createRandomCheque();
+
+ Mockito
+ .doAnswer(invocation -> false)
+ .when(this.organizationServiceSpy).officeExistsByBranchSortCode(randomCheque.getMicr().getBranchSortCode());
+
+ Mockito
+ .doAnswer(invocation -> false)
+ .when(this.accountingServiceSpy).accountExists(randomCheque.getMicr().getAccountNumber());
+
+
+ final ChequeTransaction chequeTransaction = new ChequeTransaction();
+ chequeTransaction.setCheque(randomCheque);
+ chequeTransaction.setCreditorAccountNumber(RandomStringUtils.randomAlphanumeric(34));
+ super.chequeManager.process(chequeTransaction);
+
+ Assert.assertTrue(
+ super.eventRecorder.wait(EventConstants.CHEQUE_TRANSACTION, MICRParser.toIdentifier(randomCheque.getMicr()))
+ );
+
+ final Cheque fetchedCheque = super.chequeManager.get(MICRParser.toIdentifier(randomCheque.getMicr()));
+ Assert.assertNotNull(fetchedCheque);
+ Assert.assertEquals(State.PENDING.name(), fetchedCheque.getState());
+
+ final ChequeProcessingCommand chequeProcessingCommand = new ChequeProcessingCommand();
+ chequeProcessingCommand.setAction(Action.CANCEL.name());
+
+ Mockito
+ .doAnswer(invocation -> {
+ final JournalEntry journalEntry = new JournalEntry();
+ journalEntry.setTransactionIdentifier(UUID.randomUUID().toString());
+
+ final Debtor debtor = new Debtor();
+ debtor.setAccountNumber(RandomStringUtils.randomAlphanumeric(34));
+ debtor.setAmount("100.00");
+ journalEntry.setDebtors(Sets.newHashSet(debtor));
+
+ final Creditor creditor = new Creditor();
+ creditor.setAccountNumber(RandomStringUtils.randomAlphanumeric(34));
+ creditor.setAmount("100.00");
+ journalEntry.setCreditors(Sets.newHashSet(creditor));
+ return journalEntry;
+ })
+ .when(this.accountingServiceSpy).findJournalEntry(Matchers.anyString());
+
+ super.chequeManager.process(MICRParser.toIdentifier(fetchedCheque.getMicr()), chequeProcessingCommand);
+
+ Assert.assertTrue(
+ super.eventRecorder.wait(EventConstants.CHEQUE_TRANSACTION_CANCELED,
+ MICRParser.toIdentifier(fetchedCheque.getMicr()))
+ );
+
+ final Cheque canceledCheque = super.chequeManager.get(MICRParser.toIdentifier(randomCheque.getMicr()));
+ Assert.assertNotNull(canceledCheque);
+ Assert.assertEquals(State.CANCELED.name(), canceledCheque.getState());
+
+ Mockito
+ .verify(this.accountingServiceSpy, Mockito.times(2))
+ .processJournalEntry(Matchers.any(JournalEntry.class));
+ }
+}
diff --git a/component-test/src/main/java/io/mifos/cheque/TestIssuingCheques.java b/component-test/src/main/java/io/mifos/cheque/TestIssuingCheques.java
new file mode 100644
index 0000000..474ad4b
--- /dev/null
+++ b/component-test/src/main/java/io/mifos/cheque/TestIssuingCheques.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright 2017 Kuelap, Inc.
+ *
+ * All Rights Reserved.
+ *
+ * NOTICE: All information contained herein is, and remains
+ * the property of Kuelap, Inc and its suppliers, if any.
+ * The intellectual and technical concepts contained herein
+ * are proprietary to Kuelap, Inc and its suppliers and may
+ * be covered by U.S. and Foreign Patents, patents in process,
+ * and are protected by trade secret or copyright law.
+ * Dissemination of this information or reproduction of this material
+ * is strictly forbidden unless prior written permission is obtained
+ * Kuelap, Inc.
+ */
+package io.mifos.cheque;
+
+import io.mifos.cheque.api.v1.client.IssuingCount;
+import io.mifos.cheque.service.internal.repository.IssuedChequeEntity;
+import io.mifos.cheque.service.internal.repository.IssuedChequeRepository;
+import io.mifos.cheque.service.internal.service.helper.AccountingService;
+import io.mifos.cheque.service.internal.service.helper.DepositService;
+import org.apache.commons.lang3.RandomStringUtils;
+import org.junit.Assert;
+import org.junit.Test;
+import org.mockito.Mockito;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.mock.mockito.MockBean;
+
+import java.util.Collections;
+import java.util.Optional;
+
+public class TestIssuingCheques extends AbstractChequeTest {
+
+ @MockBean
+ private DepositService depositServiceSpy;
+
+ @MockBean
+ private AccountingService accountingServiceSpy;
+
+ @Autowired
+ private IssuedChequeRepository issuedChequeRepository;
+
+ public TestIssuingCheques() {
+ super();
+ }
+
+ @Test
+ public void shouldIssueChequesWithGivenStartSequence() throws Exception {
+ final IssuingCount issuingCount = new IssuingCount();
+ issuingCount.setAccountIdentifier(RandomStringUtils.randomAlphanumeric(34));
+ issuingCount.setStart(201);
+ issuingCount.setAmount(10);
+
+ Mockito
+ .doAnswer(invocation -> Collections.emptyList())
+ .when(this.depositServiceSpy).getIssueChequeCharges(issuingCount.getAccountIdentifier());
+
+ final String accountIdentifier = super.chequeManager.issue(issuingCount);
+ Assert.assertEquals(issuingCount.getAccountIdentifier(), accountIdentifier);
+
+ final Optional<IssuedChequeEntity> optionalIssuedCheque =
+ this.issuedChequeRepository.findByAccountIdentifier(accountIdentifier);
+
+ Assert.assertTrue(optionalIssuedCheque.isPresent());
+ Assert.assertEquals(Integer.valueOf(210), optionalIssuedCheque.get().getLastIssuedNumber());
+ }
+
+ @Test
+ public void shouldIssueChequesForNextIssuing() throws Exception {
+ final IssuingCount issuingCount = new IssuingCount();
+ issuingCount.setAccountIdentifier(RandomStringUtils.randomAlphanumeric(34));
+ issuingCount.setAmount(50);
+
+ Mockito
+ .doAnswer(invocation -> Collections.emptyList())
+ .when(this.depositServiceSpy).getIssueChequeCharges(issuingCount.getAccountIdentifier());
+
+ final String accountIdentifier = super.chequeManager.issue(issuingCount);
+ Assert.assertEquals(issuingCount.getAccountIdentifier(), accountIdentifier);
+
+ final Optional<IssuedChequeEntity> optionalIssuedCheque =
+ this.issuedChequeRepository.findByAccountIdentifier(accountIdentifier);
+
+ Assert.assertTrue(optionalIssuedCheque.isPresent());
+ Assert.assertEquals(Integer.valueOf(50), optionalIssuedCheque.get().getLastIssuedNumber());
+
+ issuingCount.setAmount(50);
+
+ final String nextFetchedAccountIdentifier = super.chequeManager.issue(issuingCount);
+ Assert.assertEquals(issuingCount.getAccountIdentifier(), nextFetchedAccountIdentifier);
+
+ final Optional<IssuedChequeEntity> nextOptionalIssuedCheque =
+ this.issuedChequeRepository.findByAccountIdentifier(nextFetchedAccountIdentifier);
+
+ Assert.assertTrue(nextOptionalIssuedCheque.isPresent());
+ Assert.assertEquals(Integer.valueOf(100), nextOptionalIssuedCheque.get().getLastIssuedNumber());
+ }
+}
diff --git a/component-test/src/main/java/io/mifos/cheque/TestSuite.java b/component-test/src/main/java/io/mifos/cheque/TestSuite.java
new file mode 100644
index 0000000..8dfa201
--- /dev/null
+++ b/component-test/src/main/java/io/mifos/cheque/TestSuite.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2017 Kuelap, Inc.
+ *
+ * All Rights Reserved.
+ *
+ * NOTICE: All information contained herein is, and remains
+ * the property of Kuelap, Inc and its suppliers, if any.
+ * The intellectual and technical concepts contained herein
+ * are proprietary to Kuelap, Inc and its suppliers and may
+ * be covered by U.S. and Foreign Patents, patents in process,
+ * and are protected by trade secret or copyright law.
+ * Dissemination of this information or reproduction of this material
+ * is strictly forbidden unless prior written permission is obtained
+ * Kuelap, Inc.
+ */
+package io.mifos.cheque;
+
+import org.junit.runner.RunWith;
+import org.junit.runners.Suite;
+
+/**
+ * @author Myrle Krantz
+ */
+@RunWith(Suite.class)
+@Suite.SuiteClasses({
+ TestIssuingCheques.class,
+ TestCheques.class
+})
+public class TestSuite extends SuiteTestEnvironment {
+}
diff --git a/component-test/src/main/java/io/mifos/cheque/listener/ChequeEventListener.java b/component-test/src/main/java/io/mifos/cheque/listener/ChequeEventListener.java
new file mode 100644
index 0000000..1432897
--- /dev/null
+++ b/component-test/src/main/java/io/mifos/cheque/listener/ChequeEventListener.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright 2017 Kuelap, Inc.
+ *
+ * All Rights Reserved.
+ *
+ * NOTICE: All information contained herein is, and remains
+ * the property of Kuelap, Inc and its suppliers, if any.
+ * The intellectual and technical concepts contained herein
+ * are proprietary to Kuelap, Inc and its suppliers and may
+ * be covered by U.S. and Foreign Patents, patents in process,
+ * and are protected by trade secret or copyright law.
+ * Dissemination of this information or reproduction of this material
+ * is strictly forbidden unless prior written permission is obtained
+ * Kuelap, Inc.
+ */
+package io.mifos.cheque.listener;
+
+import io.mifos.cheque.AbstractChequeTest;
+import io.mifos.cheque.api.v1.EventConstants;
+import io.mifos.core.lang.config.TenantHeaderFilter;
+import io.mifos.core.test.listener.EventRecorder;
+import org.slf4j.Logger;
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.jms.annotation.JmsListener;
+import org.springframework.messaging.handler.annotation.Header;
+import org.springframework.stereotype.Component;
+
+@Component
+public class ChequeEventListener {
+
+ private final Logger logger;
+ private final EventRecorder eventRecorder;
+
+ public ChequeEventListener(@Qualifier(AbstractChequeTest.TEST_LOGGER) final Logger logger,
+ final EventRecorder eventRecorder) {
+ super();
+ this.logger = logger;
+ this.eventRecorder = eventRecorder;
+ }
+
+ @JmsListener(
+ destination = EventConstants.DESTINATION,
+ selector = EventConstants.SELECTOR_ISSUE_CHEQUES,
+ subscription = EventConstants.DESTINATION
+ )
+ public void onIssueCheques(@Header(TenantHeaderFilter.TENANT_HEADER) final String tenant,
+ final String payload) {
+ this.logger.debug("Cheques issued.");
+ this.eventRecorder.event(tenant, EventConstants.ISSUE_CHEQUES, payload, String.class);
+ }
+
+ @JmsListener(
+ destination = EventConstants.DESTINATION,
+ selector = EventConstants.SELECTOR_CHEQUE_TRANSACTION,
+ subscription = EventConstants.DESTINATION
+ )
+ public void onChequeTransaction(@Header(TenantHeaderFilter.TENANT_HEADER) final String tenant,
+ final String payload) {
+ this.logger.debug("Cheque transaction processed.");
+ this.eventRecorder.event(tenant, EventConstants.CHEQUE_TRANSACTION, payload, String.class);
+ }
+
+ @JmsListener(
+ destination = EventConstants.DESTINATION,
+ selector = EventConstants.SELECTOR_CHEQUE_TRANSACTION_APPROVED,
+ subscription = EventConstants.DESTINATION
+ )
+ public void onChequeApproved(@Header(TenantHeaderFilter.TENANT_HEADER) final String tenant,
+ final String payload) {
+ this.logger.debug("Cheque transaction approved.");
+ this.eventRecorder.event(tenant, EventConstants.CHEQUE_TRANSACTION_APPROVED, payload, String.class);
+ }
+
+ @JmsListener(
+ destination = EventConstants.DESTINATION,
+ selector = EventConstants.SELECTOR_CHEQUE_TRANSACTION_CANCELED,
+ subscription = EventConstants.DESTINATION
+ )
+ public void onChequeCanceled(@Header(TenantHeaderFilter.TENANT_HEADER) final String tenant,
+ final String payload) {
+ this.logger.debug("Cheque transaction canceled.");
+ this.eventRecorder.event(tenant, EventConstants.CHEQUE_TRANSACTION_CANCELED, payload, String.class);
+ }
+}
diff --git a/component-test/src/main/java/io/mifos/cheque/listener/ManagementEventMigrationListener.java b/component-test/src/main/java/io/mifos/cheque/listener/ManagementEventMigrationListener.java
new file mode 100644
index 0000000..1afa7ad
--- /dev/null
+++ b/component-test/src/main/java/io/mifos/cheque/listener/ManagementEventMigrationListener.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2017 Kuelap, Inc.
+ *
+ * All Rights Reserved.
+ *
+ * NOTICE: All information contained herein is, and remains
+ * the property of Kuelap, Inc and its suppliers, if any.
+ * The intellectual and technical concepts contained herein
+ * are proprietary to Kuelap, Inc and its suppliers and may
+ * be covered by U.S. and Foreign Patents, patents in process,
+ * and are protected by trade secret or copyright law.
+ * Dissemination of this information or reproduction of this material
+ * is strictly forbidden unless prior written permission is obtained
+ * Kuelap, Inc.
+ */
+package io.mifos.cheque.listener;
+
+import io.mifos.cheque.AbstractChequeTest;
+import io.mifos.cheque.api.v1.EventConstants;
+import io.mifos.core.lang.config.TenantHeaderFilter;
+import io.mifos.core.test.listener.EventRecorder;
+import org.slf4j.Logger;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.jms.annotation.JmsListener;
+import org.springframework.messaging.handler.annotation.Header;
+import org.springframework.stereotype.Component;
+
+@Component
+public class ManagementEventMigrationListener {
+
+ private final Logger logger;
+ private final EventRecorder eventRecorder;
+
+ @Autowired
+ public ManagementEventMigrationListener(@Qualifier(AbstractChequeTest.TEST_LOGGER) final Logger logger,
+ final EventRecorder eventRecorder) {
+ super();
+ this.logger = logger;
+ this.eventRecorder = eventRecorder;
+ }
+
+ @JmsListener(
+ destination = EventConstants.DESTINATION,
+ selector = EventConstants.SELECTOR_INITIALIZE,
+ subscription = EventConstants.DESTINATION
+ )
+ public void onInitialize(@Header(TenantHeaderFilter.TENANT_HEADER) final String tenant,
+ final String payload) {
+ this.logger.debug("Service initialized.");
+ this.eventRecorder.event(tenant, EventConstants.INITIALIZE, payload, String.class);
+ }
+}
diff --git a/component-test/src/main/resources/logback-test.xml b/component-test/src/main/resources/logback-test.xml
new file mode 100644
index 0000000..321fb42
--- /dev/null
+++ b/component-test/src/main/resources/logback-test.xml
@@ -0,0 +1,32 @@
+<!--
+
+ Copyright 2017 Kuelap, Inc.
+
+ All Rights Reserved.
+
+ NOTICE: All information contained herein is, and remains
+ the property of Kuelap, Inc and its suppliers, if any.
+ The intellectual and technical concepts contained herein
+ are proprietary to Kuelap, Inc and its suppliers and may
+ be covered by U.S. and Foreign Patents, patents in process,
+ and are protected by trade secret or copyright law.
+ Dissemination of this information or reproduction of this material
+ is strictly forbidden unless prior written permission is obtained
+ Kuelap, Inc.
+
+-->
+<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>
+
+ <logger name="org" level="INFO"/>
+ <logger name="com" level="INFO"/>
+ <logger name="ch" level="OFF"/>
+
+ <root level="DEBUG">
+ <appender-ref ref="STDOUT"/>
+ </root>
+</configuration>
\ No newline at end of file
diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 0000000..400f155
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..71e5108
--- /dev/null
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,6 @@
+#Tue Jul 25 13:05:05 CEST 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-all.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..af26fc0
--- /dev/null
+++ b/service/build.gradle
@@ -0,0 +1,67 @@
+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.kuelap.cheques', name: 'api', version: project.version],
+ [group: 'io.mifos.anubis', name: 'library', version: versions.frameworkanubis],
+ [group: 'io.mifos.accounting', name: 'api', version: versions.frameworkaccounting],
+ [group: 'io.mifos.deposit-account-management', name: 'api', version: versions.frameworkdeposit],
+ [group: 'io.mifos.office', name: 'api', version: versions.frameworkoffice],
+ [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],
+ [group: 'org.threeten', name: 'threeten-extra', version: '1.2']
+ )
+}
+
+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..081feb4
--- /dev/null
+++ b/service/settings.gradle
@@ -0,0 +1 @@
+rootProject.name = 'service'
\ No newline at end of file
diff --git a/service/src/main/java/io/mifos/cheque/service/ChequeApplication.java b/service/src/main/java/io/mifos/cheque/service/ChequeApplication.java
new file mode 100644
index 0000000..78d9674
--- /dev/null
+++ b/service/src/main/java/io/mifos/cheque/service/ChequeApplication.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2017 Kuelap, Inc.
+ *
+ * All Rights Reserved.
+ *
+ * NOTICE: All information contained herein is, and remains
+ * the property of Kuelap, Inc and its suppliers, if any.
+ * The intellectual and technical concepts contained herein
+ * are proprietary to Kuelap, Inc and its suppliers and may
+ * be covered by U.S. and Foreign Patents, patents in process,
+ * and are protected by trade secret or copyright law.
+ * Dissemination of this information or reproduction of this material
+ * is strictly forbidden unless prior written permission is obtained
+ * Kuelap, Inc.
+ */
+package io.mifos.cheque.service;
+
+import org.springframework.boot.SpringApplication;
+
+public class ChequeApplication {
+
+ public ChequeApplication() {
+ super();
+ }
+
+ public static void main(String[] args) {
+ SpringApplication.run(ChequeConfiguration.class, args);
+ }
+}
diff --git a/service/src/main/java/io/mifos/cheque/service/ChequeConfiguration.java b/service/src/main/java/io/mifos/cheque/service/ChequeConfiguration.java
new file mode 100644
index 0000000..f1f0570
--- /dev/null
+++ b/service/src/main/java/io/mifos/cheque/service/ChequeConfiguration.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright 2017 Kuelap, Inc.
+ *
+ * All Rights Reserved.
+ *
+ * NOTICE: All information contained herein is, and remains
+ * the property of Kuelap, Inc and its suppliers, if any.
+ * The intellectual and technical concepts contained herein
+ * are proprietary to Kuelap, Inc and its suppliers and may
+ * be covered by U.S. and Foreign Patents, patents in process,
+ * and are protected by trade secret or copyright law.
+ * Dissemination of this information or reproduction of this material
+ * is strictly forbidden unless prior written permission is obtained
+ * Kuelap, Inc.
+ */
+package io.mifos.cheque.service;
+
+import io.mifos.accounting.api.v1.client.LedgerManager;
+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 io.mifos.deposit.api.v1.client.DepositAccountManager;
+import io.mifos.office.api.v1.client.OrganizationManager;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
+import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
+import org.springframework.cloud.netflix.feign.EnableFeignClients;
+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;
+import org.springframework.web.servlet.config.annotation.PathMatchConfigurer;
+import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
+
+@SuppressWarnings("WeakerAccess")
+@Configuration
+@EnableAutoConfiguration
+@EnableDiscoveryClient
+@EnableAsync
+@EnableTenantContext
+@EnableCassandra
+@EnableMariaDB
+@EnableCommandProcessing
+@EnableAnubis
+@EnableServiceException
+@EnableFeignClients(
+ clients = {
+ LedgerManager.class,
+ DepositAccountManager.class,
+ OrganizationManager.class
+ }
+)
+@ComponentScan({
+ "io.mifos.cheque.service.rest",
+ "io.mifos.cheque.service.internal.service",
+ "io.mifos.cheque.service.internal.repository",
+ "io.mifos.cheque.service.internal.command.handler"
+})
+@EnableJpaRepositories({
+ "io.mifos.cheque.service.internal.repository"
+})
+public class ChequeConfiguration extends WebMvcConfigurerAdapter {
+
+ public ChequeConfiguration() {
+ super();
+ }
+
+ @Bean(name = ServiceConstants.LOGGER_NAME)
+ public Logger logger() {
+ return LoggerFactory.getLogger(ServiceConstants.LOGGER_NAME);
+ }
+
+ @Override
+ public void configurePathMatch(final PathMatchConfigurer configurer) {
+ configurer.setUseSuffixPatternMatch(Boolean.FALSE);
+ }
+}
diff --git a/service/src/main/java/io/mifos/cheque/service/ServiceConstants.java b/service/src/main/java/io/mifos/cheque/service/ServiceConstants.java
new file mode 100644
index 0000000..88bcca3
--- /dev/null
+++ b/service/src/main/java/io/mifos/cheque/service/ServiceConstants.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright 2017 Kuelap, Inc.
+ *
+ * All Rights Reserved.
+ *
+ * NOTICE: All information contained herein is, and remains
+ * the property of Kuelap, Inc and its suppliers, if any.
+ * The intellectual and technical concepts contained herein
+ * are proprietary to Kuelap, Inc and its suppliers and may
+ * be covered by U.S. and Foreign Patents, patents in process,
+ * and are protected by trade secret or copyright law.
+ * Dissemination of this information or reproduction of this material
+ * is strictly forbidden unless prior written permission is obtained
+ * Kuelap, Inc.
+ */
+package io.mifos.cheque.service;
+
+public interface ServiceConstants {
+ String LOGGER_NAME = "cheques";
+
+ String TX_ISSUE_CHEQUES = "ICHQ";
+}
diff --git a/service/src/main/java/io/mifos/cheque/service/internal/command/ApproveChequeTransactionCommand.java b/service/src/main/java/io/mifos/cheque/service/internal/command/ApproveChequeTransactionCommand.java
new file mode 100644
index 0000000..64dc0f0
--- /dev/null
+++ b/service/src/main/java/io/mifos/cheque/service/internal/command/ApproveChequeTransactionCommand.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2017 Kuelap, Inc.
+ *
+ * All Rights Reserved.
+ *
+ * NOTICE: All information contained herein is, and remains
+ * the property of Kuelap, Inc and its suppliers, if any.
+ * The intellectual and technical concepts contained herein
+ * are proprietary to Kuelap, Inc and its suppliers and may
+ * be covered by U.S. and Foreign Patents, patents in process,
+ * and are protected by trade secret or copyright law.
+ * Dissemination of this information or reproduction of this material
+ * is strictly forbidden unless prior written permission is obtained
+ * Kuelap, Inc.
+ */
+package io.mifos.cheque.service.internal.command;
+
+public class ApproveChequeTransactionCommand {
+ private final String identifier;
+
+ public ApproveChequeTransactionCommand(final String identifier) {
+ super();
+ this.identifier = identifier;
+ }
+
+ public String identifier() {
+ return this.identifier;
+ }
+}
diff --git a/service/src/main/java/io/mifos/cheque/service/internal/command/CancelChequeTransactionCommand.java b/service/src/main/java/io/mifos/cheque/service/internal/command/CancelChequeTransactionCommand.java
new file mode 100644
index 0000000..8bec5e3
--- /dev/null
+++ b/service/src/main/java/io/mifos/cheque/service/internal/command/CancelChequeTransactionCommand.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2017 Kuelap, Inc.
+ *
+ * All Rights Reserved.
+ *
+ * NOTICE: All information contained herein is, and remains
+ * the property of Kuelap, Inc and its suppliers, if any.
+ * The intellectual and technical concepts contained herein
+ * are proprietary to Kuelap, Inc and its suppliers and may
+ * be covered by U.S. and Foreign Patents, patents in process,
+ * and are protected by trade secret or copyright law.
+ * Dissemination of this information or reproduction of this material
+ * is strictly forbidden unless prior written permission is obtained
+ * Kuelap, Inc.
+ */
+package io.mifos.cheque.service.internal.command;
+
+public class CancelChequeTransactionCommand {
+ private final String identifier;
+
+ public CancelChequeTransactionCommand(final String identifier) {
+ super();
+ this.identifier = identifier;
+ }
+
+ public String identifier() {
+ return this.identifier;
+ }
+}
diff --git a/service/src/main/java/io/mifos/cheque/service/internal/command/ChequeTransactionCommand.java b/service/src/main/java/io/mifos/cheque/service/internal/command/ChequeTransactionCommand.java
new file mode 100644
index 0000000..81c46e6
--- /dev/null
+++ b/service/src/main/java/io/mifos/cheque/service/internal/command/ChequeTransactionCommand.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2017 Kuelap, Inc.
+ *
+ * All Rights Reserved.
+ *
+ * NOTICE: All information contained herein is, and remains
+ * the property of Kuelap, Inc and its suppliers, if any.
+ * The intellectual and technical concepts contained herein
+ * are proprietary to Kuelap, Inc and its suppliers and may
+ * be covered by U.S. and Foreign Patents, patents in process,
+ * and are protected by trade secret or copyright law.
+ * Dissemination of this information or reproduction of this material
+ * is strictly forbidden unless prior written permission is obtained
+ * Kuelap, Inc.
+ */
+package io.mifos.cheque.service.internal.command;
+
+import io.mifos.cheque.api.v1.domain.Cheque;
+
+public class ChequeTransactionCommand {
+ private final Cheque cheque;
+ private final String creditorAccountNumber;
+
+ public ChequeTransactionCommand(final Cheque cheque, final String creditorAccountNumber) {
+ super();
+ this.cheque = cheque;
+ this.creditorAccountNumber = creditorAccountNumber;
+ }
+
+ public Cheque cheque() {
+ return this.cheque;
+ }
+
+ public String creditorAccountNumber() {
+ return this.creditorAccountNumber;
+ }
+}
diff --git a/service/src/main/java/io/mifos/cheque/service/internal/command/IssueChequesCommand.java b/service/src/main/java/io/mifos/cheque/service/internal/command/IssueChequesCommand.java
new file mode 100644
index 0000000..68c8de7
--- /dev/null
+++ b/service/src/main/java/io/mifos/cheque/service/internal/command/IssueChequesCommand.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2017 Kuelap, Inc.
+ *
+ * All Rights Reserved.
+ *
+ * NOTICE: All information contained herein is, and remains
+ * the property of Kuelap, Inc and its suppliers, if any.
+ * The intellectual and technical concepts contained herein
+ * are proprietary to Kuelap, Inc and its suppliers and may
+ * be covered by U.S. and Foreign Patents, patents in process,
+ * and are protected by trade secret or copyright law.
+ * Dissemination of this information or reproduction of this material
+ * is strictly forbidden unless prior written permission is obtained
+ * Kuelap, Inc.
+ */
+package io.mifos.cheque.service.internal.command;
+
+public class IssueChequesCommand {
+
+ private final String accountIdentifer;
+ private final Integer startSequence;
+ private final Integer issueCount;
+
+ public IssueChequesCommand(final String accountIdentifer, final Integer startSequence, final Integer issueCount) {
+ super();
+ this.accountIdentifer = accountIdentifer;
+ this.startSequence = startSequence;
+ this.issueCount = issueCount;
+ }
+
+ public String accountIdentifer() {
+ return this.accountIdentifer;
+ }
+
+ public Integer startSequence() {
+ return this.startSequence;
+ }
+
+ public Integer issueCount() {
+ return this.issueCount;
+ }
+}
diff --git a/service/src/main/java/io/mifos/cheque/service/internal/command/MigrationCommand.java b/service/src/main/java/io/mifos/cheque/service/internal/command/MigrationCommand.java
new file mode 100644
index 0000000..fdbf526
--- /dev/null
+++ b/service/src/main/java/io/mifos/cheque/service/internal/command/MigrationCommand.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright 2017 Kuelap, Inc.
+ *
+ * All Rights Reserved.
+ *
+ * NOTICE: All information contained herein is, and remains
+ * the property of Kuelap, Inc and its suppliers, if any.
+ * The intellectual and technical concepts contained herein
+ * are proprietary to Kuelap, Inc and its suppliers and may
+ * be covered by U.S. and Foreign Patents, patents in process,
+ * and are protected by trade secret or copyright law.
+ * Dissemination of this information or reproduction of this material
+ * is strictly forbidden unless prior written permission is obtained
+ * Kuelap, Inc.
+ */
+package io.mifos.cheque.service.internal.command;
+
+public class MigrationCommand {
+ public MigrationCommand() {
+ super();
+ }
+}
diff --git a/service/src/main/java/io/mifos/cheque/service/internal/command/handler/ChequeAggregate.java b/service/src/main/java/io/mifos/cheque/service/internal/command/handler/ChequeAggregate.java
new file mode 100644
index 0000000..95fd298
--- /dev/null
+++ b/service/src/main/java/io/mifos/cheque/service/internal/command/handler/ChequeAggregate.java
@@ -0,0 +1,265 @@
+/*
+ * Copyright 2017 Kuelap, Inc.
+ *
+ * All Rights Reserved.
+ *
+ * NOTICE: All information contained herein is, and remains
+ * the property of Kuelap, Inc and its suppliers, if any.
+ * The intellectual and technical concepts contained herein
+ * are proprietary to Kuelap, Inc and its suppliers and may
+ * be covered by U.S. and Foreign Patents, patents in process,
+ * and are protected by trade secret or copyright law.
+ * Dissemination of this information or reproduction of this material
+ * is strictly forbidden unless prior written permission is obtained
+ * Kuelap, Inc.
+ */
+package io.mifos.cheque.service.internal.command.handler;
+
+import com.google.common.collect.Sets;
+import io.mifos.accounting.api.v1.client.AccountNotFoundException;
+import io.mifos.accounting.api.v1.domain.Creditor;
+import io.mifos.accounting.api.v1.domain.Debtor;
+import io.mifos.accounting.api.v1.domain.JournalEntry;
+import io.mifos.cheque.api.v1.EventConstants;
+import io.mifos.cheque.api.v1.domain.MICR;
+import io.mifos.cheque.api.v1.domain.State;
+import io.mifos.cheque.service.ServiceConstants;
+import io.mifos.cheque.service.internal.command.ApproveChequeTransactionCommand;
+import io.mifos.cheque.service.internal.command.CancelChequeTransactionCommand;
+import io.mifos.cheque.service.internal.command.ChequeTransactionCommand;
+import io.mifos.cheque.service.internal.command.IssueChequesCommand;
+import io.mifos.cheque.service.internal.format.MICRParser;
+import io.mifos.cheque.service.internal.mapper.ChequeMapper;
+import io.mifos.cheque.service.internal.repository.ChequeEntity;
+import io.mifos.cheque.service.internal.repository.ChequeRepository;
+import io.mifos.cheque.service.internal.repository.IssuedChequeEntity;
+import io.mifos.cheque.service.internal.repository.IssuedChequeRepository;
+import io.mifos.cheque.service.internal.service.helper.AccountingService;
+import io.mifos.cheque.service.internal.service.helper.DepositService;
+import io.mifos.cheque.service.internal.service.helper.OrganizationService;
+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.deposit.api.v1.definition.domain.Charge;
+import io.mifos.deposit.api.v1.instance.ProductInstanceNotFoundException;
+import org.slf4j.Logger;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Qualifier;
+
+import javax.transaction.Transactional;
+import java.time.Clock;
+import java.time.LocalDateTime;
+import java.util.List;
+import java.util.Optional;
+import java.util.UUID;
+import java.util.stream.Collectors;
+
+@Aggregate
+public class ChequeAggregate {
+
+ private final Logger logger;
+ private final DepositService depositService;
+ private final OrganizationService organizationService;
+ private final AccountingService accountingService;
+ private final IssuedChequeRepository issuedChequeRepository;
+ private final ChequeRepository chequeRepository;
+
+ @Autowired
+ public ChequeAggregate(@Qualifier(ServiceConstants.LOGGER_NAME) final Logger logger,
+ final DepositService depositService,
+ final OrganizationService organizationService,
+ final AccountingService accountingService,
+ final IssuedChequeRepository issuedChequeRepository,
+ final ChequeRepository chequeRepository) {
+ super();
+ this.logger = logger;
+ this.depositService = depositService;
+ this.organizationService = organizationService;
+ this.accountingService = accountingService;
+ this.issuedChequeRepository = issuedChequeRepository;
+ this.chequeRepository = chequeRepository;
+ }
+
+ @Transactional
+ @CommandHandler
+ @EventEmitter(selectorName = EventConstants.SELECTOR_NAME, selectorValue = EventConstants.ISSUE_CHEQUES)
+ public String process(final IssueChequesCommand issueChequesCommand) {
+
+ final Optional<IssuedChequeEntity> optionalIssuedCheque =
+ this.issuedChequeRepository.findByAccountIdentifier(issueChequesCommand.accountIdentifer());
+
+ final IssuedChequeEntity issuedChequeEntity = optionalIssuedCheque.orElseGet(() -> {
+ final IssuedChequeEntity toCreate = new IssuedChequeEntity();
+ toCreate.setAccountIdentifier(issueChequesCommand.accountIdentifer());
+ toCreate.setCreatedBy(UserContextHolder.checkedGetUser());
+ toCreate.setCreatedOn(LocalDateTime.now(Clock.systemUTC()));
+ return toCreate;
+ });
+
+ final Integer lastIssuedNumber;
+ if (issuedChequeEntity.getLastIssuedNumber() == null) {
+ lastIssuedNumber = issueChequesCommand.startSequence() != null
+ ? (issueChequesCommand.startSequence() - 1) + issueChequesCommand.issueCount()
+ : issueChequesCommand.issueCount();
+ } else {
+ lastIssuedNumber = issuedChequeEntity.getLastIssuedNumber() + issueChequesCommand.issueCount();
+ }
+
+ issuedChequeEntity.setLastIssuedNumber(lastIssuedNumber);
+ issuedChequeEntity.setLastModifiedBy(UserContextHolder.checkedGetUser());
+ issuedChequeEntity.setLastModifiedOn(LocalDateTime.now(Clock.systemUTC()));
+
+ this.issuedChequeRepository.save(issuedChequeEntity);
+
+ try {
+ final List<Charge> issueChequeCharges =
+ this.depositService.getIssueChequeCharges(issueChequesCommand.accountIdentifer());
+ if (!issueChequeCharges.isEmpty()) {
+ this.accountingService.bookCharges(issueChequesCommand.accountIdentifer(), issueChequeCharges);
+ }
+ } catch (final ProductInstanceNotFoundException | AccountNotFoundException ex) {
+ throw ServiceException.notFound("Account {0} not found", issueChequesCommand.accountIdentifer());
+ }
+
+ return issueChequesCommand.accountIdentifer();
+ }
+
+ @Transactional
+ @CommandHandler
+ @EventEmitter(selectorName = EventConstants.SELECTOR_NAME, selectorValue = EventConstants.CHEQUE_TRANSACTION)
+ public String process(final ChequeTransactionCommand chequeTransactionCommand) {
+ final ChequeEntity chequeEntity = ChequeMapper.map(chequeTransactionCommand.cheque());
+
+ final MICR micr = chequeTransactionCommand.cheque().getMicr();
+
+ final JournalEntry journalEntry = new JournalEntry();
+ journalEntry.setTransactionIdentifier("chq-tx-" + UUID.randomUUID().toString());
+ final String transactionType =
+ chequeEntity.getOpenCheque() != null && chequeEntity.getOpenCheque() ? "OPCQ " : "ORCQ";
+ journalEntry.setTransactionType(transactionType);
+ journalEntry.setTransactionDate(DateConverter.toIsoString(chequeEntity.getCreatedOn()));
+ journalEntry.setMessage(transactionType);
+ journalEntry.setClerk(UserContextHolder.checkedGetUser());
+
+ final Debtor debtor = new Debtor();
+ debtor.setAmount(chequeEntity.getAmount().toString());
+ if (this.onUs(micr)) {
+ final Optional<IssuedChequeEntity> optionalIssuedCheque =
+ this.issuedChequeRepository.findByAccountIdentifier(micr.getAccountNumber());
+
+ if (optionalIssuedCheque.isPresent()) {
+ final Integer lastIssuedNumber = optionalIssuedCheque.get().getLastIssuedNumber();
+ if (Integer.valueOf(chequeEntity.getChequeNumber()) > lastIssuedNumber) {
+ throw ServiceException.conflict("Unknown cheque {0}.", MICRParser.toIdentifier(micr));
+ }
+ } else {
+ throw ServiceException.conflict("Account {0} never issued cheques.", micr.getAccountNumber());
+ }
+
+ debtor.setAccountNumber(micr.getAccountNumber());
+ chequeEntity.setState(State.PROCESSED.name());
+ } else {
+ debtor.setAccountNumber(
+ this.depositService.getCashAccountForProduct(chequeTransactionCommand.creditorAccountNumber())
+ );
+ chequeEntity.setState(State.PENDING.name());
+ }
+ journalEntry.setDebtors(Sets.newHashSet(debtor));
+
+ final Creditor creditor = new Creditor();
+ creditor.setAmount(chequeEntity.getAmount().toString());
+ creditor.setAccountNumber(chequeTransactionCommand.creditorAccountNumber());
+ journalEntry.setCreditors(Sets.newHashSet(creditor));
+
+ this.accountingService.processJournalEntry(journalEntry);
+
+ chequeEntity.setJournalEntryIdentifier(journalEntry.getTransactionIdentifier());
+
+ this.chequeRepository.save(chequeEntity);
+
+ return MICRParser.toIdentifier(micr);
+ }
+
+ @Transactional
+ @CommandHandler
+ @EventEmitter(selectorName = EventConstants.SELECTOR_NAME, selectorValue = EventConstants.CHEQUE_TRANSACTION_APPROVED)
+ public String process(final ApproveChequeTransactionCommand approveChequeTransactionCommand) {
+ final MICR micr = MICRParser.fromIdentifier(approveChequeTransactionCommand.identifier());
+ final Optional<ChequeEntity> optionalCheque = this.chequeRepository.findByChequeNumberAndBranchSortCodeAndAccountNumber(
+ micr.getChequeNumber(), micr.getBranchSortCode(), micr.getAccountNumber()
+ );
+
+ if (optionalCheque.isPresent()) {
+ final ChequeEntity chequeEntity = optionalCheque.get();
+ chequeEntity.setState(State.PROCESSED.name());
+ chequeEntity.setLastModifiedBy(UserContextHolder.checkedGetUser());
+ chequeEntity.setLastModifiedOn(LocalDateTime.now(Clock.systemUTC()));
+ this.chequeRepository.save(chequeEntity);
+ return approveChequeTransactionCommand.identifier();
+ } else {
+ throw ServiceException.notFound("Cheque {0} not found.", approveChequeTransactionCommand.identifier());
+ }
+ }
+
+ @Transactional
+ @CommandHandler
+ @EventEmitter(selectorName = EventConstants.SELECTOR_NAME, selectorValue = EventConstants.CHEQUE_TRANSACTION_CANCELED)
+ public String process(final CancelChequeTransactionCommand cancelChequeTransactionCommand) {
+ final MICR micr = MICRParser.fromIdentifier(cancelChequeTransactionCommand.identifier());
+ final Optional<ChequeEntity> optionalCheque = this.chequeRepository.findByChequeNumberAndBranchSortCodeAndAccountNumber(
+ micr.getChequeNumber(), micr.getBranchSortCode(), micr.getAccountNumber()
+ );
+
+ if (optionalCheque.isPresent()) {
+
+ final ChequeEntity chequeEntity = optionalCheque.get();
+
+ final JournalEntry journalEntryToReverse =
+ this.accountingService.findJournalEntry(chequeEntity.getJournalEntryIdentifier());
+
+ final LocalDateTime now = LocalDateTime.now(Clock.systemUTC());
+
+ final JournalEntry journalEntry = new JournalEntry();
+ journalEntry.setTransactionIdentifier("chq-cnl-" + UUID.randomUUID().toString());
+ journalEntry.setTransactionDate(DateConverter.toIsoString(now));
+ journalEntry.setTransactionType("CQRV");
+ journalEntry.setMessage("CQRV");
+ journalEntry.setNote("Cheque canceled, reversed transaction: " + journalEntryToReverse.getTransactionIdentifier() + ".");
+ journalEntry.setClerk(UserContextHolder.checkedGetUser());
+ journalEntry.setDebtors(
+ journalEntryToReverse.getCreditors().stream().map(creditor -> {
+ final Debtor debtor = new Debtor();
+ debtor.setAccountNumber(creditor.getAccountNumber());
+ debtor.setAccountNumber(creditor.getAmount());
+ return debtor;
+ }).collect(Collectors.toSet())
+ );
+ journalEntry.setCreditors(
+ journalEntryToReverse.getDebtors().stream().map(debtor -> {
+ final Creditor creditor = new Creditor();
+ creditor.setAccountNumber(debtor.getAccountNumber());
+ creditor.setAccountNumber(debtor.getAmount());
+ return creditor;
+ }).collect(Collectors.toSet())
+ );
+
+ this.accountingService.processJournalEntry(journalEntry);
+
+ chequeEntity.setState(State.CANCELED.name());
+ chequeEntity.setLastModifiedBy(UserContextHolder.checkedGetUser());
+ chequeEntity.setLastModifiedOn(now);
+ this.chequeRepository.save(chequeEntity);
+ return cancelChequeTransactionCommand.identifier();
+ } else {
+ throw ServiceException.notFound("Cheque {0} not found.", cancelChequeTransactionCommand.identifier());
+ }
+ }
+
+ private boolean onUs(final MICR micr) {
+ return this.organizationService.officeExistsByBranchSortCode(micr.getBranchSortCode())
+ && this.accountingService.accountExists(micr.getAccountNumber());
+ }
+}
diff --git a/service/src/main/java/io/mifos/cheque/service/internal/command/handler/ManagementAggregate.java b/service/src/main/java/io/mifos/cheque/service/internal/command/handler/ManagementAggregate.java
new file mode 100644
index 0000000..99cab0f
--- /dev/null
+++ b/service/src/main/java/io/mifos/cheque/service/internal/command/handler/ManagementAggregate.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright 2017 Kuelap, Inc.
+ *
+ * All Rights Reserved.
+ *
+ * NOTICE: All information contained herein is, and remains
+ * the property of Kuelap, Inc and its suppliers, if any.
+ * The intellectual and technical concepts contained herein
+ * are proprietary to Kuelap, Inc and its suppliers and may
+ * be covered by U.S. and Foreign Patents, patents in process,
+ * and are protected by trade secret or copyright law.
+ * Dissemination of this information or reproduction of this material
+ * is strictly forbidden unless prior written permission is obtained
+ * Kuelap, Inc.
+ */
+package io.mifos.cheque.service.internal.command.handler;
+
+import io.mifos.cheque.api.v1.EventConstants;
+import io.mifos.cheque.service.ServiceConstants;
+import io.mifos.cheque.service.internal.command.MigrationCommand;
+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.ApplicationName;
+import io.mifos.core.mariadb.domain.FlywayFactoryBean;
+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;
+
+@Aggregate
+public class ManagementAggregate {
+
+ private final Logger logger;
+ private final DataSource dataSource;
+ private final FlywayFactoryBean flywayFactoryBean;
+ private final ApplicationName applicationName;
+
+ @Autowired
+ public ManagementAggregate(@Qualifier(ServiceConstants.LOGGER_NAME) final Logger logger,
+ final DataSource dataSource,
+ final FlywayFactoryBean flywayFactoryBean,
+ final ApplicationName applicationName) {
+ super();
+ this.logger = logger;
+ this.dataSource = dataSource;
+ this.flywayFactoryBean = flywayFactoryBean;
+ this.applicationName = applicationName;
+ }
+
+ @Transactional
+ @CommandHandler
+ @EventEmitter(selectorName = EventConstants.SELECTOR_NAME, selectorValue = EventConstants.INITIALIZE)
+ public String process(final MigrationCommand migrationCommand) {
+ this.logger.info("Starting migration for cheques version: {}.", applicationName.getVersionString());
+ this.flywayFactoryBean.create(this.dataSource).migrate();
+
+ this.logger.info("Migration finished.");
+ return this.applicationName.getVersionString();
+ }
+}
diff --git a/service/src/main/java/io/mifos/cheque/service/internal/format/MICRParser.java b/service/src/main/java/io/mifos/cheque/service/internal/format/MICRParser.java
new file mode 100644
index 0000000..46c2af9
--- /dev/null
+++ b/service/src/main/java/io/mifos/cheque/service/internal/format/MICRParser.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright 2017 Kuelap, Inc.
+ *
+ * All Rights Reserved.
+ *
+ * NOTICE: All information contained herein is, and remains
+ * the property of Kuelap, Inc and its suppliers, if any.
+ * The intellectual and technical concepts contained herein
+ * are proprietary to Kuelap, Inc and its suppliers and may
+ * be covered by U.S. and Foreign Patents, patents in process,
+ * and are protected by trade secret or copyright law.
+ * Dissemination of this information or reproduction of this material
+ * is strictly forbidden unless prior written permission is obtained
+ * Kuelap, Inc.
+ */
+package io.mifos.cheque.service.internal.format;
+
+import io.mifos.cheque.api.v1.domain.MICR;
+
+public class MICRParser {
+
+ private static final String DELIMITER = "~";
+
+ private MICRParser() {
+ super();
+ }
+
+ public static String toIdentifier(final MICR micr) {
+ if (micr == null
+ || micr.getChequeNumber() == null || micr.getChequeNumber().isEmpty()
+ || micr.getBranchSortCode() == null || micr.getBranchSortCode().isEmpty()
+ || micr.getAccountNumber() == null || micr.getAccountNumber().isEmpty()) {
+ throw new IllegalArgumentException("MICR must be given and all values need to be set.");
+ }
+
+ return micr.getChequeNumber()
+ + MICRParser.DELIMITER
+ + micr.getBranchSortCode()
+ + MICRParser.DELIMITER
+ + micr.getAccountNumber();
+ }
+
+ public static MICR fromIdentifier(final String identifier) {
+ if (identifier == null || identifier.isEmpty()) {
+ throw new IllegalArgumentException("Identifier must be given.");
+ }
+
+ final String[] micrParts = identifier.split(MICRParser.DELIMITER);
+
+ if (micrParts.length != 3) {
+ throw new IllegalArgumentException("Identifier must contain 3 parts delimited by " + MICRParser.DELIMITER + ".");
+ }
+
+ final MICR micr = new MICR();
+ micr.setChequeNumber(micrParts[0]);
+ micr.setBranchSortCode(micrParts[1]);
+ micr.setAccountNumber(micrParts[2]);
+ return micr;
+ }
+}
diff --git a/service/src/main/java/io/mifos/cheque/service/internal/mapper/ChequeMapper.java b/service/src/main/java/io/mifos/cheque/service/internal/mapper/ChequeMapper.java
new file mode 100644
index 0000000..20a24ad
--- /dev/null
+++ b/service/src/main/java/io/mifos/cheque/service/internal/mapper/ChequeMapper.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright 2017 Kuelap, Inc.
+ *
+ * All Rights Reserved.
+ *
+ * NOTICE: All information contained herein is, and remains
+ * the property of Kuelap, Inc and its suppliers, if any.
+ * The intellectual and technical concepts contained herein
+ * are proprietary to Kuelap, Inc and its suppliers and may
+ * be covered by U.S. and Foreign Patents, patents in process,
+ * and are protected by trade secret or copyright law.
+ * Dissemination of this information or reproduction of this material
+ * is strictly forbidden unless prior written permission is obtained
+ * Kuelap, Inc.
+ */
+package io.mifos.cheque.service.internal.mapper;
+
+import io.mifos.cheque.api.v1.domain.Cheque;
+import io.mifos.cheque.api.v1.domain.MICR;
+import io.mifos.cheque.service.internal.repository.ChequeEntity;
+import io.mifos.core.api.util.UserContextHolder;
+import io.mifos.core.lang.DateConverter;
+
+import java.sql.Date;
+import java.time.Clock;
+import java.time.LocalDateTime;
+
+public class ChequeMapper {
+
+ private ChequeMapper() {
+ super();
+ }
+
+ public static ChequeEntity map(final Cheque cheque) {
+ final ChequeEntity chequeEntity = new ChequeEntity();
+ chequeEntity.setChequeNumber(cheque.getMicr().getChequeNumber());
+ chequeEntity.setBranchSortCode(cheque.getMicr().getBranchSortCode());
+ chequeEntity.setAccountNumber(cheque.getMicr().getAccountNumber());
+ chequeEntity.setDrawee(cheque.getDrawee());
+ chequeEntity.setDrawer(cheque.getDrawer());
+ chequeEntity.setPayee(cheque.getPayee());
+ chequeEntity.setAmount(Double.valueOf(cheque.getAmount()));
+ final Date dateIssued = Date.valueOf(DateConverter.dateFromIsoString(cheque.getDateIssued()));
+ chequeEntity.setDateIssued(dateIssued);
+ chequeEntity.setOpenCheque(cheque.isOpenCheque());
+ chequeEntity.setJournalEntryIdentifier(cheque.getJournalEntryIdentifier());
+ chequeEntity.setCreatedBy(UserContextHolder.checkedGetUser());
+ chequeEntity.setCreatedOn(LocalDateTime.now(Clock.systemUTC()));
+ return chequeEntity;
+ }
+
+ public static Cheque map(final ChequeEntity chequeEntity) {
+ final MICR micr = new MICR();
+ micr.setChequeNumber(chequeEntity.getChequeNumber());
+ micr.setBranchSortCode(chequeEntity.getBranchSortCode());
+ micr.setAccountNumber(chequeEntity.getAccountNumber());
+
+ final Cheque cheque = new Cheque();
+ cheque.setMicr(micr);
+ cheque.setDrawee(chequeEntity.getDrawee());
+ cheque.setDrawer(chequeEntity.getDrawer());
+ cheque.setPayee(chequeEntity.getPayee());
+ cheque.setAmount(chequeEntity.getAmount().toString());
+ cheque.setDateIssued(DateConverter.toIsoString(chequeEntity.getDateIssued().toLocalDate()));
+ cheque.setOpenCheque(chequeEntity.getOpenCheque());
+ cheque.setState(chequeEntity.getState());
+ cheque.setJournalEntryIdentifier(chequeEntity.getJournalEntryIdentifier());
+ return cheque;
+ }
+}
diff --git a/service/src/main/java/io/mifos/cheque/service/internal/repository/ChequeEntity.java b/service/src/main/java/io/mifos/cheque/service/internal/repository/ChequeEntity.java
new file mode 100644
index 0000000..91d4545
--- /dev/null
+++ b/service/src/main/java/io/mifos/cheque/service/internal/repository/ChequeEntity.java
@@ -0,0 +1,202 @@
+/*
+ * Copyright 2017 Kuelap, Inc.
+ *
+ * All Rights Reserved.
+ *
+ * NOTICE: All information contained herein is, and remains
+ * the property of Kuelap, Inc and its suppliers, if any.
+ * The intellectual and technical concepts contained herein
+ * are proprietary to Kuelap, Inc and its suppliers and may
+ * be covered by U.S. and Foreign Patents, patents in process,
+ * and are protected by trade secret or copyright law.
+ * Dissemination of this information or reproduction of this material
+ * is strictly forbidden unless prior written permission is obtained
+ * Kuelap, Inc.
+ */
+package io.mifos.cheque.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.sql.Date;
+import java.time.LocalDateTime;
+
+@Entity
+@Table(name = "sopdet_cheques")
+public class ChequeEntity {
+
+ @Id
+ @GeneratedValue(strategy = GenerationType.IDENTITY)
+ @Column(name = "id")
+ private Long id;
+ @Column(name = "cheque_number", nullable = false, length = 8)
+ private String chequeNumber;
+ @Column(name = "branch_sort_code", nullable = false, length = 11)
+ private String branchSortCode;
+ @Column(name = "account_number", nullable = false, length = 34)
+ private String accountNumber;
+ @Column(name = "drawee", nullable = false, length = 2048)
+ private String drawee;
+ @Column(name = "drawer", nullable = false, length = 256)
+ private String drawer;
+ @Column(name = "payee", nullable = false, length = 256)
+ private String payee;
+ @Column(name = "amount", nullable = false)
+ private Double amount;
+ @Column(name = "date_issued", nullable = false)
+ private Date dateIssued;
+ @Column(name = "open_cheque", nullable = true)
+ private Boolean openCheque;
+ @Column(name = "state", nullable = false)
+ private String state;
+ @Column(name = "journal_entry_identifier", nullable = false, length = 2200)
+ private String journalEntryIdentifier;
+ @Column(name = "created_by", nullable = false)
+ private String createdBy;
+ @Column(name = "created_on", nullable = false)
+ @Convert(converter = LocalDateTimeConverter.class)
+ private LocalDateTime createdOn;
+ @Column(name = "last_modified_by", nullable = true)
+ private String lastModifiedBy;
+ @Column(name = "last_modified_on")
+ @Convert(converter = LocalDateTimeConverter.class)
+ private LocalDateTime lastModifiedOn;
+
+ public ChequeEntity() {
+ super();
+ }
+
+ public Long getId() {
+ return this.id;
+ }
+
+ public void setId(final Long id) {
+ this.id = id;
+ }
+
+ public String getChequeNumber() {
+ return this.chequeNumber;
+ }
+
+ public void setChequeNumber(final String chequeNumber) {
+ this.chequeNumber = chequeNumber;
+ }
+
+ public String getBranchSortCode() {
+ return this.branchSortCode;
+ }
+
+ public void setBranchSortCode(final String branchSortCode) {
+ this.branchSortCode = branchSortCode;
+ }
+
+ public String getAccountNumber() {
+ return this.accountNumber;
+ }
+
+ public void setAccountNumber(final String accountNumber) {
+ this.accountNumber = accountNumber;
+ }
+
+ public String getDrawee() {
+ return this.drawee;
+ }
+
+ public void setDrawee(final String drawee) {
+ this.drawee = drawee;
+ }
+
+ public String getDrawer() {
+ return this.drawer;
+ }
+
+ public void setDrawer(final String drawer) {
+ this.drawer = drawer;
+ }
+
+ public String getPayee() {
+ return this.payee;
+ }
+
+ public void setPayee(final String payee) {
+ this.payee = payee;
+ }
+
+ public Double getAmount() {
+ return this.amount;
+ }
+
+ public void setAmount(final Double amount) {
+ this.amount = amount;
+ }
+
+ public Date getDateIssued() {
+ return this.dateIssued;
+ }
+
+ public void setDateIssued(final Date dateIssued) {
+ this.dateIssued = dateIssued;
+ }
+
+ public Boolean getOpenCheque() {
+ return this.openCheque;
+ }
+
+ public void setOpenCheque(final Boolean openCheque) {
+ this.openCheque = openCheque;
+ }
+
+ public String getState() {
+ return this.state;
+ }
+
+ public void setState(final String state) {
+ this.state = state;
+ }
+
+ public String getJournalEntryIdentifier() {
+ return this.journalEntryIdentifier;
+ }
+
+ public void setJournalEntryIdentifier(final String journalEntryIdentifier) {
+ this.journalEntryIdentifier = journalEntryIdentifier;
+ }
+
+ public String getCreatedBy() {
+ return this.createdBy;
+ }
+
+ public void setCreatedBy(final String createdBy) {
+ this.createdBy = createdBy;
+ }
+
+ public LocalDateTime getCreatedOn() {
+ return this.createdOn;
+ }
+
+ public void setCreatedOn(final LocalDateTime createdOn) {
+ this.createdOn = createdOn;
+ }
+
+ public String getLastModifiedBy() {
+ return this.lastModifiedBy;
+ }
+
+ public void setLastModifiedBy(final String lastModifiedBy) {
+ this.lastModifiedBy = lastModifiedBy;
+ }
+
+ public LocalDateTime getLastModifiedOn() {
+ return this.lastModifiedOn;
+ }
+
+ public void setLastModifiedOn(final LocalDateTime lastModifiedOn) {
+ this.lastModifiedOn = lastModifiedOn;
+ }
+}
diff --git a/service/src/main/java/io/mifos/cheque/service/internal/repository/ChequeRepository.java b/service/src/main/java/io/mifos/cheque/service/internal/repository/ChequeRepository.java
new file mode 100644
index 0000000..d6e574d
--- /dev/null
+++ b/service/src/main/java/io/mifos/cheque/service/internal/repository/ChequeRepository.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2017 Kuelap, Inc.
+ *
+ * All Rights Reserved.
+ *
+ * NOTICE: All information contained herein is, and remains
+ * the property of Kuelap, Inc and its suppliers, if any.
+ * The intellectual and technical concepts contained herein
+ * are proprietary to Kuelap, Inc and its suppliers and may
+ * be covered by U.S. and Foreign Patents, patents in process,
+ * and are protected by trade secret or copyright law.
+ * Dissemination of this information or reproduction of this material
+ * is strictly forbidden unless prior written permission is obtained
+ * Kuelap, Inc.
+ */
+package io.mifos.cheque.service.internal.repository;
+
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
+import org.springframework.stereotype.Repository;
+
+import java.util.Optional;
+
+@Repository
+public interface ChequeRepository extends JpaRepository<ChequeEntity, Long>, JpaSpecificationExecutor<ChequeEntity> {
+
+ Optional<ChequeEntity> findByChequeNumberAndBranchSortCodeAndAccountNumber(final String chequeNumber,
+ final String branchSortCode,
+ final String accountNumber);
+}
diff --git a/service/src/main/java/io/mifos/cheque/service/internal/repository/IssuedChequeEntity.java b/service/src/main/java/io/mifos/cheque/service/internal/repository/IssuedChequeEntity.java
new file mode 100644
index 0000000..8a1d463
--- /dev/null
+++ b/service/src/main/java/io/mifos/cheque/service/internal/repository/IssuedChequeEntity.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright 2017 Kuelap, Inc.
+ *
+ * All Rights Reserved.
+ *
+ * NOTICE: All information contained herein is, and remains
+ * the property of Kuelap, Inc and its suppliers, if any.
+ * The intellectual and technical concepts contained herein
+ * are proprietary to Kuelap, Inc and its suppliers and may
+ * be covered by U.S. and Foreign Patents, patents in process,
+ * and are protected by trade secret or copyright law.
+ * Dissemination of this information or reproduction of this material
+ * is strictly forbidden unless prior written permission is obtained
+ * Kuelap, Inc.
+ */
+package io.mifos.cheque.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 = "sopdet_issued_cheques")
+public class IssuedChequeEntity {
+
+ @Id
+ @GeneratedValue(strategy = GenerationType.IDENTITY)
+ @Column(name = "id", nullable = false)
+ private Long id;
+ @Column(name = "account_identifier", nullable = false, length = 34)
+ private String accountIdentifier;
+ @Column(name = "last_issued_number", nullable = false, length = 8)
+ private Integer lastIssuedNumber;
+ @Column(name = "created_by", nullable = false)
+ private String createdBy;
+ @Column(name = "created_on", nullable = false)
+ @Convert(converter = LocalDateTimeConverter.class)
+ private LocalDateTime createdOn;
+ @Column(name = "last_modified_by", nullable = true)
+ private String lastModifiedBy;
+ @Column(name = "last_modified_on")
+ @Convert(converter = LocalDateTimeConverter.class)
+ private LocalDateTime lastModifiedOn;
+
+ public IssuedChequeEntity() {
+ super();
+ }
+
+ public Long getId() {
+ return this.id;
+ }
+
+ public void setId(final Long id) {
+ this.id = id;
+ }
+
+ public String getAccountIdentifier() {
+ return this.accountIdentifier;
+ }
+
+ public void setAccountIdentifier(final String accountIdentifier) {
+ this.accountIdentifier = accountIdentifier;
+ }
+
+ public Integer getLastIssuedNumber() {
+ return this.lastIssuedNumber;
+ }
+
+ public void setLastIssuedNumber(final Integer lastIssuedNumber) {
+ this.lastIssuedNumber = lastIssuedNumber;
+ }
+
+ public String getCreatedBy() {
+ return this.createdBy;
+ }
+
+ public void setCreatedBy(final String createdBy) {
+ this.createdBy = createdBy;
+ }
+
+ public LocalDateTime getCreatedOn() {
+ return this.createdOn;
+ }
+
+ public void setCreatedOn(final LocalDateTime createdOn) {
+ this.createdOn = createdOn;
+ }
+
+ public String getLastModifiedBy() {
+ return this.lastModifiedBy;
+ }
+
+ public void setLastModifiedBy(final String lastModifiedBy) {
+ this.lastModifiedBy = lastModifiedBy;
+ }
+
+ public LocalDateTime getLastModifiedOn() {
+ return this.lastModifiedOn;
+ }
+
+ public void setLastModifiedOn(final LocalDateTime lastModifiedOn) {
+ this.lastModifiedOn = lastModifiedOn;
+ }
+}
diff --git a/service/src/main/java/io/mifos/cheque/service/internal/repository/IssuedChequeRepository.java b/service/src/main/java/io/mifos/cheque/service/internal/repository/IssuedChequeRepository.java
new file mode 100644
index 0000000..1e0e349
--- /dev/null
+++ b/service/src/main/java/io/mifos/cheque/service/internal/repository/IssuedChequeRepository.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2017 Kuelap, Inc.
+ *
+ * All Rights Reserved.
+ *
+ * NOTICE: All information contained herein is, and remains
+ * the property of Kuelap, Inc and its suppliers, if any.
+ * The intellectual and technical concepts contained herein
+ * are proprietary to Kuelap, Inc and its suppliers and may
+ * be covered by U.S. and Foreign Patents, patents in process,
+ * and are protected by trade secret or copyright law.
+ * Dissemination of this information or reproduction of this material
+ * is strictly forbidden unless prior written permission is obtained
+ * Kuelap, Inc.
+ */
+package io.mifos.cheque.service.internal.repository;
+
+import org.springframework.data.jpa.repository.JpaRepository;
+
+import java.util.Optional;
+
+public interface IssuedChequeRepository extends JpaRepository<IssuedChequeEntity, Long> {
+
+ Optional<IssuedChequeEntity> findByAccountIdentifier(final String accountIdentifier);
+}
diff --git a/service/src/main/java/io/mifos/cheque/service/internal/repository/specification/ChequeSpecification.java b/service/src/main/java/io/mifos/cheque/service/internal/repository/specification/ChequeSpecification.java
new file mode 100644
index 0000000..90d111e
--- /dev/null
+++ b/service/src/main/java/io/mifos/cheque/service/internal/repository/specification/ChequeSpecification.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2017 Kuelap, Inc.
+ *
+ * All Rights Reserved.
+ *
+ * NOTICE: All information contained herein is, and remains
+ * the property of Kuelap, Inc and its suppliers, if any.
+ * The intellectual and technical concepts contained herein
+ * are proprietary to Kuelap, Inc and its suppliers and may
+ * be covered by U.S. and Foreign Patents, patents in process,
+ * and are protected by trade secret or copyright law.
+ * Dissemination of this information or reproduction of this material
+ * is strictly forbidden unless prior written permission is obtained
+ * Kuelap, Inc.
+ */
+package io.mifos.cheque.service.internal.repository.specification;
+
+import io.mifos.cheque.api.v1.domain.State;
+import io.mifos.cheque.service.internal.repository.ChequeEntity;
+import org.springframework.data.jpa.domain.Specification;
+
+import javax.persistence.criteria.Predicate;
+import java.util.ArrayList;
+
+public class ChequeSpecification {
+
+ public static Specification<ChequeEntity> createListSpecification(
+ final String state, final String account) {
+
+ return (root, query, cb) -> {
+
+ final ArrayList<Predicate> predicates = new ArrayList<>();
+
+ if (!state.equalsIgnoreCase("ALL")) {
+ predicates.add(cb.equal(root.get("state"), State.valueOf(state.toUpperCase()).name()));
+ }
+
+ if (account != null && ! account.isEmpty()) {
+ predicates.add(cb.equal(root.get("accountNumber"), account));
+ }
+
+ return cb.and(predicates.toArray(new Predicate[predicates.size()]));
+ };
+ }
+}
diff --git a/service/src/main/java/io/mifos/cheque/service/internal/service/ChequeService.java b/service/src/main/java/io/mifos/cheque/service/internal/service/ChequeService.java
new file mode 100644
index 0000000..75c3269
--- /dev/null
+++ b/service/src/main/java/io/mifos/cheque/service/internal/service/ChequeService.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright 2017 Kuelap, Inc.
+ *
+ * All Rights Reserved.
+ *
+ * NOTICE: All information contained herein is, and remains
+ * the property of Kuelap, Inc and its suppliers, if any.
+ * The intellectual and technical concepts contained herein
+ * are proprietary to Kuelap, Inc and its suppliers and may
+ * be covered by U.S. and Foreign Patents, patents in process,
+ * and are protected by trade secret or copyright law.
+ * Dissemination of this information or reproduction of this material
+ * is strictly forbidden unless prior written permission is obtained
+ * Kuelap, Inc.
+ */
+package io.mifos.cheque.service.internal.service;
+
+import io.mifos.cheque.api.v1.domain.Cheque;
+import io.mifos.cheque.api.v1.domain.MICR;
+import io.mifos.cheque.service.ServiceConstants;
+import io.mifos.cheque.service.internal.mapper.ChequeMapper;
+import io.mifos.cheque.service.internal.repository.ChequeRepository;
+import io.mifos.cheque.service.internal.repository.specification.ChequeSpecification;
+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 ChequeService {
+
+ private final Logger logger;
+ private final ChequeRepository chequeRepository;
+
+ @Autowired
+ public ChequeService(@Qualifier(ServiceConstants.LOGGER_NAME) final Logger logger,
+ final ChequeRepository chequeRepository) {
+ super();
+ this.logger = logger;
+ this.chequeRepository = chequeRepository;
+ }
+
+ public boolean chequeExists(final Cheque cheque) {
+ final MICR micr = cheque.getMicr();
+ return this.chequeRepository.findByChequeNumberAndBranchSortCodeAndAccountNumber(
+ micr.getChequeNumber(), micr.getBranchSortCode(), micr.getAccountNumber()
+ ).isPresent();
+ }
+
+ public List<Cheque> fetchCheques(final String state, final String accountIdentifier) {
+ return this.chequeRepository.findAll(ChequeSpecification.createListSpecification(state, accountIdentifier))
+ .stream()
+ .map(ChequeMapper::map)
+ .collect(Collectors.toList());
+ }
+
+ public Optional<Cheque> findBy(final MICR micr) {
+ return
+ this.chequeRepository.findByChequeNumberAndBranchSortCodeAndAccountNumber(
+ micr.getChequeNumber(), micr.getBranchSortCode(), micr.getAccountNumber())
+ .map(ChequeMapper::map);
+ }
+}
diff --git a/service/src/main/java/io/mifos/cheque/service/internal/service/helper/AccountingService.java b/service/src/main/java/io/mifos/cheque/service/internal/service/helper/AccountingService.java
new file mode 100644
index 0000000..238e3c4
--- /dev/null
+++ b/service/src/main/java/io/mifos/cheque/service/internal/service/helper/AccountingService.java
@@ -0,0 +1,110 @@
+/*
+ * Copyright 2017 Kuelap, Inc.
+ *
+ * All Rights Reserved.
+ *
+ * NOTICE: All information contained herein is, and remains
+ * the property of Kuelap, Inc and its suppliers, if any.
+ * The intellectual and technical concepts contained herein
+ * are proprietary to Kuelap, Inc and its suppliers and may
+ * be covered by U.S. and Foreign Patents, patents in process,
+ * and are protected by trade secret or copyright law.
+ * Dissemination of this information or reproduction of this material
+ * is strictly forbidden unless prior written permission is obtained
+ * Kuelap, Inc.
+ */
+package io.mifos.cheque.service.internal.service.helper;
+
+import com.google.common.collect.Sets;
+import io.mifos.accounting.api.v1.client.AccountNotFoundException;
+import io.mifos.accounting.api.v1.client.LedgerManager;
+import io.mifos.accounting.api.v1.domain.Account;
+import io.mifos.accounting.api.v1.domain.Creditor;
+import io.mifos.accounting.api.v1.domain.Debtor;
+import io.mifos.accounting.api.v1.domain.JournalEntry;
+import io.mifos.cheque.service.ServiceConstants;
+import io.mifos.core.api.util.UserContextHolder;
+import io.mifos.core.lang.DateConverter;
+import io.mifos.core.lang.ServiceException;
+import io.mifos.deposit.api.v1.definition.domain.Charge;
+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.time.Clock;
+import java.time.LocalDateTime;
+import java.util.List;
+import java.util.UUID;
+import java.util.stream.Collectors;
+
+@Service
+public class AccountingService {
+
+ private final Logger logger;
+ private final LedgerManager ledgerManager;
+
+ @Autowired
+ public AccountingService(@Qualifier(ServiceConstants.LOGGER_NAME) final Logger logger,
+ final LedgerManager ledgerManager) {
+ super();
+ this.logger = logger;
+ this.ledgerManager = ledgerManager;
+ }
+
+ public void bookCharges(final String sourceAccount, final List<Charge> charges) {
+
+ final Double totalCharges = charges.stream().mapToDouble(Charge::getAmount).sum();
+ if (totalCharges == 0.00D) {
+ return;
+ }
+
+ final Account account = this.ledgerManager.findAccount(sourceAccount);
+ if (account.getBalance() < totalCharges) {
+ throw ServiceException.conflict("Insufficient account balance.");
+ }
+
+ final JournalEntry journalEntry = new JournalEntry();
+ journalEntry.setTransactionIdentifier("chq-iss-" + UUID.randomUUID().toString());
+ journalEntry.setTransactionDate(DateConverter.toIsoString(LocalDateTime.now(Clock.systemUTC())));
+ journalEntry.setTransactionType(ServiceConstants.TX_ISSUE_CHEQUES);
+ journalEntry.setMessage(ServiceConstants.TX_ISSUE_CHEQUES);
+ journalEntry.setClerk(UserContextHolder.checkedGetUser());
+
+ final Debtor debtor = new Debtor();
+ debtor.setAccountNumber(sourceAccount);
+ debtor.setAmount(totalCharges.toString());
+ journalEntry.setDebtors(Sets.newHashSet(debtor));
+
+ journalEntry.setCreditors(
+ charges
+ .stream()
+ .map(charge -> {
+ final Creditor creditor = new Creditor();
+ creditor.setAccountNumber(charge.getIncomeAccountIdentifier());
+ creditor.setAmount(charge.getAmount().toString());
+ return creditor;
+ })
+ .collect(Collectors.toSet())
+ );
+
+ this.ledgerManager.createJournalEntry(journalEntry);
+ }
+
+ public boolean accountExists(final String accountIdentifier) {
+ try {
+ this.ledgerManager.findAccount(accountIdentifier);
+ return true;
+ } catch (final AccountNotFoundException anfex) {
+ return false;
+ }
+ }
+
+ public JournalEntry findJournalEntry(final String identifier) {
+ return this.ledgerManager.findJournalEntry(identifier);
+ }
+
+ public void processJournalEntry(final JournalEntry journalEntry) {
+ this.ledgerManager.createJournalEntry(journalEntry);
+ }
+}
diff --git a/service/src/main/java/io/mifos/cheque/service/internal/service/helper/DepositService.java b/service/src/main/java/io/mifos/cheque/service/internal/service/helper/DepositService.java
new file mode 100644
index 0000000..7047bf8
--- /dev/null
+++ b/service/src/main/java/io/mifos/cheque/service/internal/service/helper/DepositService.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright 2017 Kuelap, Inc.
+ *
+ * All Rights Reserved.
+ *
+ * NOTICE: All information contained herein is, and remains
+ * the property of Kuelap, Inc and its suppliers, if any.
+ * The intellectual and technical concepts contained herein
+ * are proprietary to Kuelap, Inc and its suppliers and may
+ * be covered by U.S. and Foreign Patents, patents in process,
+ * and are protected by trade secret or copyright law.
+ * Dissemination of this information or reproduction of this material
+ * is strictly forbidden unless prior written permission is obtained
+ * Kuelap, Inc.
+ */
+package io.mifos.cheque.service.internal.service.helper;
+
+import io.mifos.cheque.service.ServiceConstants;
+import io.mifos.deposit.api.v1.client.DepositAccountManager;
+import io.mifos.deposit.api.v1.definition.domain.Action;
+import io.mifos.deposit.api.v1.definition.domain.Charge;
+import io.mifos.deposit.api.v1.definition.domain.ProductDefinition;
+import io.mifos.deposit.api.v1.instance.domain.ProductInstance;
+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.stream.Collectors;
+
+@Service
+public class DepositService {
+
+ private final Logger logger;
+ private final DepositAccountManager depositAccountManager;
+
+ @Autowired
+ public DepositService(@Qualifier(ServiceConstants.LOGGER_NAME) final Logger logger,
+ final DepositAccountManager depositAccountManager) {
+ super();
+ this.logger = logger;
+ this.depositAccountManager = depositAccountManager;
+ }
+
+ public List<Charge> getIssueChequeCharges(final String accountIdentifier) {
+
+ final List<Action> actions = this.depositAccountManager.fetchActions();
+
+ final List<String> issueChequeActions =
+ actions
+ .stream()
+ .filter(action -> action.getTransactionType().equals(ServiceConstants.TX_ISSUE_CHEQUES))
+ .map(Action::getIdentifier)
+ .collect(Collectors.toList());
+
+ final ProductInstance productInstance =
+ this.depositAccountManager.findProductInstance(accountIdentifier);
+
+ final ProductDefinition productDefinition =
+ this.depositAccountManager.findProductDefinition(productInstance.getProductIdentifier());
+
+ return productDefinition.getCharges()
+ .stream()
+ .filter(charge -> issueChequeActions.contains(charge.getActionIdentifier()))
+ .collect(Collectors.toList());
+ }
+
+ public String getCashAccountForProduct(final String accountIdentifier) {
+ final ProductInstance productInstance =
+ this.depositAccountManager.findProductInstance(accountIdentifier);
+
+ final ProductDefinition productDefinition =
+ this.depositAccountManager.findProductDefinition(productInstance.getProductIdentifier());
+
+ return productDefinition.getCashAccountIdentifier();
+ }
+}
diff --git a/service/src/main/java/io/mifos/cheque/service/internal/service/helper/OrganizationService.java b/service/src/main/java/io/mifos/cheque/service/internal/service/helper/OrganizationService.java
new file mode 100644
index 0000000..f8e857a
--- /dev/null
+++ b/service/src/main/java/io/mifos/cheque/service/internal/service/helper/OrganizationService.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2017 Kuelap, Inc.
+ *
+ * All Rights Reserved.
+ *
+ * NOTICE: All information contained herein is, and remains
+ * the property of Kuelap, Inc and its suppliers, if any.
+ * The intellectual and technical concepts contained herein
+ * are proprietary to Kuelap, Inc and its suppliers and may
+ * be covered by U.S. and Foreign Patents, patents in process,
+ * and are protected by trade secret or copyright law.
+ * Dissemination of this information or reproduction of this material
+ * is strictly forbidden unless prior written permission is obtained
+ * Kuelap, Inc.
+ */
+package io.mifos.cheque.service.internal.service.helper;
+
+import io.mifos.cheque.service.ServiceConstants;
+import io.mifos.office.api.v1.client.NotFoundException;
+import io.mifos.office.api.v1.client.OrganizationManager;
+import org.slf4j.Logger;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.stereotype.Service;
+
+@Service
+public class OrganizationService {
+
+ private final Logger logger;
+ private final OrganizationManager organizationManager;
+
+ @Autowired
+ public OrganizationService(@Qualifier(ServiceConstants.LOGGER_NAME) final Logger logger,
+ final OrganizationManager organizationManager) {
+ super();
+ this.logger = logger;
+ this.organizationManager = organizationManager;
+ }
+
+ public boolean officeExistsByBranchSortCode(final String branchSortCode) {
+ try {
+ this.organizationManager.findOfficeByIdentifier(branchSortCode);
+ return true;
+ } catch (final NotFoundException nfex) {
+ return false;
+ }
+ }
+}
diff --git a/service/src/main/java/io/mifos/cheque/service/rest/ChequeManagementRestController.java b/service/src/main/java/io/mifos/cheque/service/rest/ChequeManagementRestController.java
new file mode 100644
index 0000000..9317c17
--- /dev/null
+++ b/service/src/main/java/io/mifos/cheque/service/rest/ChequeManagementRestController.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright 2017 Kuelap, Inc.
+ *
+ * All Rights Reserved.
+ *
+ * NOTICE: All information contained herein is, and remains
+ * the property of Kuelap, Inc and its suppliers, if any.
+ * The intellectual and technical concepts contained herein
+ * are proprietary to Kuelap, Inc and its suppliers and may
+ * be covered by U.S. and Foreign Patents, patents in process,
+ * and are protected by trade secret or copyright law.
+ * Dissemination of this information or reproduction of this material
+ * is strictly forbidden unless prior written permission is obtained
+ * Kuelap, Inc.
+ */
+package io.mifos.cheque.service.rest;
+
+import io.mifos.anubis.annotation.AcceptedTokenType;
+import io.mifos.anubis.annotation.Permittable;
+import io.mifos.cheque.service.ServiceConstants;
+import io.mifos.cheque.service.internal.command.MigrationCommand;
+import io.mifos.core.command.gateway.CommandGateway;
+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("/")
+public class ChequeManagementRestController {
+
+ private final Logger logger;
+ private final CommandGateway commandGateway;
+
+ @Autowired
+ public ChequeManagementRestController(@Qualifier(ServiceConstants.LOGGER_NAME) final Logger logger,
+ final CommandGateway commandGateway) {
+ super();
+ this.logger = logger;
+ this.commandGateway = commandGateway;
+ }
+
+ @Permittable(value = AcceptedTokenType.SYSTEM)
+ @RequestMapping(
+ value = "/initialize",
+ method = RequestMethod.POST,
+ consumes = MediaType.ALL_VALUE,
+ produces = MediaType.APPLICATION_JSON_VALUE
+ )
+ public
+ @ResponseBody
+ ResponseEntity<Void> initialize() {
+ this.commandGateway.process(new MigrationCommand());
+ return ResponseEntity.accepted().build();
+ }
+}
diff --git a/service/src/main/java/io/mifos/cheque/service/rest/ChequeRestController.java b/service/src/main/java/io/mifos/cheque/service/rest/ChequeRestController.java
new file mode 100644
index 0000000..7f9414f
--- /dev/null
+++ b/service/src/main/java/io/mifos/cheque/service/rest/ChequeRestController.java
@@ -0,0 +1,150 @@
+/*
+ * Copyright 2017 Kuelap, Inc.
+ *
+ * All Rights Reserved.
+ *
+ * NOTICE: All information contained herein is, and remains
+ * the property of Kuelap, Inc and its suppliers, if any.
+ * The intellectual and technical concepts contained herein
+ * are proprietary to Kuelap, Inc and its suppliers and may
+ * be covered by U.S. and Foreign Patents, patents in process,
+ * and are protected by trade secret or copyright law.
+ * Dissemination of this information or reproduction of this material
+ * is strictly forbidden unless prior written permission is obtained
+ * Kuelap, Inc.
+ */
+package io.mifos.cheque.service.rest;
+
+import io.mifos.anubis.annotation.AcceptedTokenType;
+import io.mifos.anubis.annotation.Permittable;
+import io.mifos.cheque.api.v1.PermittableGroupIds;
+import io.mifos.cheque.api.v1.client.IssuingCount;
+import io.mifos.cheque.api.v1.domain.Action;
+import io.mifos.cheque.api.v1.domain.Cheque;
+import io.mifos.cheque.api.v1.domain.ChequeProcessingCommand;
+import io.mifos.cheque.service.ServiceConstants;
+import io.mifos.cheque.service.internal.command.ApproveChequeTransactionCommand;
+import io.mifos.cheque.service.internal.command.CancelChequeTransactionCommand;
+import io.mifos.cheque.service.internal.command.IssueChequesCommand;
+import io.mifos.cheque.service.internal.format.MICRParser;
+import io.mifos.cheque.service.internal.service.ChequeService;
+import io.mifos.core.command.domain.CommandCallback;
+import io.mifos.core.command.gateway.CommandGateway;
+import io.mifos.core.lang.ServiceException;
+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.RequestParam;
+import org.springframework.web.bind.annotation.ResponseBody;
+import org.springframework.web.bind.annotation.RestController;
+
+import javax.validation.Valid;
+import java.util.List;
+
+@RestController
+@RequestMapping("/cheques")
+public class ChequeRestController {
+
+ private final Logger logger;
+ private final CommandGateway commandGateway;
+ private final ChequeService chequeService;
+
+ @Autowired
+ public ChequeRestController(@Qualifier(ServiceConstants.LOGGER_NAME) final Logger logger,
+ final CommandGateway commandGateway,
+ final ChequeService chequeService) {
+ super();
+ this.logger = logger;
+ this.commandGateway = commandGateway;
+ this.chequeService = chequeService;
+ }
+
+ @Permittable(value = AcceptedTokenType.TENANT, groupId = PermittableGroupIds.CHEQUE_MANAGEMENT)
+ @RequestMapping(
+ value = "/",
+ method = RequestMethod.POST,
+ produces = {MediaType.APPLICATION_JSON_VALUE},
+ consumes = {MediaType.APPLICATION_JSON_VALUE}
+ )
+ @ResponseBody
+ ResponseEntity<String> issue(@RequestBody @Valid final IssuingCount issuingCount) {
+
+ try {
+ final CommandCallback<String> callback = this.commandGateway.process(
+ new IssueChequesCommand(
+ issuingCount.getAccountIdentifier(), issuingCount.getStart(), issuingCount.getAmount()),
+ String.class
+ );
+
+ return ResponseEntity.ok(callback.get());
+ } catch (final ServiceException ex) {
+ throw ex;
+ } catch (final Exception ex) {
+ throw ServiceException.internalError(ex.getMessage());
+ }
+ }
+
+ @Permittable(value = AcceptedTokenType.TENANT, groupId = PermittableGroupIds.CHEQUE_MANAGEMENT)
+ @RequestMapping(
+ value = "/",
+ method = RequestMethod.GET,
+ produces = {MediaType.APPLICATION_JSON_VALUE},
+ consumes = {MediaType.ALL_VALUE}
+ )
+ @ResponseBody
+ ResponseEntity<List<Cheque>> fetch(
+ @RequestParam(value = "state", required = false, defaultValue = "ALL") final String state,
+ @RequestParam(value = "account", required = false) final String accountIdentifier) {
+ return ResponseEntity.ok(this.chequeService.fetchCheques(state, accountIdentifier));
+ }
+
+ @Permittable(value = AcceptedTokenType.TENANT, groupId = PermittableGroupIds.CHEQUE_MANAGEMENT)
+ @RequestMapping(
+ value = "/{identifier}",
+ method = RequestMethod.GET,
+ produces = {MediaType.APPLICATION_JSON_VALUE},
+ consumes = {MediaType.ALL_VALUE}
+ )
+ @ResponseBody
+ ResponseEntity<Cheque> get(@PathVariable("identifier") final String identifier) {
+ return ResponseEntity.ok(
+ this.chequeService.findBy(MICRParser.fromIdentifier(identifier))
+ .orElseThrow(() -> ServiceException.notFound("Cheque {0} not found.", identifier))
+ );
+ }
+
+ @Permittable(value = AcceptedTokenType.TENANT, groupId = PermittableGroupIds.CHEQUE_MANAGEMENT)
+ @RequestMapping(
+ value = "/{identifier}/commands",
+ method = RequestMethod.POST,
+ produces = {MediaType.APPLICATION_JSON_VALUE},
+ consumes = {MediaType.APPLICATION_JSON_VALUE}
+ )
+ @ResponseBody
+ ResponseEntity<Void> process(@PathVariable("identifier") final String identifier,
+ @RequestBody @Valid final ChequeProcessingCommand chequeProcessingCommand) {
+
+ if (!this.chequeService.findBy(MICRParser.fromIdentifier(identifier)).isPresent()) {
+ throw ServiceException.notFound("Cheque {0} not found.", identifier);
+ }
+
+ switch (Action.valueOf(chequeProcessingCommand.getAction())) {
+ case APPROVE:
+ this.commandGateway.process(new ApproveChequeTransactionCommand(identifier));
+ break;
+ case CANCEL:
+ this.commandGateway.process(new CancelChequeTransactionCommand(identifier));
+ break;
+ default:
+ throw ServiceException.badRequest("Unknown cheque command {0}.", chequeProcessingCommand.getAction());
+ }
+
+ return ResponseEntity.accepted().build();
+ }
+}
diff --git a/service/src/main/java/io/mifos/cheque/service/rest/ChequeTransactionRestController.java b/service/src/main/java/io/mifos/cheque/service/rest/ChequeTransactionRestController.java
new file mode 100644
index 0000000..b07d957
--- /dev/null
+++ b/service/src/main/java/io/mifos/cheque/service/rest/ChequeTransactionRestController.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright 2017 Kuelap, Inc.
+ *
+ * All Rights Reserved.
+ *
+ * NOTICE: All information contained herein is, and remains
+ * the property of Kuelap, Inc and its suppliers, if any.
+ * The intellectual and technical concepts contained herein
+ * are proprietary to Kuelap, Inc and its suppliers and may
+ * be covered by U.S. and Foreign Patents, patents in process,
+ * and are protected by trade secret or copyright law.
+ * Dissemination of this information or reproduction of this material
+ * is strictly forbidden unless prior written permission is obtained
+ * Kuelap, Inc.
+ */
+package io.mifos.cheque.service.rest;
+
+import io.mifos.anubis.annotation.AcceptedTokenType;
+import io.mifos.anubis.annotation.Permittable;
+import io.mifos.cheque.api.v1.PermittableGroupIds;
+import io.mifos.cheque.api.v1.domain.Cheque;
+import io.mifos.cheque.api.v1.domain.ChequeTransaction;
+import io.mifos.cheque.service.ServiceConstants;
+import io.mifos.cheque.service.internal.command.ChequeTransactionCommand;
+import io.mifos.cheque.service.internal.format.MICRParser;
+import io.mifos.cheque.service.internal.service.ChequeService;
+import io.mifos.core.command.gateway.CommandGateway;
+import io.mifos.core.lang.ServiceException;
+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.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;
+
+@RestController
+@RequestMapping("/transactions")
+public class ChequeTransactionRestController {
+
+ private final Logger logger;
+ private final CommandGateway commandGateway;
+ private final ChequeService chequeService;
+
+ @Autowired
+ public ChequeTransactionRestController(@Qualifier(ServiceConstants.LOGGER_NAME) final Logger logger,
+ final CommandGateway commandGateway,
+ final ChequeService chequeService) {
+ super();
+ this.logger = logger;
+ this.commandGateway = commandGateway;
+ this.chequeService = chequeService;
+ }
+
+ @Permittable(value = AcceptedTokenType.TENANT, groupId = PermittableGroupIds.CHEQUE_TRANSACTION)
+ @RequestMapping(
+ value = "/",
+ method = RequestMethod.POST,
+ produces = {MediaType.APPLICATION_JSON_VALUE},
+ consumes = {MediaType.APPLICATION_JSON_VALUE}
+ )
+ @ResponseBody
+ ResponseEntity<Void> process(@RequestBody @Valid final ChequeTransaction chequeTransaction) {
+
+ final Cheque cheque = chequeTransaction.getCheque();
+
+ if (this.chequeService.chequeExists(chequeTransaction.getCheque())) {
+ throw ServiceException.conflict("Cheque {0} already used.",
+ MICRParser.toIdentifier(chequeTransaction.getCheque().getMicr()));
+ }
+
+ this.commandGateway.process(new ChequeTransactionCommand(cheque, chequeTransaction.getCreditorAccountNumber()));
+
+ 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..c73c1b8
--- /dev/null
+++ b/service/src/main/resources/application.yml
@@ -0,0 +1,66 @@
+#
+# Copyright 2017 Kuelap, Inc.
+#
+# All Rights Reserved.
+#
+# NOTICE: All information contained herein is, and remains
+# the property of Kuelap, Inc and its suppliers, if any.
+# The intellectual and technical concepts contained herein
+# are proprietary to Kuelap, Inc and its suppliers and may
+# be covered by U.S. and Foreign Patents, patents in process,
+# and are protected by trade secret or copyright law.
+# Dissemination of this information or reproduction of this material
+# is strictly forbidden unless prior written permission is obtained
+# Kuelap, Inc.
+#
+
+spring:
+ cloud:
+ discovery:
+ enabled: false
+ config:
+ enabled: false
+
+eureka:
+ client:
+ serviceUrl:
+ defaultZone: http://localhost:8761/eureka/
+
+server:
+ port: 8081
+ contextPath: /cheques/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..7ab35c9
--- /dev/null
+++ b/service/src/main/resources/bootstrap.yml
@@ -0,0 +1,19 @@
+#
+# Copyright 2017 Kuelap, Inc.
+#
+# All Rights Reserved.
+#
+# NOTICE: All information contained herein is, and remains
+# the property of Kuelap, Inc and its suppliers, if any.
+# The intellectual and technical concepts contained herein
+# are proprietary to Kuelap, Inc and its suppliers and may
+# be covered by U.S. and Foreign Patents, patents in process,
+# and are protected by trade secret or copyright law.
+# Dissemination of this information or reproduction of this material
+# is strictly forbidden unless prior written permission is obtained
+# Kuelap, Inc.
+#
+
+spring:
+ application:
+ name: cheques-v1
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..23bdb44
--- /dev/null
+++ b/service/src/main/resources/db/migrations/mariadb/V1__initial_setup.sql
@@ -0,0 +1,48 @@
+--
+-- Copyright 2017 Kuelap, Inc.
+--
+-- All Rights Reserved.
+--
+-- NOTICE: All information contained herein is, and remains
+-- the property of Kuelap, Inc and its suppliers, if any.
+-- The intellectual and technical concepts contained herein
+-- are proprietary to Kuelap, Inc and its suppliers and may
+-- be covered by U.S. and Foreign Patents, patents in process,
+-- and are protected by trade secret or copyright law.
+-- Dissemination of this information or reproduction of this material
+-- is strictly forbidden unless prior written permission is obtained
+-- Kuelap, Inc.
+--
+
+CREATE TABLE sopdet_issued_cheques (
+ id BIGINT NOT NULL AUTO_INCREMENT,
+ account_identifier VARCHAR(34) NOT NULL,
+ last_issued_number NUMERIC(8) 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 sopdet_issued_cheques_pk PRIMARY KEY (id),
+ CONSTRAINT sopdet_issued_cheques_uq UNIQUE (account_identifier)
+);
+
+CREATE TABLE sopdet_cheques (
+ id BIGINT NOT NULL AUTO_INCREMENT,
+ cheque_number VARCHAR(8) NOT NULL,
+ branch_sort_code VARCHAR(11) NOT NULL,
+ account_number VARCHAR(34) NOT NULL,
+ drawee VARCHAR(2048) NOT NULL,
+ drawer VARCHAR(256) NOT NULL,
+ payee VARCHAR(256) NOT NULL,
+ amount NUMERIC(15,5) NOT NULL,
+ date_issued DATE NOT NULL,
+ open_cheque BOOLEAN NULL,
+ state VARCHAR(32) NOT NULL,
+ journal_entry_identifier VARCHAR(2200) 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 sopdet_cheques_pk PRIMARY KEY (id),
+ CONSTRAINT sopdet_cheques_uq UNIQUE (cheque_number, branch_sort_code, account_number)
+);
diff --git a/service/src/main/resources/logback.xml b/service/src/main/resources/logback.xml
new file mode 100644
index 0000000..e68c93a
--- /dev/null
+++ b/service/src/main/resources/logback.xml
@@ -0,0 +1,55 @@
+<!--
+
+ Copyright 2017 Kuelap, Inc.
+
+ All Rights Reserved.
+
+ NOTICE: All information contained herein is, and remains
+ the property of Kuelap, Inc and its suppliers, if any.
+ The intellectual and technical concepts contained herein
+ are proprietary to Kuelap, Inc and its suppliers and may
+ be covered by U.S. and Foreign Patents, patents in process,
+ and are protected by trade secret or copyright law.
+ Dissemination of this information or reproduction of this material
+ is strictly forbidden unless prior written permission is obtained
+ Kuelap, Inc.
+
+-->
+<configuration>
+ <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
+ <file>logs/cheques.log</file>
+ <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
+ <fileNamePattern>logs/archive/cheques.%d{yyyy-MM-dd}.log</fileNamePattern>
+ <maxHistory>7</maxHistory>
+ <totalSizeCap>2GB</totalSizeCap>
+ </rollingPolicy>
+ <encoder>
+ <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
+ </encoder>
+ </appender>
+ <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>
+
+ <logger name="com" level="INFO">
+ <appender-ref ref="STDOUT" />
+ </logger>
+
+ <logger name="org" level="INFO">
+ <appender-ref ref="STDOUT" />
+ </logger>
+
+ <logger name="io" level="INFO">
+ <appender-ref ref="STDOUT" />
+ </logger>
+
+ <logger name="net" level="INFO">
+ <appender-ref ref="STDOUT" />
+ </logger>
+
+ <root level="DEBUG">
+ <appender-ref ref="FILE"/>
+ </root>
+</configuration>
\ No newline at end of file
diff --git a/service/src/test/java/io/mifos/cheque/service/internal/format/TestMICRParser.java b/service/src/test/java/io/mifos/cheque/service/internal/format/TestMICRParser.java
new file mode 100644
index 0000000..c74fa2b
--- /dev/null
+++ b/service/src/test/java/io/mifos/cheque/service/internal/format/TestMICRParser.java
@@ -0,0 +1,125 @@
+/*
+ * Copyright 2017 Kuelap, Inc.
+ *
+ * All Rights Reserved.
+ *
+ * NOTICE: All information contained herein is, and remains
+ * the property of Kuelap, Inc and its suppliers, if any.
+ * The intellectual and technical concepts contained herein
+ * are proprietary to Kuelap, Inc and its suppliers and may
+ * be covered by U.S. and Foreign Patents, patents in process,
+ * and are protected by trade secret or copyright law.
+ * Dissemination of this information or reproduction of this material
+ * is strictly forbidden unless prior written permission is obtained
+ * Kuelap, Inc.
+ */
+package io.mifos.cheque.service.internal.format;
+
+import io.mifos.cheque.api.v1.domain.MICR;
+import org.junit.Assert;
+import org.junit.Test;
+
+public class TestMICRParser {
+
+ @Test
+ public void shouldParseIdentifier() throws Exception {
+ final String identifier = "1357~2468~1470";
+
+ final MICR micr = MICRParser.fromIdentifier(identifier);
+ Assert.assertEquals("1357", micr.getChequeNumber());
+ Assert.assertEquals("2468", micr.getBranchSortCode());
+ Assert.assertEquals("1470", micr.getAccountNumber());
+ }
+
+ @Test
+ public void shouldParseMIRC() throws Exception {
+ final MICR micr = new MICR();
+ micr.setChequeNumber("1357");
+ micr.setBranchSortCode("2468");
+ micr.setAccountNumber("1470");
+
+ final String identifier = MICRParser.toIdentifier(micr);
+ Assert.assertEquals("1357~2468~1470", identifier);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void shouldFailIdentifierNull() throws Exception {
+ MICRParser.fromIdentifier(null);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void shouldFailIdentifierEmpty() throws Exception {
+ MICRParser.fromIdentifier("");
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void shouldFailIdentifierLessParts() throws Exception {
+ MICRParser.fromIdentifier("1357~2468");
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void shouldFailIdentifierManyParts() throws Exception {
+ MICRParser.fromIdentifier("1357~2468~1470~1");
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void shouldFailMICRNull() throws Exception {
+ MICRParser.toIdentifier(null);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void shouldFailChequeNumberNull() throws Exception {
+ final MICR micr = new MICR();
+ micr.setBranchSortCode("2468");
+ micr.setAccountNumber("1470");
+
+ MICRParser.toIdentifier(micr);
+ }
+ @Test(expected = IllegalArgumentException.class)
+ public void shouldFailChequeNumberMissing() throws Exception {
+ final MICR micr = new MICR();
+ micr.setChequeNumber("");
+ micr.setBranchSortCode("2468");
+ micr.setAccountNumber("1470");
+
+ MICRParser.toIdentifier(micr);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void shouldFailBranchSortCodeNull() throws Exception {
+ final MICR micr = new MICR();
+ micr.setChequeNumber("1357");
+ micr.setAccountNumber("1470");
+
+ MICRParser.toIdentifier(micr);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void shouldFailBranchSortCodeMissing() throws Exception {
+ final MICR micr = new MICR();
+ micr.setChequeNumber("1357");
+ micr.setBranchSortCode("");
+ micr.setAccountNumber("1470");
+
+ MICRParser.toIdentifier(micr);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void shouldFailAccountNumberNull() throws Exception {
+ final MICR micr = new MICR();
+ micr.setChequeNumber("1357");
+ micr.setBranchSortCode("2468");
+
+ MICRParser.toIdentifier(micr);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void shouldFailAccountNumberMissing() throws Exception {
+ final MICR micr = new MICR();
+ micr.setChequeNumber("1357");
+ micr.setBranchSortCode("2468");
+ micr.setAccountNumber("");
+
+ MICRParser.toIdentifier(micr);
+ }
+}
diff --git a/settings.gradle b/settings.gradle
new file mode 100644
index 0000000..4f44661
--- /dev/null
+++ b/settings.gradle
@@ -0,0 +1,6 @@
+rootProject.name = 'cheques'
+
+includeBuild 'api'
+includeBuild 'service'
+includeBuild 'component-test'
+
diff --git a/shared.gradle b/shared.gradle
new file mode 100644
index 0000000..53a6aa4
--- /dev/null
+++ b/shared.gradle
@@ -0,0 +1,73 @@
+group 'io.kuelap.cheques'
+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',
+ frameworkoffice : '0.1.0-BUILD-SNAPSHOT',
+ frameworkaccounting: '0.1.0-BUILD-SNAPSHOT',
+ frameworkdeposit : '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'
+
+tasks.withType(JavaCompile) {
+ 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']
+ )
+}
+
+jar {
+ from sourceSets.main.allSource
+ duplicatesStrategy = DuplicatesStrategy.EXCLUDE
+}
+
+license {
+ header rootProject.file('../HEADER')
+ strictCheck true
+ mapping {
+ java = 'SLASHSTAR_STYLE'
+ xml = 'XML_STYLE'
+ yml = 'SCRIPT_STYLE'
+ yaml = 'SCRIPT_STYLE'
+ }
+ ext.year = Calendar.getInstance().get(Calendar.YEAR)
+ ext.company = 'Kuelap, Inc'
+}
\ No newline at end of file