You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@fineract.apache.org by my...@apache.org on 2018/01/22 15:47:01 UTC

[fineract-cn-customer] branch develop updated (2db0052 -> 3962e18)

This is an automated email from the ASF dual-hosted git repository.

myrle pushed a change to branch develop
in repository https://gitbox.apache.org/repos/asf/fineract-cn-customer.git.


    from 2db0052  Introduced a TestSuite so that all the component-tests can be run with one click.
     new 3771232  added process steps to fetch actions based on customer state
     new fb278a5  Merge pull request #12 from mgeiss/develop
     new 1500da3  Merge pull request #13 from myrle-krantz/develop
     new a8fa2be  Added helper function to feign client isCustomerInGoodStanding.
     new 498c48e  Add identification card upload
     new 65d25fe  Merge pull request #14 from crain/develop
     new 6c963dd  Merge branch 'develop' of https://github.com/KuelapInc/customer into develop
     new fd4bf02  Merge pull request #1 from myrlen/develop
     new aaa22f2  added ability to manage payroll distribution per customer
     new 73def97  Merge pull request #2 from markusgeiss/develop
     new 50a054e  added validation to prevent empty commands in task definition
     new fdabad4  Merge pull request #3 from markusgeiss/develop
     new 136016e  replaced delimiter in update task
     new 3ed6029  Merge pull request #4 from markusgeiss/develop
     new f078702  added filtering for task based on command
     new 81c0404  Merge pull request #5 from markusgeiss/develop
     new 5d94ca2  renamed customer endpoit to customers fixed taks execution
     new dd82190  Merge pull request #6 from markusgeiss/develop
     new 2159f03  Fix race conditions in task test - reset predefined tasks to false after test - reset events in recorder to prevent successful assert based on previous events
     new b730e07  Merge pull request #7 from markvanveen/develop
     new 167341d  refactored payroll into own service
     new c717b50  Merge pull request #8 from markusgeiss/develop
     new 38ab00d  added date range validation to expiration date
     new 9f5e721  Merge pull request #9 from markusgeiss/develop
     new 48bfd66  fixed NPE when checking four eye task
     new 5184b15  Merge pull request #10 from markusgeiss/develop
     new 02cda5a  added delete and update for catalogs and fields
     new 9936657  Merge pull request #11 from markusgeiss/develop
     new e86d2a1  using valid identifier for domain objects
     new b560180  Merge pull request #12 from markusgeiss/develop
     new a280d78  Fix issue not updating fields by flushing field value repository after delete Throw conflict instead of bad request when field or catalog is in use Adapted tests to use new exceptions
     new 5abd772  Merge pull request #13 from markvanveen/develop
     new d8b5bf7  Implementing documents for customers.
     new 2071809  Merge pull request #14 from myrlen/develop
     new 2a84c2a  Implementing document descriptions for customers.
     new 683ba37  Adding change document description functionality.
     new 1456f73  Merge pull request #15 from myrlen/develop
     new 647cbf1  Added missing completed check to change document.
     new 41749c5  Added delete document endpoint.
     new aacadcb  Merge pull request #16 from myrlen/develop
     new 5e05d4a  TestCustomer.shouldDeletePortrait was failing intermittently with an InternalServerError resulting from a NullPointerException in the GET endpoint implementation.  The delete transaction was getting committed between the query if the portrait exits and the actually acquisition of the portrait in the rest implementation.  I changed the repository to return an Optional.empty instead of a null if the identifier doesn't correspond to a customer.  This enabled me to replace t [...]
     new b0e57a2  Merge pull request #17 from myrlen/develop
     new be60ba6  added application date for customer
     new 47897f0  Merge pull request #18 from markusgeiss/develop
     new dfad7e2  added time zone 'Z' handling to ease UI usage
     new 3962e18  Merge pull request #19 from markusgeiss/develop

The 46 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 api/build.gradle                                   |   4 +
 .../io/mifos/customer/PermittableGroupIds.java     |   1 +
 .../customer/api/v1/CustomerEventConstants.java    |  20 ++
 ...CompletedDocumentCannotBeChangedException.java} |   5 +-
 .../api/v1/client/CustomerDocumentsManager.java    | 182 ++++++++++++
 .../customer/api/v1/client/CustomerManager.java    |  97 ++++++-
 ...ption.java => DocumentValidationException.java} |   2 +-
 ...eption.java => ScanAlreadyExistsException.java} |   2 +-
 ...onException.java => ScanNotFoundException.java} |   2 +-
 ...Exception.java => ScanValidationException.java} |   2 +-
 .../io/mifos/customer/api/v1/domain/Command.java   |   4 +-
 .../io/mifos/customer/api/v1/domain/Customer.java  |   9 +
 .../customer/api/v1/domain/CustomerDocument.java   | 105 +++++++
 .../customer/api/v1/domain/ExpirationDate.java     |   8 +
 .../api/v1/domain/IdentificationCardScan.java      |  26 +-
 .../mifos/customer/api/v1/domain/ProcessStep.java  |  30 +-
 .../customer/api/v1/domain/TaskDefinition.java     |   2 +
 .../customer/api/v1/events/DocumentEvent.java      |  71 +++++
 .../customer/api/v1/events/DocumentPageEvent.java  |  85 ++++++
 .../io/mifos/customer/api/v1/events/ScanEvent.java |  52 ++++
 .../catalog/api/v1/CatalogEventConstants.java      |   7 +-
 ...tion.java => CatalogAlreadyInUseException.java} |   2 +-
 .../catalog/api/v1/client/CatalogManager.java      |  47 ++++
 ...xception.java => CatalogNotFoundException.java} |   2 +-
 ...eption.java => FieldAlreadyInUseException.java} |   2 +-
 .../customer/catalog/api/v1/domain/Catalog.java    |   3 +-
 .../customer/catalog/api/v1/domain/Field.java      |   3 +-
 .../customer/catalog/api/v1/domain/Value.java      |   5 +-
 .../api/v1/domain/CustomerDocumentTest.java        |  70 +++++
 .../io/mifos/customer/AbstractCustomerTest.java    |  22 ++
 .../main/java/io/mifos/customer/TestCustomer.java  | 208 +++++++-------
 .../main/java/io/mifos/customer/TestDocuments.java | 221 +++++++++++++++
 .../io/mifos/customer/TestIdentificationCards.java | 252 +++++++++++++++++
 .../src/main/java/io/mifos/customer/TestSuite.java |   2 +
 .../java/io/mifos/customer/TestTaskInstance.java   | 115 +++++++-
 .../io/mifos/customer/catalog/TestCatalog.java     | 153 ++++++++++-
 .../catalog/listener/CatalogEventListener.java     |  30 +-
 .../customer/listener/CustomerEventListener.java   |  19 ++
 .../customer/listener/DocumentEventListener.java   | 101 +++++++
 ...nerator.java => CustomerDocumentGenerator.java} |  17 +-
 ...tactDetailGenerator.java => ScanGenerator.java} |  29 +-
 .../resources/{logback.xml => logback-test.xml}    |   0
 ...CatalogCommand.java => ChangeFieldCommand.java} |  20 +-
 .../internal/command/DeleteCatalogCommand.java}    |   8 +-
 ...CatalogCommand.java => DeleteFieldCommand.java} |  20 +-
 .../internal/command/handler/CatalogAggregate.java |  86 +++++-
 .../internal/repository/FieldValueRepository.java  |   3 +
 .../internal/repository/OptionRepository.java}     |   8 +-
 .../service/internal/service/CatalogService.java   |  40 ++-
 .../internal/service/FieldValueValidator.java      |   2 +-
 .../rest/controller/CatalogRestController.java     |  67 ++++-
 ...omerCommand.java => ChangeDocumentCommand.java} |  31 ++-
 ...erCommand.java => CompleteDocumentCommand.java} |  29 +-
 ...omerCommand.java => CreateDocumentCommand.java} |  31 ++-
 .../command/CreateDocumentPageCommand.java         |  55 ++++
 .../CreateIdentificationCardScanCommand.java       |  52 ++++
 ...omerCommand.java => DeleteDocumentCommand.java} |  29 +-
 .../command/DeleteDocumentPageCommand.java         |  52 ++++
 ...va => DeleteIdentificationCardScanCommand.java} |  15 +-
 .../command/handler/CustomerAggregate.java         | 113 ++++++--
 .../command/handler/DocumentCommandHandler.java    | 149 ++++++++++
 .../internal/command/handler/TaskAggregate.java    |  25 +-
 .../service/internal/mapper/CustomerMapper.java    |  14 +
 .../service/internal/mapper/DocumentMapper.java    |  72 +++++
 .../mapper/IdentificationCardScanMapper.java       |  44 +++
 .../internal/mapper/TaskInstanceMapper.java        |   2 +-
 .../internal/repository/CommandRepository.java     |   4 +-
 .../internal/repository/CustomerEntity.java        |  13 +
 .../internal/repository/CustomerRepository.java    |   4 +-
 .../internal/repository/DocumentEntity.java        | 129 +++++++++
 ...PortraitEntity.java => DocumentPageEntity.java} |  94 +++++--
 ...Repository.java => DocumentPageRepository.java} |  19 +-
 ...CardRepository.java => DocumentRepository.java} |  18 +-
 .../repository/IdentificationCardRepository.java   |   4 +-
 ...tity.java => IdentificationCardScanEntity.java} | 105 ++++---
 ....java => IdentificationCardScanRepository.java} |  10 +-
 .../internal/repository/PortraitEntity.java        |  11 +-
 .../repository/TaskInstanceRepository.java         |   3 +
 .../service/internal/service/CustomerService.java  | 214 +++++++++------
 .../service/internal/service/DocumentService.java  | 103 +++++++
 .../service/internal/service/TaskService.java      |   9 +-
 .../rest/config/CustomerRestConfiguration.java     |   2 +
 .../service/rest/config/UploadProperties.java      |  51 ++++
 .../rest/controller/CustomerRestController.java    | 281 ++++++++++++++-----
 .../rest/controller/DocumentsRestController.java   | 305 +++++++++++++++++++++
 ...trait.sql => V4__identification_card_scans.sql} |  20 +-
 .../mariadb/V5__add_payroll_distributions.sql      |  39 +++
 ..._flag.sql => V6__drop_pyroll_distributions.sql} |   7 +-
 ...V2__customer_portrait.sql => V7__documents.sql} |  25 +-
 ...mber_flag.sql => V8__documents_description.sql} |   6 +-
 ...ql => V9__add_application_date_to_customer.sql} |   6 +-
 91 files changed, 3899 insertions(+), 569 deletions(-)
 copy api/src/main/java/io/mifos/customer/api/v1/client/{IdentificationCardNotFoundException.java => CompletedDocumentCannotBeChangedException.java} (87%)
 create mode 100644 api/src/main/java/io/mifos/customer/api/v1/client/CustomerDocumentsManager.java
 rename api/src/main/java/io/mifos/customer/api/v1/client/{PortraitValidationException.java => DocumentValidationException.java} (91%)
 copy api/src/main/java/io/mifos/customer/api/v1/client/{ContactDetailValidationException.java => ScanAlreadyExistsException.java} (91%)
 copy api/src/main/java/io/mifos/customer/api/v1/client/{CustomerValidationException.java => ScanNotFoundException.java} (91%)
 copy api/src/main/java/io/mifos/customer/api/v1/client/{CustomerValidationException.java => ScanValidationException.java} (90%)
 create mode 100644 api/src/main/java/io/mifos/customer/api/v1/domain/CustomerDocument.java
 copy service/src/main/java/io/mifos/customer/service/internal/command/ActivateCustomerCommand.java => api/src/main/java/io/mifos/customer/api/v1/domain/IdentificationCardScan.java (62%)
 copy service/src/main/java/io/mifos/customer/service/internal/command/UpdateContactDetailsCommand.java => api/src/main/java/io/mifos/customer/api/v1/domain/ProcessStep.java (54%)
 create mode 100644 api/src/main/java/io/mifos/customer/api/v1/events/DocumentEvent.java
 create mode 100644 api/src/main/java/io/mifos/customer/api/v1/events/DocumentPageEvent.java
 create mode 100644 api/src/main/java/io/mifos/customer/api/v1/events/ScanEvent.java
 copy api/src/main/java/io/mifos/customer/catalog/api/v1/client/{CatalogValidationException.java => CatalogAlreadyInUseException.java} (90%)
 copy api/src/main/java/io/mifos/customer/catalog/api/v1/client/{CatalogValidationException.java => CatalogNotFoundException.java} (90%)
 copy api/src/main/java/io/mifos/customer/catalog/api/v1/client/{CatalogValidationException.java => FieldAlreadyInUseException.java} (91%)
 create mode 100644 api/src/test/java/io/mifos/customer/api/v1/domain/CustomerDocumentTest.java
 create mode 100644 component-test/src/main/java/io/mifos/customer/TestDocuments.java
 create mode 100644 component-test/src/main/java/io/mifos/customer/TestIdentificationCards.java
 create mode 100644 component-test/src/main/java/io/mifos/customer/listener/DocumentEventListener.java
 copy component-test/src/main/java/io/mifos/customer/util/{CommandGenerator.java => CustomerDocumentGenerator.java} (58%)
 copy component-test/src/main/java/io/mifos/customer/util/{ContactDetailGenerator.java => ScanGenerator.java} (53%)
 rename component-test/src/main/resources/{logback.xml => logback-test.xml} (100%)
 copy service/src/main/java/io/mifos/customer/catalog/service/internal/command/{CreateCatalogCommand.java => ChangeFieldCommand.java} (61%)
 copy service/src/main/java/io/mifos/customer/{service/internal/command/DeletePortraitCommand.java => catalog/service/internal/command/DeleteCatalogCommand.java} (83%)
 copy service/src/main/java/io/mifos/customer/catalog/service/internal/command/{CreateCatalogCommand.java => DeleteFieldCommand.java} (60%)
 copy service/src/main/java/io/mifos/customer/{service/internal/repository/AddressRepository.java => catalog/service/internal/repository/OptionRepository.java} (77%)
 copy service/src/main/java/io/mifos/customer/service/internal/command/{ExecuteTaskForCustomerCommand.java => ChangeDocumentCommand.java} (51%)
 copy service/src/main/java/io/mifos/customer/service/internal/command/{AddTaskDefinitionToCustomerCommand.java => CompleteDocumentCommand.java} (55%)
 copy service/src/main/java/io/mifos/customer/service/internal/command/{ExecuteTaskForCustomerCommand.java => CreateDocumentCommand.java} (51%)
 create mode 100644 service/src/main/java/io/mifos/customer/service/internal/command/CreateDocumentPageCommand.java
 create mode 100644 service/src/main/java/io/mifos/customer/service/internal/command/CreateIdentificationCardScanCommand.java
 copy service/src/main/java/io/mifos/customer/service/internal/command/{AddTaskDefinitionToCustomerCommand.java => DeleteDocumentCommand.java} (55%)
 create mode 100644 service/src/main/java/io/mifos/customer/service/internal/command/DeleteDocumentPageCommand.java
 copy service/src/main/java/io/mifos/customer/service/internal/command/{DeleteIdentificationCardCommand.java => DeleteIdentificationCardScanCommand.java} (68%)
 create mode 100644 service/src/main/java/io/mifos/customer/service/internal/command/handler/DocumentCommandHandler.java
 create mode 100644 service/src/main/java/io/mifos/customer/service/internal/mapper/DocumentMapper.java
 create mode 100644 service/src/main/java/io/mifos/customer/service/internal/mapper/IdentificationCardScanMapper.java
 create mode 100644 service/src/main/java/io/mifos/customer/service/internal/repository/DocumentEntity.java
 copy service/src/main/java/io/mifos/customer/service/internal/repository/{PortraitEntity.java => DocumentPageEntity.java} (51%)
 copy service/src/main/java/io/mifos/customer/service/internal/repository/{IdentificationCardRepository.java => DocumentPageRepository.java} (50%)
 copy service/src/main/java/io/mifos/customer/service/internal/repository/{IdentificationCardRepository.java => DocumentRepository.java} (58%)
 copy service/src/main/java/io/mifos/customer/service/internal/repository/{IdentificationCardEntity.java => IdentificationCardScanEntity.java} (50%)
 copy service/src/main/java/io/mifos/customer/service/internal/repository/{IdentificationCardRepository.java => IdentificationCardScanRepository.java} (59%)
 create mode 100644 service/src/main/java/io/mifos/customer/service/internal/service/DocumentService.java
 create mode 100644 service/src/main/java/io/mifos/customer/service/rest/config/UploadProperties.java
 create mode 100644 service/src/main/java/io/mifos/customer/service/rest/controller/DocumentsRestController.java
 copy service/src/main/resources/db/migrations/mariadb/{V2__customer_portrait.sql => V4__identification_card_scans.sql} (57%)
 create mode 100644 service/src/main/resources/db/migrations/mariadb/V5__add_payroll_distributions.sql
 copy service/src/main/resources/db/migrations/mariadb/{V3__added_is_member_flag.sql => V6__drop_pyroll_distributions.sql} (78%)
 copy service/src/main/resources/db/migrations/mariadb/{V2__customer_portrait.sql => V7__documents.sql} (50%)
 copy service/src/main/resources/db/migrations/mariadb/{V3__added_is_member_flag.sql => V8__documents_description.sql} (78%)
 copy service/src/main/resources/db/migrations/mariadb/{V3__added_is_member_flag.sql => V9__add_application_date_to_customer.sql} (78%)

-- 
To stop receiving notification emails like this one, please contact
myrle@apache.org.

[fineract-cn-customer] 03/46: Merge pull request #13 from myrle-krantz/develop

Posted by my...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

myrle pushed a commit to branch develop
in repository https://gitbox.apache.org/repos/asf/fineract-cn-customer.git

commit 1500da396718921cda9bd8120384b7a318abecb5
Merge: fb278a5 2db0052
Author: Myrle Krantz <my...@apache.org>
AuthorDate: Wed Jul 12 23:46:05 2017 +0200

    Merge pull request #13 from myrle-krantz/develop
    
    minor improvements

 ...frastructure.java => AbstractCustomerTest.java} | 76 +++++++++---------
 .../io/mifos/customer/SuiteTestEnvironment.java    | 40 ++++++++++
 .../main/java/io/mifos/customer/TestCustomer.java  | 91 +---------------------
 .../java/io/mifos/customer/TestInfrastructure.java | 65 +---------------
 .../src/main/java/io/mifos/customer/TestSuite.java | 32 ++++++++
 .../java/io/mifos/customer/TestTaskDefinition.java | 88 +--------------------
 .../java/io/mifos/customer/TestTaskInstance.java   | 88 +--------------------
 shared.gradle                                      |  1 +
 8 files changed, 118 insertions(+), 363 deletions(-)


-- 
To stop receiving notification emails like this one, please contact
myrle@apache.org.

[fineract-cn-customer] 28/46: Merge pull request #11 from markusgeiss/develop

Posted by my...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

myrle pushed a commit to branch develop
in repository https://gitbox.apache.org/repos/asf/fineract-cn-customer.git

commit 9936657b9ab8cfb789d213120fcccc06ce0201e5
Merge: 5184b15 02cda5a
Author: Markus Geiss <ma...@kuelap.io>
AuthorDate: Wed Sep 27 11:35:05 2017 +0200

    Merge pull request #11 from markusgeiss/develop
    
    added delete and update for catalogs and fields

 .../catalog/api/v1/CatalogEventConstants.java      |   7 +-
 .../catalog/api/v1/client/CatalogManager.java      |  44 +++++++
 .../CatalogNotFoundException.java}                 |  12 +-
 .../io/mifos/customer/catalog/TestCatalog.java     | 144 +++++++++++++++++++++
 .../catalog/listener/CatalogEventListener.java     |  30 ++++-
 .../internal/command/ChangeFieldCommand.java       |  22 +++-
 .../internal/command/DeleteCatalogCommand.java     |  18 +--
 .../internal/command/DeleteFieldCommand.java       |  22 +++-
 .../internal/command/handler/CatalogAggregate.java |  82 +++++++++++-
 .../internal/repository/FieldValueRepository.java  |   3 +
 .../internal/repository/OptionRepository.java      |  12 +-
 .../service/internal/service/CatalogService.java   |  40 +++++-
 .../rest/controller/CatalogRestController.java     |  65 ++++++++++
 13 files changed, 458 insertions(+), 43 deletions(-)

-- 
To stop receiving notification emails like this one, please contact
myrle@apache.org.

[fineract-cn-customer] 04/46: Add identification card upload

Posted by my...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

myrle pushed a commit to branch develop
in repository https://gitbox.apache.org/repos/asf/fineract-cn-customer.git

commit 498c48ec0336bfe0ba0c17b26d042876cb31bf34
Author: Mark <ma...@gmail.com>
AuthorDate: Wed Jul 19 18:24:20 2017 +0200

    Add identification card upload
---
 .../customer/api/v1/CustomerEventConstants.java    |   6 +
 .../customer/api/v1/client/CustomerManager.java    |  83 +++++--
 .../api/v1/client/ScanAlreadyExistsException.java  |  19 +-
 .../api/v1/client/ScanNotFoundException.java       |  19 +-
 .../api/v1/client/ScanValidationException.java     |  17 +-
 .../api/v1/domain/IdentificationCardScan.java      |  38 ++--
 .../io/mifos/customer/api/v1/events/ScanEvent.java |  52 +++++
 .../main/java/io/mifos/customer/TestCustomer.java  | 105 +--------
 .../io/mifos/customer/TestIdentificationCards.java | 252 +++++++++++++++++++++
 .../src/main/java/io/mifos/customer/TestSuite.java |   1 +
 .../customer/listener/CustomerEventListener.java   |  19 ++
 .../java/io/mifos/customer/util/ScanGenerator.java |  39 ++++
 .../CreateIdentificationCardScanCommand.java       |  52 +++++
 .../DeleteIdentificationCardScanCommand.java       |  32 +--
 .../command/handler/CustomerAggregate.java         |  64 ++++++
 .../mapper/IdentificationCardScanMapper.java       |  44 ++++
 .../repository/IdentificationCardScanEntity.java   | 128 +++++++++++
 .../IdentificationCardScanRepository.java          |  35 +++
 .../service/internal/service/CustomerService.java  |  45 +++-
 .../rest/controller/CustomerRestController.java    | 151 +++++++++++-
 .../mariadb/V4__identification_card_scans.sql      |  30 +++
 21 files changed, 1016 insertions(+), 215 deletions(-)

diff --git a/api/src/main/java/io/mifos/customer/api/v1/CustomerEventConstants.java b/api/src/main/java/io/mifos/customer/api/v1/CustomerEventConstants.java
index 97a15c8..ab5a9a3 100644
--- a/api/src/main/java/io/mifos/customer/api/v1/CustomerEventConstants.java
+++ b/api/src/main/java/io/mifos/customer/api/v1/CustomerEventConstants.java
@@ -32,6 +32,9 @@ public interface CustomerEventConstants {
   String PUT_IDENTIFICATION_CARD = "put-identification-card";
   String DELETE_IDENTIFICATION_CARD = "delete-identification-card";
 
+  String POST_IDENTIFICATION_CARD_SCAN = "post-identification-card-scan";
+  String DELETE_IDENTIFICATION_CARD_SCAN = "delete-identification-card-scan";
+
   String ACTIVATE_CUSTOMER = "activate-customer";
   String LOCK_CUSTOMER = "lock-customer";
   String UNLOCK_CUSTOMER = "unlock-customer";
@@ -55,6 +58,9 @@ public interface CustomerEventConstants {
   String SELECTOR_PUT_IDENTIFICATION_CARD = SELECTOR_NAME + " = '" + PUT_IDENTIFICATION_CARD + "'";
   String SELECTOR_DELETE_IDENTIFICATION_CARD = SELECTOR_NAME + " = '" + DELETE_IDENTIFICATION_CARD + "'";
 
+  String SELECTOR_POST_IDENTIFICATION_CARD_SCAN = SELECTOR_NAME + " = '" + POST_IDENTIFICATION_CARD_SCAN + "'";
+  String SELECTOR_DELETE_IDENTIFICATION_CARD_SCAN = SELECTOR_NAME + " = '" + DELETE_IDENTIFICATION_CARD_SCAN + "'";
+
   String SELECTOR_ACTIVATE_CUSTOMER = SELECTOR_NAME + " = '" + ACTIVATE_CUSTOMER + "'";
   String SELECTOR_LOCK_CUSTOMER = SELECTOR_NAME + " = '" + LOCK_CUSTOMER + "'";
   String SELECTOR_UNLOCK_CUSTOMER = SELECTOR_NAME + " = '" + UNLOCK_CUSTOMER + "'";
diff --git a/api/src/main/java/io/mifos/customer/api/v1/client/CustomerManager.java b/api/src/main/java/io/mifos/customer/api/v1/client/CustomerManager.java
index bac6bcb..597a3aa 100644
--- a/api/src/main/java/io/mifos/customer/api/v1/client/CustomerManager.java
+++ b/api/src/main/java/io/mifos/customer/api/v1/client/CustomerManager.java
@@ -17,26 +17,16 @@ package io.mifos.customer.api.v1.client;
 
 import io.mifos.core.api.annotation.ThrowsException;
 import io.mifos.core.api.annotation.ThrowsExceptions;
-import io.mifos.core.api.util.CustomFeignClientsConfiguration;
+import io.mifos.core.lang.validation.constraints.ValidIdentifier;
 import io.mifos.customer.api.v1.config.CustomerFeignClientConfig;
-import io.mifos.customer.api.v1.domain.Address;
-import io.mifos.customer.api.v1.domain.Command;
-import io.mifos.customer.api.v1.domain.ContactDetail;
-import io.mifos.customer.api.v1.domain.IdentificationCard;
-import io.mifos.customer.api.v1.domain.Customer;
-import io.mifos.customer.api.v1.domain.CustomerPage;
-import io.mifos.customer.api.v1.domain.ProcessStep;
-import io.mifos.customer.api.v1.domain.TaskDefinition;
+import io.mifos.customer.api.v1.domain.*;
 import org.springframework.cloud.netflix.feign.FeignClient;
 import org.springframework.http.HttpStatus;
 import org.springframework.http.MediaType;
-import org.springframework.web.bind.annotation.PathVariable;
-import org.springframework.web.bind.annotation.RequestBody;
-import org.springframework.web.bind.annotation.RequestMapping;
-import org.springframework.web.bind.annotation.RequestMethod;
-import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.*;
 import org.springframework.web.multipart.MultipartFile;
 
+import javax.validation.constraints.Size;
 import java.util.List;
 
 @SuppressWarnings("unused")
@@ -233,6 +223,71 @@ public interface CustomerManager {
                                 @PathVariable("number") final String number);
 
   @RequestMapping(
+          value = "/customers/{identifier}/identifications/{number}/scans",
+          method = RequestMethod.GET,
+          produces = MediaType.ALL_VALUE,
+          consumes = MediaType.APPLICATION_JSON_VALUE
+  )
+  @ThrowsExceptions({
+          @ThrowsException(status = HttpStatus.NOT_FOUND, exception = IdentificationCardNotFoundException.class)
+  })
+  List<IdentificationCardScan> fetchIdentificationCardScans(@PathVariable("identifier") final String identifier,
+                                                            @PathVariable("number") final String number);
+
+  @RequestMapping(
+          value = "/customers/{identifier}/identifications/{number}/scans/{scanIdentifier}",
+          method = RequestMethod.GET,
+          produces = MediaType.ALL_VALUE,
+          consumes = MediaType.APPLICATION_JSON_VALUE
+  )
+  @ThrowsExceptions({
+          @ThrowsException(status = HttpStatus.NOT_FOUND, exception = ScanNotFoundException.class)
+  })
+  IdentificationCardScan findIdentificationCardScan(@PathVariable("identifier") final String identifier,
+                                                     @PathVariable("number") final String number,
+                                                     @PathVariable("scanIdentifier") final String scanIdentifier);
+
+  @RequestMapping(
+          value = "/customers/{identifier}/identifications/{number}/scans/{scanIdentifier}/image",
+          method = RequestMethod.GET,
+          produces = MediaType.ALL_VALUE,
+          consumes = MediaType.APPLICATION_JSON_VALUE
+  )
+  @ThrowsExceptions({
+          @ThrowsException(status = HttpStatus.NOT_FOUND, exception = ScanNotFoundException.class)
+  })
+  byte[] fetchIdentificationCardScanImage(@PathVariable("identifier") final String identifier,
+                                          @PathVariable("number") final String number,
+                                          @PathVariable("scanIdentifier") final String scanIdentifier);
+
+  @RequestMapping(
+          value = "/customers/{identifier}/identifications/{number}/scans",
+          method = RequestMethod.POST,
+          produces = MediaType.ALL_VALUE,
+          consumes = MediaType.MULTIPART_FORM_DATA_VALUE
+  )
+  @ThrowsExceptions({
+          @ThrowsException(status = HttpStatus.NOT_FOUND, exception = IdentificationCardNotFoundException.class),
+          @ThrowsException(status = HttpStatus.BAD_REQUEST, exception = ScanValidationException.class),
+          @ThrowsException(status = HttpStatus.CONFLICT, exception = ScanAlreadyExistsException.class)
+  })
+  void postIdentificationCardScan(@PathVariable("identifier") final String identifier,
+                                  @PathVariable("number") final String number,
+                                  @RequestParam("scanIdentifier") @ValidIdentifier final String scanIdentifier,
+                                  @RequestParam("description") @Size(max = 4096) final String description,
+                                  @RequestBody final MultipartFile image);
+
+  @RequestMapping(
+          value = "/customers/{identifier}/identifications/{number}/scans/{scanIdentifier}",
+          method = RequestMethod.DELETE,
+          produces = MediaType.ALL_VALUE,
+          consumes = MediaType.APPLICATION_JSON_VALUE
+  )
+  void deleteScan(@PathVariable("identifier") final String identifier,
+                  @PathVariable("number") final String number,
+                  @PathVariable("scanIdentifier") final String scanIdentifier);
+
+  @RequestMapping(
           value = "/customers/{identifier}/portrait",
           method = RequestMethod.GET,
           produces = MediaType.ALL_VALUE
diff --git a/component-test/src/main/java/io/mifos/customer/TestSuite.java b/api/src/main/java/io/mifos/customer/api/v1/client/ScanAlreadyExistsException.java
similarity index 60%
copy from component-test/src/main/java/io/mifos/customer/TestSuite.java
copy to api/src/main/java/io/mifos/customer/api/v1/client/ScanAlreadyExistsException.java
index 2e12b10..45ef5a1 100644
--- a/component-test/src/main/java/io/mifos/customer/TestSuite.java
+++ b/api/src/main/java/io/mifos/customer/api/v1/client/ScanAlreadyExistsException.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2017 The Mifos Initiative.
+ * Copyright 2016 The Mifos Initiative.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -13,20 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package io.mifos.customer;
+package io.mifos.customer.api.v1.client;
 
-import org.junit.runner.RunWith;
-import org.junit.runners.Suite;
-
-/**
- * @author Myrle Krantz
- */
-@RunWith(Suite.class)
-@Suite.SuiteClasses({
-    TestCustomer.class,
-    TestInfrastructure.class,
-    TestTaskDefinition.class,
-    TestTaskInstance.class,
-})
-public class TestSuite extends SuiteTestEnvironment {
+public final class ScanAlreadyExistsException extends RuntimeException {
 }
diff --git a/component-test/src/main/java/io/mifos/customer/TestSuite.java b/api/src/main/java/io/mifos/customer/api/v1/client/ScanNotFoundException.java
similarity index 60%
copy from component-test/src/main/java/io/mifos/customer/TestSuite.java
copy to api/src/main/java/io/mifos/customer/api/v1/client/ScanNotFoundException.java
index 2e12b10..3f2ada1 100644
--- a/component-test/src/main/java/io/mifos/customer/TestSuite.java
+++ b/api/src/main/java/io/mifos/customer/api/v1/client/ScanNotFoundException.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2017 The Mifos Initiative.
+ * Copyright 2016 The Mifos Initiative.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -13,20 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package io.mifos.customer;
+package io.mifos.customer.api.v1.client;
 
-import org.junit.runner.RunWith;
-import org.junit.runners.Suite;
-
-/**
- * @author Myrle Krantz
- */
-@RunWith(Suite.class)
-@Suite.SuiteClasses({
-    TestCustomer.class,
-    TestInfrastructure.class,
-    TestTaskDefinition.class,
-    TestTaskInstance.class,
-})
-public class TestSuite extends SuiteTestEnvironment {
+public final class ScanNotFoundException extends RuntimeException {
 }
diff --git a/component-test/src/main/java/io/mifos/customer/TestSuite.java b/api/src/main/java/io/mifos/customer/api/v1/client/ScanValidationException.java
similarity index 64%
copy from component-test/src/main/java/io/mifos/customer/TestSuite.java
copy to api/src/main/java/io/mifos/customer/api/v1/client/ScanValidationException.java
index 2e12b10..cef64f9 100644
--- a/component-test/src/main/java/io/mifos/customer/TestSuite.java
+++ b/api/src/main/java/io/mifos/customer/api/v1/client/ScanValidationException.java
@@ -13,20 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package io.mifos.customer;
+package io.mifos.customer.api.v1.client;
 
-import org.junit.runner.RunWith;
-import org.junit.runners.Suite;
-
-/**
- * @author Myrle Krantz
- */
-@RunWith(Suite.class)
-@Suite.SuiteClasses({
-    TestCustomer.class,
-    TestInfrastructure.class,
-    TestTaskDefinition.class,
-    TestTaskInstance.class,
-})
-public class TestSuite extends SuiteTestEnvironment {
+public final class ScanValidationException extends RuntimeException {
 }
diff --git a/component-test/src/main/java/io/mifos/customer/TestSuite.java b/api/src/main/java/io/mifos/customer/api/v1/domain/IdentificationCardScan.java
similarity index 53%
copy from component-test/src/main/java/io/mifos/customer/TestSuite.java
copy to api/src/main/java/io/mifos/customer/api/v1/domain/IdentificationCardScan.java
index 2e12b10..462e4fe 100644
--- a/component-test/src/main/java/io/mifos/customer/TestSuite.java
+++ b/api/src/main/java/io/mifos/customer/api/v1/domain/IdentificationCardScan.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2017 The Mifos Initiative.
+ * Copyright 2016 The Mifos Initiative.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -13,20 +13,28 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package io.mifos.customer;
+package io.mifos.customer.api.v1.domain;
 
-import org.junit.runner.RunWith;
-import org.junit.runners.Suite;
+public class IdentificationCardScan {
+
+  private String identifier;
+
+  private String description;
+
+  public String getIdentifier() {
+    return identifier;
+  }
+
+  public void setIdentifier(String identifier) {
+    this.identifier = identifier;
+  }
+
+  public String getDescription() {
+    return description;
+  }
+
+  public void setDescription(String description) {
+    this.description = description;
+  }
 
-/**
- * @author Myrle Krantz
- */
-@RunWith(Suite.class)
-@Suite.SuiteClasses({
-    TestCustomer.class,
-    TestInfrastructure.class,
-    TestTaskDefinition.class,
-    TestTaskInstance.class,
-})
-public class TestSuite extends SuiteTestEnvironment {
 }
diff --git a/api/src/main/java/io/mifos/customer/api/v1/events/ScanEvent.java b/api/src/main/java/io/mifos/customer/api/v1/events/ScanEvent.java
new file mode 100644
index 0000000..f4f8057
--- /dev/null
+++ b/api/src/main/java/io/mifos/customer/api/v1/events/ScanEvent.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2016 The Mifos Initiative.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.mifos.customer.api.v1.events;
+
+import java.util.Objects;
+
+public class ScanEvent {
+
+  private final String number;
+
+  private final String scanIdentifier;
+
+  public ScanEvent(final String number, final String scanIdentifier) {
+    this.number = number;
+    this.scanIdentifier = scanIdentifier;
+  }
+
+  @Override
+  public boolean equals(Object o) {
+    if (this == o) return true;
+    if (o == null || getClass() != o.getClass()) return false;
+    ScanEvent scanEvent = (ScanEvent) o;
+    return Objects.equals(number, scanEvent.number) &&
+            Objects.equals(scanIdentifier, scanEvent.scanIdentifier);
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hash(number, scanIdentifier);
+  }
+
+  @Override
+  public String toString() {
+    return "ScanEvent{" +
+            "number='" + number + '\'' +
+            ", scanIdentifier='" + scanIdentifier + '\'' +
+            '}';
+  }
+}
diff --git a/component-test/src/main/java/io/mifos/customer/TestCustomer.java b/component-test/src/main/java/io/mifos/customer/TestCustomer.java
index 7ddf59e..228bc53 100644
--- a/component-test/src/main/java/io/mifos/customer/TestCustomer.java
+++ b/component-test/src/main/java/io/mifos/customer/TestCustomer.java
@@ -18,7 +18,10 @@ package io.mifos.customer;
 import io.mifos.customer.api.v1.CustomerEventConstants;
 import io.mifos.customer.api.v1.client.*;
 import io.mifos.customer.api.v1.domain.*;
-import io.mifos.customer.util.*;
+import io.mifos.customer.util.AddressGenerator;
+import io.mifos.customer.util.CommandGenerator;
+import io.mifos.customer.util.ContactDetailGenerator;
+import io.mifos.customer.util.CustomerGenerator;
 import org.apache.commons.lang3.RandomStringUtils;
 import org.junit.Assert;
 import org.junit.Test;
@@ -303,106 +306,6 @@ public class TestCustomer extends AbstractCustomerTest {
   }
 
   @Test
-  public void shouldFetchIdentificationCards() throws Exception {
-    final Customer customer = CustomerGenerator.createRandomCustomer();
-
-    this.customerManager.createCustomer(customer);
-
-    this.eventRecorder.wait(CustomerEventConstants.POST_CUSTOMER, customer.getIdentifier());
-
-    Stream.of(
-            IdentificationCardGenerator.createRandomIdentificationCard(),
-            IdentificationCardGenerator.createRandomIdentificationCard(),
-            IdentificationCardGenerator.createRandomIdentificationCard()
-    ).forEach(identificationCard -> {
-      this.customerManager.createIdentificationCard(customer.getIdentifier(), identificationCard);
-      try {
-        this.eventRecorder.wait(CustomerEventConstants.POST_IDENTIFICATION_CARD, identificationCard.getNumber());
-      } catch (final InterruptedException ex) {
-        Assert.fail(ex.getMessage());
-      }
-    });
-
-    final List<IdentificationCard> result = this.customerManager.fetchIdentificationCards(customer.getIdentifier());
-
-    Assert.assertTrue(result.size() == 3);
-  }
-
-  @Test
-  public void shouldCreateIdentificationCard() throws Exception {
-    final Customer customer = CustomerGenerator.createRandomCustomer();
-
-    this.customerManager.createCustomer(customer);
-
-    this.eventRecorder.wait(CustomerEventConstants.POST_CUSTOMER, customer.getIdentifier());
-
-    final IdentificationCard newIdentificationCard = IdentificationCardGenerator.createRandomIdentificationCard();
-
-    this.customerManager.createIdentificationCard(customer.getIdentifier(), newIdentificationCard);
-
-    this.eventRecorder.wait(CustomerEventConstants.POST_IDENTIFICATION_CARD, newIdentificationCard.getNumber());
-
-    final IdentificationCard identificationCard = this.customerManager.findIdentificationCard(customer.getIdentifier(), newIdentificationCard.getNumber());
-
-    Assert.assertNotNull(identificationCard);
-
-    Assert.assertEquals(identificationCard.getCreatedBy(), TEST_USER);
-  }
-
-  @Test
-  public void shouldUpdateIdentificationCard() throws Exception {
-    final Customer customer = CustomerGenerator.createRandomCustomer();
-
-    this.customerManager.createCustomer(customer);
-
-    this.eventRecorder.wait(CustomerEventConstants.POST_CUSTOMER, customer.getIdentifier());
-
-    final IdentificationCard newIdentificationCard = IdentificationCardGenerator.createRandomIdentificationCard();
-
-    this.customerManager.createIdentificationCard(customer.getIdentifier(), newIdentificationCard);
-
-    this.eventRecorder.wait(CustomerEventConstants.POST_IDENTIFICATION_CARD, newIdentificationCard.getNumber());
-
-    final IdentificationCard identificationCard = this.customerManager.findIdentificationCard(customer.getIdentifier(), newIdentificationCard.getNumber());
-
-    final IdentificationCard updatedIdentificationCard = IdentificationCardGenerator.createRandomIdentificationCard();
-
-    updatedIdentificationCard.setNumber(newIdentificationCard.getNumber());
-
-    this.customerManager.updateIdentificationCard(customer.getIdentifier(), updatedIdentificationCard.getNumber(), updatedIdentificationCard);
-
-    this.eventRecorder.wait(CustomerEventConstants.PUT_IDENTIFICATION_CARD, updatedIdentificationCard.getNumber());
-
-    final IdentificationCard changedIdentificationCard = this.customerManager.findIdentificationCard(customer.getIdentifier(), identificationCard.getNumber());
-
-    Assert.assertEquals(updatedIdentificationCard.getType(), changedIdentificationCard.getType());
-    Assert.assertEquals(updatedIdentificationCard.getIssuer(), changedIdentificationCard.getIssuer());
-    Assert.assertEquals(updatedIdentificationCard.getNumber(), changedIdentificationCard.getNumber());
-    Assert.assertEquals(TEST_USER, changedIdentificationCard.getLastModifiedBy());
-  }
-
-  @Test(expected = IdentificationCardNotFoundException.class)
-  public void shouldDeleteIdentificationCard() throws Exception {
-    final Customer customer = CustomerGenerator.createRandomCustomer();
-
-    this.customerManager.createCustomer(customer);
-
-    this.eventRecorder.wait(CustomerEventConstants.POST_CUSTOMER, customer.getIdentifier());
-
-    final IdentificationCard identificationCard = IdentificationCardGenerator.createRandomIdentificationCard();
-
-    this.customerManager.createIdentificationCard(customer.getIdentifier(), identificationCard);
-
-    this.eventRecorder.wait(CustomerEventConstants.POST_IDENTIFICATION_CARD, identificationCard.getNumber());
-
-    this.customerManager.deleteIdentificationCard(customer.getIdentifier(), identificationCard.getNumber());
-
-    this.eventRecorder.wait(CustomerEventConstants.DELETE_IDENTIFICATION_CARD, identificationCard.getNumber());
-
-    this.customerManager.findIdentificationCard(customer.getIdentifier(), identificationCard.getNumber());
-  }
-
-  @Test
   public void shouldUploadPortrait() throws Exception {
     final Customer customer = CustomerGenerator.createRandomCustomer();
     this.customerManager.createCustomer(customer);
diff --git a/component-test/src/main/java/io/mifos/customer/TestIdentificationCards.java b/component-test/src/main/java/io/mifos/customer/TestIdentificationCards.java
new file mode 100644
index 0000000..0740bba
--- /dev/null
+++ b/component-test/src/main/java/io/mifos/customer/TestIdentificationCards.java
@@ -0,0 +1,252 @@
+/*
+ * Copyright 2016 The Mifos Initiative.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.mifos.customer;
+
+import io.mifos.customer.api.v1.CustomerEventConstants;
+import io.mifos.customer.api.v1.client.IdentificationCardNotFoundException;
+import io.mifos.customer.api.v1.client.ScanAlreadyExistsException;
+import io.mifos.customer.api.v1.client.ScanNotFoundException;
+import io.mifos.customer.api.v1.domain.Customer;
+import io.mifos.customer.api.v1.domain.IdentificationCard;
+import io.mifos.customer.api.v1.domain.IdentificationCardScan;
+import io.mifos.customer.api.v1.events.ScanEvent;
+import io.mifos.customer.util.CustomerGenerator;
+import io.mifos.customer.util.IdentificationCardGenerator;
+import io.mifos.customer.util.ScanGenerator;
+import org.apache.commons.lang3.RandomStringUtils;
+import org.junit.Assert;
+import org.junit.Test;
+import org.springframework.http.MediaType;
+import org.springframework.mock.web.MockMultipartFile;
+
+import java.util.List;
+import java.util.stream.Stream;
+
+public class TestIdentificationCards extends AbstractCustomerTest {
+
+  @Test
+  public void shouldFetchIdentificationCards() throws Exception {
+    final String customerIdentifier = this.createCustomer();
+
+    Stream.of(
+            IdentificationCardGenerator.createRandomIdentificationCard(),
+            IdentificationCardGenerator.createRandomIdentificationCard(),
+            IdentificationCardGenerator.createRandomIdentificationCard()
+    ).forEach(identificationCard -> {
+      this.customerManager.createIdentificationCard(customerIdentifier, identificationCard);
+      try {
+        this.eventRecorder.wait(CustomerEventConstants.POST_IDENTIFICATION_CARD, identificationCard.getNumber());
+      } catch (final InterruptedException ex) {
+        Assert.fail(ex.getMessage());
+      }
+    });
+
+    final List<IdentificationCard> result = this.customerManager.fetchIdentificationCards(customerIdentifier);
+
+    Assert.assertTrue(result.size() == 3);
+  }
+
+  @Test
+  public void shouldCreateIdentificationCard() throws Exception {
+    final String customerIdentifier = this.createCustomer();
+
+    final String identificationCardNumber = this.createIdentificationCard(customerIdentifier);
+
+    final IdentificationCard identificationCard = this.customerManager.findIdentificationCard(customerIdentifier, identificationCardNumber);
+
+    Assert.assertNotNull(identificationCard);
+
+    Assert.assertEquals(identificationCard.getCreatedBy(), TEST_USER);
+  }
+
+  @Test
+  public void shouldUpdateIdentificationCard() throws Exception {
+    final String customerIdentifier = this.createCustomer();
+
+    final String identificationCardNumber = this.createIdentificationCard(customerIdentifier);
+
+    final IdentificationCard identificationCard = this.customerManager.findIdentificationCard(customerIdentifier, identificationCardNumber);
+
+    final IdentificationCard updatedIdentificationCard = IdentificationCardGenerator.createRandomIdentificationCard();
+
+    updatedIdentificationCard.setNumber(identificationCardNumber);
+
+    this.customerManager.updateIdentificationCard(customerIdentifier, updatedIdentificationCard.getNumber(), updatedIdentificationCard);
+
+    this.eventRecorder.wait(CustomerEventConstants.PUT_IDENTIFICATION_CARD, updatedIdentificationCard.getNumber());
+
+    final IdentificationCard changedIdentificationCard = this.customerManager.findIdentificationCard(customerIdentifier, identificationCard.getNumber());
+
+    Assert.assertEquals(updatedIdentificationCard.getType(), changedIdentificationCard.getType());
+    Assert.assertEquals(updatedIdentificationCard.getIssuer(), changedIdentificationCard.getIssuer());
+    Assert.assertEquals(updatedIdentificationCard.getNumber(), changedIdentificationCard.getNumber());
+    Assert.assertEquals(TEST_USER, changedIdentificationCard.getLastModifiedBy());
+  }
+
+  @Test(expected = IdentificationCardNotFoundException.class)
+  public void shouldDeleteIdentificationCard() throws Exception {
+    final String customerIdentifier = this.createCustomer();
+
+    final String identificationCardNumber = this.createIdentificationCard(customerIdentifier);
+
+    this.customerManager.deleteIdentificationCard(customerIdentifier, identificationCardNumber);
+
+    this.eventRecorder.wait(CustomerEventConstants.DELETE_IDENTIFICATION_CARD, identificationCardNumber);
+
+    this.customerManager.findIdentificationCard(customerIdentifier, identificationCardNumber);
+  }
+
+  @Test(expected = IdentificationCardNotFoundException.class)
+  public void shouldDeleteIdentificationCardWithScan() throws Exception {
+    final String customerIdentifier = this.createCustomer();
+
+    final String identificationCardNumber = this.createIdentificationCard(customerIdentifier);
+
+    this.createScan(customerIdentifier, identificationCardNumber);
+
+    this.customerManager.deleteIdentificationCard(customerIdentifier, identificationCardNumber);
+
+    this.eventRecorder.wait(CustomerEventConstants.DELETE_IDENTIFICATION_CARD, identificationCardNumber);
+
+    this.customerManager.findIdentificationCard(customerIdentifier, identificationCardNumber);
+  }
+
+  @Test
+  public void shouldFetchScans() throws Exception {
+    final String customerIdentifier = this.createCustomer();
+
+    final String identificationCardNumber = this.createIdentificationCard(customerIdentifier);
+
+    this.createScan(customerIdentifier, identificationCardNumber);
+    this.createScan(customerIdentifier, identificationCardNumber);
+    this.createScan(customerIdentifier, identificationCardNumber);
+
+    final List<IdentificationCardScan> result = this.customerManager.fetchIdentificationCardScans(customerIdentifier, identificationCardNumber);
+
+    Assert.assertTrue(result.size() == 3);
+  }
+
+  @Test
+  public void shouldFindScan() throws Exception {
+    final String customerIdentifier = this.createCustomer();
+
+    final String identificationCardNumberOne = this.createIdentificationCard(customerIdentifier);
+    final String identificationCardNumberTwo = this.createIdentificationCard(customerIdentifier);
+    this.createScan(customerIdentifier, identificationCardNumberOne, "sameIdentifier");
+    final IdentificationCardScan createdScan = this.createScan(customerIdentifier, identificationCardNumberTwo, "sameIdentifier");
+
+    final IdentificationCardScan scan = this.customerManager.findIdentificationCardScan(customerIdentifier, identificationCardNumberTwo, createdScan.getIdentifier());
+
+    Assert.assertNotNull(scan);
+
+    Assert.assertEquals(scan.getIdentifier(), createdScan.getIdentifier());
+    Assert.assertEquals(scan.getDescription(), createdScan.getDescription());
+  }
+
+  @Test
+  public void shouldFindScanWithImage() throws Exception {
+    final String customerIdentifier = this.createCustomer();
+
+    final String identificationCardNumber = this.createIdentificationCard(customerIdentifier);
+
+    final IdentificationCardScan scan = ScanGenerator.createRandomScan(null);
+
+    final byte[] imageInBytes = RandomStringUtils.randomAlphanumeric(20).getBytes();
+
+    final MockMultipartFile image = new MockMultipartFile("image", "test.png", MediaType.IMAGE_PNG_VALUE, imageInBytes);
+
+    this.customerManager.postIdentificationCardScan(customerIdentifier, identificationCardNumber, scan.getIdentifier(), scan.getDescription(), image);
+
+    this.eventRecorder.wait(CustomerEventConstants.POST_IDENTIFICATION_CARD_SCAN, new ScanEvent(identificationCardNumber, scan.getIdentifier()));
+
+    final byte[] persistedImageInBytes = this.customerManager.fetchIdentificationCardScanImage(customerIdentifier, identificationCardNumber, scan.getIdentifier());
+
+    Assert.assertArrayEquals(imageInBytes, persistedImageInBytes);
+  }
+
+  @Test(expected = ScanAlreadyExistsException.class)
+  public void shouldThrowIfScanAlreadyExists() throws Exception {
+    final String customerIdentifier = this.createCustomer();
+
+    final String identificationCardNumber = this.createIdentificationCard(customerIdentifier);
+
+    final IdentificationCardScan scan = this.createScan(customerIdentifier, identificationCardNumber);
+
+    final MockMultipartFile image = new MockMultipartFile("image", "test.png", MediaType.IMAGE_PNG_VALUE, RandomStringUtils.randomAlphanumeric(20).getBytes());
+
+    this.customerManager.postIdentificationCardScan(customerIdentifier, identificationCardNumber, scan.getIdentifier(), scan.getDescription(), image);
+  }
+
+  @Test(expected = ScanNotFoundException.class)
+  public void shouldDeleteScan() throws Exception {
+    final String customerIdentifier = this.createCustomer();
+
+    final String identificationCardNumber = this.createIdentificationCard(customerIdentifier);
+
+    final IdentificationCardScan createdScan = this.createScan(customerIdentifier, identificationCardNumber);
+
+    this.customerManager.deleteScan(customerIdentifier, identificationCardNumber, createdScan.getIdentifier());
+
+    this.eventRecorder.wait(CustomerEventConstants.DELETE_IDENTIFICATION_CARD_SCAN, new ScanEvent(identificationCardNumber, createdScan.getIdentifier()));
+
+    this.customerManager.findIdentificationCardScan(customerIdentifier, identificationCardNumber, createdScan.getIdentifier());
+  }
+
+  private String createCustomer() throws Exception {
+    final Customer customer = CustomerGenerator.createRandomCustomer();
+
+    this.customerManager.createCustomer(customer);
+
+    this.eventRecorder.wait(CustomerEventConstants.POST_CUSTOMER, customer.getIdentifier());
+
+    return customer.getIdentifier();
+  }
+
+  private String createIdentificationCard(final String customerIdentifier) throws Exception {
+    final IdentificationCard identificationCard = IdentificationCardGenerator.createRandomIdentificationCard();
+
+    this.customerManager.createIdentificationCard(customerIdentifier, identificationCard);
+
+    this.eventRecorder.wait(CustomerEventConstants.POST_IDENTIFICATION_CARD, identificationCard.getNumber());
+
+    return identificationCard.getNumber();
+  }
+
+  private IdentificationCardScan createScan(final String customerIdentifier, final String cardNumber) throws Exception {
+    final IdentificationCardScan scan = ScanGenerator.createRandomScan(null);
+
+    this.postIdentificationCardScan(customerIdentifier, cardNumber, scan);
+
+    return scan;
+  }
+
+  private IdentificationCardScan createScan(final String customerIdentifier, final String cardNumber, final String identifier) throws Exception {
+    final IdentificationCardScan scan = ScanGenerator.createRandomScan(identifier);
+
+    this.postIdentificationCardScan(customerIdentifier, cardNumber, scan);
+
+    return scan;
+  }
+
+  private void postIdentificationCardScan(final String customerIdentifier, final String cardNumber, final IdentificationCardScan scan) throws InterruptedException {
+    final MockMultipartFile image = new MockMultipartFile("image", "test.png", MediaType.IMAGE_PNG_VALUE, RandomStringUtils.randomAlphanumeric(20).getBytes());
+
+    this.customerManager.postIdentificationCardScan(customerIdentifier, cardNumber, scan.getIdentifier(), scan.getDescription(), image);
+
+    this.eventRecorder.wait(CustomerEventConstants.POST_IDENTIFICATION_CARD_SCAN, new ScanEvent(cardNumber, scan.getIdentifier()));
+  }
+
+}
diff --git a/component-test/src/main/java/io/mifos/customer/TestSuite.java b/component-test/src/main/java/io/mifos/customer/TestSuite.java
index 2e12b10..4ed78bb 100644
--- a/component-test/src/main/java/io/mifos/customer/TestSuite.java
+++ b/component-test/src/main/java/io/mifos/customer/TestSuite.java
@@ -24,6 +24,7 @@ import org.junit.runners.Suite;
 @RunWith(Suite.class)
 @Suite.SuiteClasses({
     TestCustomer.class,
+    TestIdentificationCards.class,
     TestInfrastructure.class,
     TestTaskDefinition.class,
     TestTaskInstance.class,
diff --git a/component-test/src/main/java/io/mifos/customer/listener/CustomerEventListener.java b/component-test/src/main/java/io/mifos/customer/listener/CustomerEventListener.java
index f066746..5c1d2e1 100644
--- a/component-test/src/main/java/io/mifos/customer/listener/CustomerEventListener.java
+++ b/component-test/src/main/java/io/mifos/customer/listener/CustomerEventListener.java
@@ -18,6 +18,7 @@ package io.mifos.customer.listener;
 import io.mifos.core.lang.config.TenantHeaderFilter;
 import io.mifos.core.test.listener.EventRecorder;
 import io.mifos.customer.api.v1.CustomerEventConstants;
+import io.mifos.customer.api.v1.events.ScanEvent;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.jms.annotation.JmsListener;
 import org.springframework.messaging.handler.annotation.Header;
@@ -144,6 +145,24 @@ public class CustomerEventListener {
 
   @JmsListener(
           destination = CustomerEventConstants.DESTINATION,
+          selector = CustomerEventConstants.SELECTOR_POST_IDENTIFICATION_CARD_SCAN
+  )
+  public void identificationCardScanCreateEvent(@Header(TenantHeaderFilter.TENANT_HEADER) final String tenant,
+                                            final String payload) {
+    this.eventRecorder.event(tenant, CustomerEventConstants.POST_IDENTIFICATION_CARD_SCAN, payload, ScanEvent.class);
+  }
+
+  @JmsListener(
+          destination = CustomerEventConstants.DESTINATION,
+          selector = CustomerEventConstants.SELECTOR_DELETE_IDENTIFICATION_CARD_SCAN
+  )
+  public void identificationCardScanDeleteEvent(@Header(TenantHeaderFilter.TENANT_HEADER) final String tenant,
+                                                final String payload) {
+    this.eventRecorder.event(tenant, CustomerEventConstants.DELETE_IDENTIFICATION_CARD_SCAN, payload, ScanEvent.class);
+  }
+
+  @JmsListener(
+          destination = CustomerEventConstants.DESTINATION,
           selector = CustomerEventConstants.SELECTOR_PUT_PORTRAIT
   )
   public void portraitPutEvent(@Header(TenantHeaderFilter.TENANT_HEADER) final String tenant,
diff --git a/component-test/src/main/java/io/mifos/customer/util/ScanGenerator.java b/component-test/src/main/java/io/mifos/customer/util/ScanGenerator.java
new file mode 100644
index 0000000..b139623
--- /dev/null
+++ b/component-test/src/main/java/io/mifos/customer/util/ScanGenerator.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2016 The Mifos Initiative.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.mifos.customer.util;
+
+import io.mifos.customer.api.v1.domain.IdentificationCardScan;
+import org.apache.commons.lang3.RandomStringUtils;
+
+import javax.annotation.Nullable;
+
+public class ScanGenerator {
+
+  private ScanGenerator() {}
+
+  public static IdentificationCardScan createRandomScan(@Nullable final String identifier) {
+    final IdentificationCardScan scan = new IdentificationCardScan();
+
+    if(identifier != null) {
+      scan.setIdentifier(identifier);
+    } else {
+      scan.setIdentifier(RandomStringUtils.randomAlphanumeric(32));
+    }
+
+    scan.setDescription(RandomStringUtils.randomAlphanumeric(128));
+    return scan;
+  }
+}
diff --git a/service/src/main/java/io/mifos/customer/service/internal/command/CreateIdentificationCardScanCommand.java b/service/src/main/java/io/mifos/customer/service/internal/command/CreateIdentificationCardScanCommand.java
new file mode 100644
index 0000000..166ba69
--- /dev/null
+++ b/service/src/main/java/io/mifos/customer/service/internal/command/CreateIdentificationCardScanCommand.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2016 The Mifos Initiative.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.mifos.customer.service.internal.command;
+
+import io.mifos.customer.api.v1.domain.IdentificationCardScan;
+import org.springframework.web.multipart.MultipartFile;
+
+public class CreateIdentificationCardScanCommand {
+
+  private final String number;
+
+  private final IdentificationCardScan scan;
+
+  private final MultipartFile image;
+
+  public CreateIdentificationCardScanCommand(final String number, final IdentificationCardScan scan, final MultipartFile image) {
+    this.number = number;
+    this.scan = scan;
+    this.image = image;
+  }
+
+  public String number () {
+    return number;
+  }
+
+  public IdentificationCardScan scan() {
+    return scan;
+  }
+
+  public MultipartFile image() { return image; }
+
+  @Override
+  public String toString() {
+    return "CreateIdentificationCardScanCommand{" +
+            "number='" + number + '\'' +
+            ", scan=" + scan +
+            '}';
+  }
+}
diff --git a/component-test/src/main/java/io/mifos/customer/TestSuite.java b/service/src/main/java/io/mifos/customer/service/internal/command/DeleteIdentificationCardScanCommand.java
similarity index 53%
copy from component-test/src/main/java/io/mifos/customer/TestSuite.java
copy to service/src/main/java/io/mifos/customer/service/internal/command/DeleteIdentificationCardScanCommand.java
index 2e12b10..2240c0a 100644
--- a/component-test/src/main/java/io/mifos/customer/TestSuite.java
+++ b/service/src/main/java/io/mifos/customer/service/internal/command/DeleteIdentificationCardScanCommand.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2017 The Mifos Initiative.
+ * Copyright 2016 The Mifos Initiative.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -13,20 +13,22 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package io.mifos.customer;
+package io.mifos.customer.service.internal.command;
 
-import org.junit.runner.RunWith;
-import org.junit.runners.Suite;
+public class DeleteIdentificationCardScanCommand {
 
-/**
- * @author Myrle Krantz
- */
-@RunWith(Suite.class)
-@Suite.SuiteClasses({
-    TestCustomer.class,
-    TestInfrastructure.class,
-    TestTaskDefinition.class,
-    TestTaskInstance.class,
-})
-public class TestSuite extends SuiteTestEnvironment {
+  private final String number;
+
+  private final String scanIdentifier;
+
+  public DeleteIdentificationCardScanCommand(final String number, final String scanIdentifier) {
+    this.number = number;
+    this.scanIdentifier = scanIdentifier;
+  }
+
+  public String number() { return number; }
+
+  public String scanIdentifier() {
+    return scanIdentifier;
+  }
 }
diff --git a/service/src/main/java/io/mifos/customer/service/internal/command/handler/CustomerAggregate.java b/service/src/main/java/io/mifos/customer/service/internal/command/handler/CustomerAggregate.java
index 953f8fd..7ec7770 100644
--- a/service/src/main/java/io/mifos/customer/service/internal/command/handler/CustomerAggregate.java
+++ b/service/src/main/java/io/mifos/customer/service/internal/command/handler/CustomerAggregate.java
@@ -18,11 +18,13 @@ package io.mifos.customer.service.internal.command.handler;
 import io.mifos.core.api.util.UserContextHolder;
 import io.mifos.core.command.annotation.Aggregate;
 import io.mifos.core.command.annotation.CommandHandler;
+import io.mifos.core.command.annotation.CommandLogLevel;
 import io.mifos.core.command.annotation.EventEmitter;
 import io.mifos.core.lang.ServiceException;
 import io.mifos.customer.api.v1.CustomerEventConstants;
 import io.mifos.customer.api.v1.domain.Command;
 import io.mifos.customer.api.v1.domain.Customer;
+import io.mifos.customer.api.v1.events.ScanEvent;
 import io.mifos.customer.catalog.service.internal.repository.CatalogEntity;
 import io.mifos.customer.catalog.service.internal.repository.CatalogRepository;
 import io.mifos.customer.catalog.service.internal.repository.FieldEntity;
@@ -34,6 +36,7 @@ import io.mifos.customer.service.internal.mapper.*;
 import io.mifos.customer.service.internal.repository.*;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.transaction.annotation.Transactional;
+import org.springframework.web.multipart.MultipartFile;
 
 import java.io.IOException;
 import java.sql.Date;
@@ -51,6 +54,7 @@ public class CustomerAggregate {
   private final AddressRepository addressRepository;
   private final CustomerRepository customerRepository;
   private final IdentificationCardRepository identificationCardRepository;
+  private final IdentificationCardScanRepository identificationCardScanRepository;
   private final PortraitRepository portraitRepository;
   private final ContactDetailRepository contactDetailRepository;
   private final FieldValueRepository fieldValueRepository;
@@ -63,6 +67,7 @@ public class CustomerAggregate {
   public CustomerAggregate(final AddressRepository addressRepository,
                            final CustomerRepository customerRepository,
                            final IdentificationCardRepository identificationCardRepository,
+                           final IdentificationCardScanRepository identificationCardScanRepository,
                            final PortraitRepository portraitRepository,
                            final ContactDetailRepository contactDetailRepository,
                            final FieldValueRepository fieldValueRepository,
@@ -74,6 +79,7 @@ public class CustomerAggregate {
     this.addressRepository = addressRepository;
     this.customerRepository = customerRepository;
     this.identificationCardRepository = identificationCardRepository;
+    this.identificationCardScanRepository = identificationCardScanRepository;
     this.portraitRepository = portraitRepository;
     this.contactDetailRepository = contactDetailRepository;
     this.fieldValueRepository = fieldValueRepository;
@@ -381,6 +387,10 @@ public class CustomerAggregate {
 
     optionalIdentificationCardEntity.ifPresent(identificationCardEntity -> {
 
+      final List<IdentificationCardScanEntity> cardScanEntities = this.identificationCardScanRepository.findByIdentificationCard(identificationCardEntity);
+
+      this.identificationCardScanRepository.delete(cardScanEntities);
+
       this.identificationCardRepository.delete(identificationCardEntity);
 
       final CustomerEntity customerEntity = identificationCardEntity.getCustomer();
@@ -395,6 +405,60 @@ public class CustomerAggregate {
   }
 
   @Transactional
+  @CommandHandler(logStart = CommandLogLevel.INFO, logFinish = CommandLogLevel.INFO)
+  @EventEmitter(selectorName = CustomerEventConstants.SELECTOR_NAME, selectorValue = CustomerEventConstants.POST_IDENTIFICATION_CARD_SCAN)
+  public ScanEvent createIdentificationCardScan(final CreateIdentificationCardScanCommand command) throws Exception {
+    final Optional<IdentificationCardEntity> identificationCardEntity = this.identificationCardRepository.findByNumber(command.number());
+
+    final IdentificationCardEntity cardEntity = identificationCardEntity.orElseThrow(() -> ServiceException.notFound("Identification card {0} not found.", command.number()));
+
+    final IdentificationCardScanEntity identificationCardScanEntity = IdentificationCardScanMapper.map(command.scan());
+
+    final MultipartFile image = command.image();
+
+    final LocalDateTime now = LocalDateTime.now(Clock.systemUTC());
+
+    identificationCardScanEntity.setImage(image.getBytes());
+    identificationCardScanEntity.setContentType(image.getContentType());
+    identificationCardScanEntity.setSize(image.getSize());
+    identificationCardScanEntity.setIdentificationCard(cardEntity);
+    identificationCardScanEntity.setCreatedBy(UserContextHolder.checkedGetUser());
+    identificationCardScanEntity.setCreatedOn(now);
+
+    identificationCardScanRepository.save(identificationCardScanEntity);
+
+    cardEntity.setLastModifiedBy(UserContextHolder.checkedGetUser());
+    cardEntity.setLastModifiedOn(now);
+
+    identificationCardRepository.save(cardEntity);
+
+    return new ScanEvent(command.number(), command.scan().getIdentifier());
+  }
+
+  @Transactional
+  @CommandHandler(logStart = CommandLogLevel.INFO, logFinish = CommandLogLevel.INFO)
+  @EventEmitter(selectorName = CustomerEventConstants.SELECTOR_NAME, selectorValue = CustomerEventConstants.DELETE_IDENTIFICATION_CARD_SCAN)
+  public ScanEvent deleteIdentificationCardScan(final DeleteIdentificationCardScanCommand command) {
+    final Optional<IdentificationCardEntity> cardEntity = this.identificationCardRepository.findByNumber(command.number());
+    final Optional<IdentificationCardScanEntity> scanEntity = cardEntity
+            .flatMap(entity -> this.identificationCardScanRepository.findByIdentifierAndIdentificationCard(command.scanIdentifier(), entity));
+
+    scanEntity.ifPresent(identificationCardScanEntity -> {
+
+      this.identificationCardScanRepository.delete(identificationCardScanEntity);
+
+      final IdentificationCardEntity identificationCard = identificationCardScanEntity.getIdentificationCard();
+
+      identificationCard.setLastModifiedBy(UserContextHolder.checkedGetUser());
+      identificationCard.setLastModifiedOn(LocalDateTime.now(Clock.systemUTC()));
+
+      this.identificationCardRepository.save(identificationCard);
+    });
+
+    return new ScanEvent(command.number(), command.scanIdentifier());
+  }
+
+  @Transactional
   @CommandHandler
   @EventEmitter(selectorName = CustomerEventConstants.SELECTOR_NAME, selectorValue = CustomerEventConstants.POST_PORTRAIT)
   public String createPortrait(final CreatePortraitCommand createPortraitCommand) throws IOException {
diff --git a/service/src/main/java/io/mifos/customer/service/internal/mapper/IdentificationCardScanMapper.java b/service/src/main/java/io/mifos/customer/service/internal/mapper/IdentificationCardScanMapper.java
new file mode 100644
index 0000000..346adfb
--- /dev/null
+++ b/service/src/main/java/io/mifos/customer/service/internal/mapper/IdentificationCardScanMapper.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2016 The Mifos Initiative.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.mifos.customer.service.internal.mapper;
+
+import io.mifos.customer.api.v1.domain.IdentificationCardScan;
+import io.mifos.customer.service.internal.repository.IdentificationCardScanEntity;
+
+public class IdentificationCardScanMapper {
+
+  private IdentificationCardScanMapper() {
+    super();
+  }
+
+  public static IdentificationCardScanEntity map(final IdentificationCardScan scan) {
+    final IdentificationCardScanEntity entity = new IdentificationCardScanEntity();
+
+    entity.setIdentifier(scan.getIdentifier());
+    entity.setDescription(scan.getDescription());
+
+    return entity;
+  }
+
+  public static IdentificationCardScan map(final IdentificationCardScanEntity entity) {
+    final IdentificationCardScan scan = new IdentificationCardScan();
+
+    scan.setIdentifier(entity.getIdentifier());
+    scan.setDescription(entity.getDescription());
+
+    return scan;
+  }
+}
diff --git a/service/src/main/java/io/mifos/customer/service/internal/repository/IdentificationCardScanEntity.java b/service/src/main/java/io/mifos/customer/service/internal/repository/IdentificationCardScanEntity.java
new file mode 100644
index 0000000..acd9808
--- /dev/null
+++ b/service/src/main/java/io/mifos/customer/service/internal/repository/IdentificationCardScanEntity.java
@@ -0,0 +1,128 @@
+/*
+ * Copyright 2016 The Mifos Initiative.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.mifos.customer.service.internal.repository;
+
+import io.mifos.core.mariadb.util.LocalDateTimeConverter;
+import io.mifos.customer.api.v1.domain.IdentificationCard;
+
+import javax.persistence.*;
+import java.time.LocalDateTime;
+
+@Entity
+@Table(name = "maat_identification_card_scans")
+public class IdentificationCardScanEntity {
+
+  @Id
+  @GeneratedValue(strategy = GenerationType.IDENTITY)
+  @Column(name = "id")
+  private Long id;
+  @Column(name = "identifier")
+  private String identifier;
+  @Column(name = "description")
+  private String description;
+  @Lob
+  @Column(name = "image")
+  private byte[] image;
+  @Column(name = "size")
+  private Long size;
+  @Column(name = "content_type")
+  private String contentType;
+  @ManyToOne(fetch = FetchType.LAZY, optional = true)
+  @JoinColumn(name = "identification_card_id")
+  private IdentificationCardEntity identificationCard;
+  @Column(name = "created_by")
+  private String createdBy;
+  @Column(name = "created_on")
+  @Convert(converter = LocalDateTimeConverter.class)
+  private LocalDateTime createdOn;
+
+  public IdentificationCardScanEntity() {
+    super();
+  }
+
+  public Long getId() {
+    return id;
+  }
+
+  public void setId(Long id) {
+    this.id = id;
+  }
+
+  public String getIdentifier() {
+    return identifier;
+  }
+
+  public void setIdentifier(String identifier) {
+    this.identifier = identifier;
+  }
+
+  public String getDescription() {
+    return description;
+  }
+
+  public void setDescription(String description) {
+    this.description = description;
+  }
+
+  public byte[] getImage() {
+    return image;
+  }
+
+  public void setImage(byte[] image) {
+    this.image = image;
+  }
+
+  public Long getSize() {
+    return size;
+  }
+
+  public void setSize(Long size) {
+    this.size = size;
+  }
+
+  public String getContentType() {
+    return contentType;
+  }
+
+  public void setContentType(String contentType) {
+    this.contentType = contentType;
+  }
+
+  public IdentificationCardEntity getIdentificationCard() {
+    return identificationCard;
+  }
+
+  public void setIdentificationCard(IdentificationCardEntity identificationCard) {
+    this.identificationCard = identificationCard;
+  }
+
+  public String getCreatedBy() {
+    return createdBy;
+  }
+
+  public void setCreatedBy(String createdBy) {
+    this.createdBy = createdBy;
+  }
+
+  public LocalDateTime getCreatedOn() {
+    return createdOn;
+  }
+
+  public void setCreatedOn(LocalDateTime createdOn) {
+    this.createdOn = createdOn;
+  }
+
+}
diff --git a/service/src/main/java/io/mifos/customer/service/internal/repository/IdentificationCardScanRepository.java b/service/src/main/java/io/mifos/customer/service/internal/repository/IdentificationCardScanRepository.java
new file mode 100644
index 0000000..dded654
--- /dev/null
+++ b/service/src/main/java/io/mifos/customer/service/internal/repository/IdentificationCardScanRepository.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2016 The Mifos Initiative.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.mifos.customer.service.internal.repository;
+
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.Query;
+import org.springframework.data.repository.query.Param;
+import org.springframework.stereotype.Repository;
+
+import java.util.List;
+import java.util.Optional;
+
+@Repository
+public interface IdentificationCardScanRepository extends JpaRepository<IdentificationCardScanEntity, Long> {
+
+  @Query("SELECT CASE WHEN COUNT(i) > 0 THEN 'true' ELSE 'false' END FROM IdentificationCardScanEntity i WHERE i.identifier = :identifier AND i.identificationCard = :identificationCard")
+  Boolean existsByIdentifierAndIdentificationCard(@Param("identifier") final String identifier, @Param("identificationCard") final IdentificationCardEntity identificationCardEntity);
+
+  Optional<IdentificationCardScanEntity> findByIdentifierAndIdentificationCard(final String identifier, final IdentificationCardEntity identificationCardEntity);
+
+  List<IdentificationCardScanEntity> findByIdentificationCard(final IdentificationCardEntity identificationCardEntity);
+}
diff --git a/service/src/main/java/io/mifos/customer/service/internal/service/CustomerService.java b/service/src/main/java/io/mifos/customer/service/internal/service/CustomerService.java
index 5048a4c..f7fd56f 100644
--- a/service/src/main/java/io/mifos/customer/service/internal/service/CustomerService.java
+++ b/service/src/main/java/io/mifos/customer/service/internal/service/CustomerService.java
@@ -15,24 +15,14 @@
  */
 package io.mifos.customer.service.internal.service;
 
-import io.mifos.customer.api.v1.domain.Command;
-import io.mifos.customer.api.v1.domain.Customer;
-import io.mifos.customer.api.v1.domain.CustomerPage;
-import io.mifos.customer.api.v1.domain.IdentificationCard;
-import io.mifos.customer.api.v1.domain.ProcessStep;
-import io.mifos.customer.api.v1.domain.TaskDefinition;
+import io.mifos.customer.api.v1.domain.*;
 import io.mifos.customer.catalog.api.v1.domain.Value;
 import io.mifos.customer.catalog.service.internal.repository.FieldEntity;
 import io.mifos.customer.catalog.service.internal.repository.FieldValueEntity;
 import io.mifos.customer.catalog.service.internal.repository.FieldValueRepository;
 import io.mifos.customer.service.ServiceConstants;
-import io.mifos.customer.service.internal.mapper.CommandMapper;
-import io.mifos.customer.service.internal.mapper.ContactDetailMapper;
-import io.mifos.customer.service.internal.mapper.CustomerMapper;
-import io.mifos.customer.service.internal.mapper.IdentificationCardMapper;
-import io.mifos.customer.service.internal.mapper.TaskDefinitionMapper;
+import io.mifos.customer.service.internal.mapper.*;
 import io.mifos.customer.service.internal.repository.*;
-import io.mifos.customer.service.internal.mapper.AddressMapper;
 import org.slf4j.Logger;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.beans.factory.annotation.Qualifier;
@@ -52,6 +42,7 @@ public class CustomerService {
   private final Logger logger;
   private final CustomerRepository customerRepository;
   private final IdentificationCardRepository identificationCardRepository;
+  private final IdentificationCardScanRepository identificationCardScanRepository;
   private final PortraitRepository portraitRepository;
   private final ContactDetailRepository contactDetailRepository;
   private final FieldValueRepository fieldValueRepository;
@@ -63,6 +54,7 @@ public class CustomerService {
   public CustomerService(@Qualifier(ServiceConstants.LOGGER_NAME) final Logger logger,
                          final CustomerRepository customerRepository,
                          final IdentificationCardRepository identificationCardRepository,
+                         final IdentificationCardScanRepository identificationCardScanRepository,
                          final PortraitRepository portraitRepository,
                          final ContactDetailRepository contactDetailRepository,
                          final FieldValueRepository fieldValueRepository,
@@ -73,6 +65,7 @@ public class CustomerService {
     this.logger = logger;
     this.customerRepository = customerRepository;
     this.identificationCardRepository = identificationCardRepository;
+    this.identificationCardScanRepository = identificationCardScanRepository;
     this.portraitRepository = portraitRepository;
     this.contactDetailRepository = contactDetailRepository;
     this.fieldValueRepository = fieldValueRepository;
@@ -93,6 +86,12 @@ public class CustomerService {
     return this.identificationCardRepository.existsByNumber(number);
   }
 
+  public Boolean identificationCardScanExists(final String number, final String identifier) {
+    return this.identificationCardRepository.findByNumber(number)
+            .map(cardEntity -> this.identificationCardScanRepository.existsByIdentifierAndIdentificationCard(identifier, cardEntity))
+            .orElse(false);
+  }
+
   public Optional<Customer> findCustomer(final String identifier) {
     final CustomerEntity customerEntity = this.customerRepository.findByIdentifier(identifier);
     if (customerEntity != null) {
@@ -195,6 +194,28 @@ public class CustomerService {
     return identificationCardEntity.map(IdentificationCardMapper::map);
   }
 
+  public final List<IdentificationCardScan> fetchScansByIdentificationCard(final String number) {
+    final Optional<IdentificationCardEntity> identificationCard = this.identificationCardRepository.findByNumber(number);
+
+    return identificationCard.map(this.identificationCardScanRepository::findByIdentificationCard)
+            .map(x -> x.stream().map(IdentificationCardScanMapper::map).collect(Collectors.toList()))
+            .orElseGet(Collections::emptyList);
+  }
+
+  private Optional<IdentificationCardScanEntity> findIdentificationCardEntity(final String number, final String identifier) {
+    final Optional<IdentificationCardEntity> cardEntity = this.identificationCardRepository.findByNumber(number);
+    final Optional<IdentificationCardScanEntity> cardScanEntity = cardEntity.flatMap(card -> this.identificationCardScanRepository.findByIdentifierAndIdentificationCard(identifier, card));
+    return cardScanEntity;
+  }
+
+  public Optional<IdentificationCardScan> findIdentificationCardScan(final String number, final String identifier) {
+    return this.findIdentificationCardEntity(number, identifier).map(IdentificationCardScanMapper::map);
+  }
+
+  public Optional<byte[]> findIdentificationCardScanImage(final String number, final String identifier) {
+    return this.findIdentificationCardEntity(number, identifier).map(IdentificationCardScanEntity::getImage);
+  }
+
   public List<ProcessStep> getProcessSteps(final String customerIdentifier) {
     final ArrayList<ProcessStep> processSteps = new ArrayList<>();
     final CustomerEntity customerEntity = this.customerRepository.findByIdentifier(customerIdentifier);
diff --git a/service/src/main/java/io/mifos/customer/service/rest/controller/CustomerRestController.java b/service/src/main/java/io/mifos/customer/service/rest/controller/CustomerRestController.java
index 41546b9..f68549e 100644
--- a/service/src/main/java/io/mifos/customer/service/rest/controller/CustomerRestController.java
+++ b/service/src/main/java/io/mifos/customer/service/rest/controller/CustomerRestController.java
@@ -20,6 +20,7 @@ import io.mifos.anubis.annotation.Permittable;
 import io.mifos.core.api.util.UserContextHolder;
 import io.mifos.core.command.gateway.CommandGateway;
 import io.mifos.core.lang.ServiceException;
+import io.mifos.core.lang.validation.constraints.ValidIdentifier;
 import io.mifos.customer.PermittableGroupIds;
 import io.mifos.customer.api.v1.domain.*;
 import io.mifos.customer.catalog.service.internal.service.FieldValueValidator;
@@ -28,6 +29,7 @@ import io.mifos.customer.service.internal.command.*;
 import io.mifos.customer.service.internal.repository.PortraitEntity;
 import io.mifos.customer.service.internal.service.CustomerService;
 import io.mifos.customer.service.internal.service.TaskService;
+import org.apache.commons.lang.RandomStringUtils;
 import org.slf4j.Logger;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.beans.factory.annotation.Qualifier;
@@ -41,6 +43,7 @@ import org.springframework.web.bind.annotation.*;
 import org.springframework.web.multipart.MultipartFile;
 
 import javax.validation.Valid;
+import javax.validation.constraints.Size;
 import java.util.List;
 import java.util.Optional;
 
@@ -454,6 +457,120 @@ public class CustomerRestController {
     return ResponseEntity.accepted().build();
   }
 
+  @Permittable(value = AcceptedTokenType.TENANT, groupId = PermittableGroupIds.IDENTIFICATIONS)
+  @RequestMapping(
+          value = "/customers/{identifier}/identifications/{number}/scans",
+          method = RequestMethod.GET,
+          produces = MediaType.APPLICATION_JSON_VALUE,
+          consumes = MediaType.ALL_VALUE
+  )
+  public
+  @ResponseBody
+  ResponseEntity<List<IdentificationCardScan>> fetchIdentificationCardScans(@PathVariable("identifier") final String identifier,
+                                                                            @PathVariable("number") final String number) {
+    this.throwIfCustomerNotExists(identifier);
+    this.throwIfIdentificationCardNotExists(number);
+
+    final List<IdentificationCardScan> identificationCardScans = this.customerService.fetchScansByIdentificationCard(number);
+
+    return ResponseEntity.ok(identificationCardScans);
+  }
+
+  @Permittable(value = AcceptedTokenType.TENANT, groupId = PermittableGroupIds.IDENTIFICATIONS)
+  @RequestMapping(
+          value = "/customers/{identifier}/identifications/{number}/scans/{scanIdentifier}",
+          method = RequestMethod.GET,
+          produces = MediaType.APPLICATION_JSON_VALUE,
+          consumes = MediaType.ALL_VALUE
+  )
+  public
+  @ResponseBody
+  ResponseEntity<IdentificationCardScan> findIdentificationCardScan(@PathVariable("identifier") final String identifier,
+                                                                     @PathVariable("number") final String number,
+                                                                     @PathVariable("scanIdentifier") final String scanIdentifier) {
+    this.throwIfCustomerNotExists(identifier);
+    this.throwIfIdentificationCardNotExists(number);
+
+    final Optional<IdentificationCardScan> identificationCardScan = this.customerService.findIdentificationCardScan(number, scanIdentifier);
+
+    return identificationCardScan
+            .map(ResponseEntity::ok)
+            .orElseThrow(() -> ServiceException.notFound("Identification card scan {0} not found.", number));
+  }
+
+  @Permittable(value = AcceptedTokenType.TENANT, groupId = PermittableGroupIds.IDENTIFICATIONS)
+  @RequestMapping(
+          value = "/customers/{identifier}/identifications/{number}/scans/{scanIdentifier}/image",
+          method = RequestMethod.GET,
+          produces = MediaType.APPLICATION_JSON_VALUE,
+          consumes = MediaType.ALL_VALUE
+  )
+  public
+  @ResponseBody
+  ResponseEntity<byte[]> fetchIdentificationCardScanImage(@PathVariable("identifier") final String identifier,
+                                          @PathVariable("number") final String number,
+                                          @PathVariable("scanIdentifier") final String scanIdentifier) {
+    this.throwIfCustomerNotExists(identifier);
+    this.throwIfIdentificationCardNotExists(number);
+    this.throwIfIdentificationCardScanNotExists(number, scanIdentifier);
+
+    final Optional<byte[]> image = this.customerService.findIdentificationCardScanImage(number, scanIdentifier);
+
+    return image.map(ResponseEntity::ok)
+            .orElseThrow(() -> ServiceException.notFound("Identification card scan {0} not found.", number));
+  }
+
+  @Permittable(value = AcceptedTokenType.TENANT, groupId = PermittableGroupIds.IDENTIFICATIONS)
+  @RequestMapping(
+          value = "/customers/{identifier}/identifications/{number}/scans",
+          produces = MediaType.APPLICATION_JSON_VALUE,
+          consumes = MediaType.MULTIPART_FORM_DATA_VALUE
+  )
+  public
+  @ResponseBody
+  ResponseEntity<Void> postIdentificationCardScan(@PathVariable("identifier") final String identifier,
+                                  @PathVariable("number") final String number,
+                                  @RequestParam("scanIdentifier") @ValidIdentifier final String scanIdentifier,
+                                  @RequestParam("description") @Size(max = 4096) final String description,
+                                  @RequestBody final MultipartFile image) throws Exception {
+    this.throwIfCustomerNotExists(identifier);
+    this.throwIfIdentificationCardNotExists(number);
+    this.throwIfInvalidSize(image.getSize());
+    this.throwIfInvalidContentType(image.getContentType());
+
+    if (this.customerService.identificationCardScanExists(number, scanIdentifier)) {
+      throw ServiceException.conflict("Scan {0} already exists.", scanIdentifier);
+    }
+
+    final IdentificationCardScan scan = new IdentificationCardScan();
+    scan.setIdentifier(scanIdentifier);
+    scan.setDescription(description);
+
+    this.commandGateway.process(new CreateIdentificationCardScanCommand(number, scan, image));
+
+    return ResponseEntity.accepted().build();
+  }
+
+  @Permittable(value = AcceptedTokenType.TENANT, groupId = PermittableGroupIds.IDENTIFICATIONS)
+  @RequestMapping(
+          value = "/customers/{identifier}/identifications/{number}/scans/{scanIdentifier}",
+          method = RequestMethod.DELETE,
+          produces = MediaType.APPLICATION_JSON_VALUE,
+          consumes = MediaType.ALL_VALUE
+  )
+  public
+  @ResponseBody
+  ResponseEntity<Void> deleteScan(@PathVariable("identifier") final String identifier,
+                  @PathVariable("number") final String number,
+                  @PathVariable("scanIdentifier") final String scanIdentifier) {
+    throwIfCustomerNotExists(identifier);
+    throwIfIdentificationCardNotExists(number);
+
+    this.commandGateway.process(new DeleteIdentificationCardScanCommand(number, scanIdentifier));
+
+    return ResponseEntity.accepted().build();
+  }
+
   @Permittable(value = AcceptedTokenType.TENANT, groupId = PermittableGroupIds.PORTRAIT)
   @RequestMapping(
       value = "/customers/{identifier}/portrait",
@@ -486,17 +603,8 @@ public class CustomerRestController {
     }
 
     this.throwIfCustomerNotExists(identifier);
-
-    final Long maxSize = this.environment.getProperty("upload.image.max-size", Long.class);
-
-    if(portrait.getSize() > maxSize) {
-      throw ServiceException.badRequest("Portrait can't exceed size of {0}", maxSize);
-    }
-
-    if(!portrait.getContentType().contains(MediaType.IMAGE_JPEG_VALUE)
-            && !portrait.getContentType().contains(MediaType.IMAGE_PNG_VALUE)) {
-      throw ServiceException.badRequest("Only content type {0} and {1} allowed", MediaType.IMAGE_JPEG_VALUE, MediaType.IMAGE_PNG_VALUE);
-    }
+    this.throwIfInvalidSize(portrait.getSize());
+    this.throwIfInvalidContentType(portrait.getContentType());
 
     try {
       this.commandGateway.process(new DeletePortraitCommand(identifier), String.class).get();
@@ -628,4 +736,25 @@ public class CustomerRestController {
       throw ServiceException.notFound("Identification card {0} not found.", number);
     }
   }
+
+  private void throwIfIdentificationCardScanNotExists(final String number, final String identifier) {
+    if (!this.customerService.identificationCardScanExists(number, identifier)) {
+      throw ServiceException.notFound("Identification card scan {0} not found.", identifier);
+    }
+  }
+
+  private void throwIfInvalidSize(final Long size) {
+    final Long maxSize = this.environment.getProperty("upload.image.max-size", Long.class);
+
+    if(size > maxSize) {
+      throw ServiceException.badRequest("Image can''t exceed size of {0}", maxSize);
+    }
+  }
+
+  private void throwIfInvalidContentType(final String contentType) {
+    if(!contentType.contains(MediaType.IMAGE_JPEG_VALUE)
+            && !contentType.contains(MediaType.IMAGE_PNG_VALUE)) {
+      throw ServiceException.badRequest("Only content type {0} and {1} allowed", MediaType.IMAGE_JPEG_VALUE, MediaType.IMAGE_PNG_VALUE);
+    }
+  }
 }
diff --git a/service/src/main/resources/db/migrations/mariadb/V4__identification_card_scans.sql b/service/src/main/resources/db/migrations/mariadb/V4__identification_card_scans.sql
new file mode 100644
index 0000000..1a1b5e3
--- /dev/null
+++ b/service/src/main/resources/db/migrations/mariadb/V4__identification_card_scans.sql
@@ -0,0 +1,30 @@
+--
+-- Copyright 2017 The Mifos Initiative.
+--
+-- Licensed under the Apache License, Version 2.0 (the "License");
+-- you may not use this file except in compliance with the License.
+-- You may obtain a copy of the License at
+--
+--    http://www.apache.org/licenses/LICENSE-2.0
+--
+-- Unless required by applicable law or agreed to in writing, software
+-- distributed under the License is distributed on an "AS IS" BASIS,
+-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+-- See the License for the specific language governing permissions and
+-- limitations under the License.
+--
+
+CREATE TABLE maat_identification_card_scans (
+  id BIGINT NOT NULL AUTO_INCREMENT,
+  identifier VARCHAR(32) NOT NULL,
+  description VARCHAR(4096) NOT NULL,
+  identification_card_id BIGINT NOT NULL,
+  content_type VARCHAR(256) NOT NULL,
+  size BIGINT NOT NULL,
+  image MEDIUMBLOB NOT NULL,
+  created_on TIMESTAMP(3) NOT NULL,
+  created_by VARCHAR(32) NOT NULL,
+  CONSTRAINT maat_ident_card_scans_pk PRIMARY KEY (id),
+  CONSTRAINT maat_ident_card_scans_ident_uq UNIQUE (identifier, identification_card_id),
+  CONSTRAINT maat_ident_card_scans_fk FOREIGN KEY (identification_card_id) REFERENCES maat_identification_cards (id)
+);
\ No newline at end of file

-- 
To stop receiving notification emails like this one, please contact
myrle@apache.org.

[fineract-cn-customer] 07/46: Merge branch 'develop' of https://github.com/KuelapInc/customer into develop

Posted by my...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

myrle pushed a commit to branch develop
in repository https://gitbox.apache.org/repos/asf/fineract-cn-customer.git

commit 6c963ddf36e2958ca0388942ba28a278a2c86203
Merge: a8fa2be 65d25fe
Author: Myrle Krantz <my...@apache.org>
AuthorDate: Mon Aug 14 12:56:41 2017 +0200

    Merge branch 'develop' of https://github.com/KuelapInc/customer into develop
    
    # Conflicts:
    #	api/src/main/java/io/mifos/customer/api/v1/client/CustomerManager.java

 .../customer/api/v1/CustomerEventConstants.java    |   6 +
 .../customer/api/v1/client/CustomerManager.java    |  82 +++++--
 .../api/v1/client/ScanAlreadyExistsException.java  |  19 +-
 .../api/v1/client/ScanNotFoundException.java       |  19 +-
 .../api/v1/client/ScanValidationException.java     |  17 +-
 .../api/v1/domain/IdentificationCardScan.java      |  38 ++--
 .../io/mifos/customer/api/v1/events/ScanEvent.java |  52 +++++
 .../main/java/io/mifos/customer/TestCustomer.java  | 105 +--------
 .../io/mifos/customer/TestIdentificationCards.java | 252 +++++++++++++++++++++
 .../src/main/java/io/mifos/customer/TestSuite.java |   1 +
 .../customer/listener/CustomerEventListener.java   |  19 ++
 .../java/io/mifos/customer/util/ScanGenerator.java |  39 ++++
 .../CreateIdentificationCardScanCommand.java       |  52 +++++
 .../DeleteIdentificationCardScanCommand.java       |  32 +--
 .../command/handler/CustomerAggregate.java         |  64 ++++++
 .../mapper/IdentificationCardScanMapper.java       |  44 ++++
 .../repository/IdentificationCardScanEntity.java   | 128 +++++++++++
 .../IdentificationCardScanRepository.java          |  35 +++
 .../service/internal/service/CustomerService.java  |  45 +++-
 .../rest/controller/CustomerRestController.java    | 151 +++++++++++-
 .../mariadb/V4__identification_card_scans.sql      |  30 +++
 21 files changed, 1016 insertions(+), 214 deletions(-)


-- 
To stop receiving notification emails like this one, please contact
myrle@apache.org.

[fineract-cn-customer] 11/46: added validation to prevent empty commands in task definition

Posted by my...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

myrle pushed a commit to branch develop
in repository https://gitbox.apache.org/repos/asf/fineract-cn-customer.git

commit 50a054e2f9d6f2f7c99c09f37fc2d0d6cdb23aeb
Author: mgeiss <mg...@mifos.org>
AuthorDate: Wed Sep 6 12:03:09 2017 +0200

    added validation to prevent empty commands in task definition
---
 api/src/main/java/io/mifos/customer/api/v1/domain/TaskDefinition.java | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/api/src/main/java/io/mifos/customer/api/v1/domain/TaskDefinition.java b/api/src/main/java/io/mifos/customer/api/v1/domain/TaskDefinition.java
index a81fba6..2a1ef6b 100644
--- a/api/src/main/java/io/mifos/customer/api/v1/domain/TaskDefinition.java
+++ b/api/src/main/java/io/mifos/customer/api/v1/domain/TaskDefinition.java
@@ -16,6 +16,7 @@
 package io.mifos.customer.api.v1.domain;
 
 import org.hibernate.validator.constraints.NotBlank;
+import org.hibernate.validator.constraints.NotEmpty;
 
 import javax.validation.constraints.NotNull;
 import java.util.HashSet;
@@ -39,6 +40,7 @@ public final class TaskDefinition {
   private String identifier;
   @NotNull
   private Type type;
+  @NotEmpty
   private Set<Command> commands;
   @NotBlank
   private String name;

-- 
To stop receiving notification emails like this one, please contact
myrle@apache.org.

[fineract-cn-customer] 06/46: Added helper function to feign client isCustomerInGoodStanding.

Posted by my...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

myrle pushed a commit to branch develop
in repository https://gitbox.apache.org/repos/asf/fineract-cn-customer.git

commit a8fa2be1cc77767cd5590f736f6d7977534e99a8
Author: Myrle Krantz <my...@apache.org>
AuthorDate: Mon Aug 14 12:49:49 2017 +0200

    Added helper function to feign client isCustomerInGoodStanding.
---
 .../customer/api/v1/client/CustomerManager.java    | 13 +++++++++-
 .../main/java/io/mifos/customer/TestCustomer.java  | 30 ++++++++++++++++++++++
 2 files changed, 42 insertions(+), 1 deletion(-)

diff --git a/api/src/main/java/io/mifos/customer/api/v1/client/CustomerManager.java b/api/src/main/java/io/mifos/customer/api/v1/client/CustomerManager.java
index bac6bcb..281ce1c 100644
--- a/api/src/main/java/io/mifos/customer/api/v1/client/CustomerManager.java
+++ b/api/src/main/java/io/mifos/customer/api/v1/client/CustomerManager.java
@@ -17,7 +17,6 @@ package io.mifos.customer.api.v1.client;
 
 import io.mifos.core.api.annotation.ThrowsException;
 import io.mifos.core.api.annotation.ThrowsExceptions;
-import io.mifos.core.api.util.CustomFeignClientsConfiguration;
 import io.mifos.customer.api.v1.config.CustomerFeignClientConfig;
 import io.mifos.customer.api.v1.domain.Address;
 import io.mifos.customer.api.v1.domain.Command;
@@ -77,6 +76,18 @@ public interface CustomerManager {
   @ThrowsException(status = HttpStatus.NOT_FOUND, exception = CustomerNotFoundException.class)
   Customer findCustomer(@PathVariable("identifier") final String identifier);
 
+  default boolean isCustomerInGoodStanding(final String customerIdentifier) {
+    final Customer customer;
+    try {
+      customer = this.findCustomer(customerIdentifier);
+    }
+    catch (CustomerNotFoundException e) {
+      return false;
+    }
+    final Customer.State state = Customer.State.valueOf(customer.getCurrentState());
+    return (state == Customer.State.ACTIVE);
+  }
+
   @RequestMapping(
       value = "/customers/{identifier}",
       method = RequestMethod.PUT,
diff --git a/component-test/src/main/java/io/mifos/customer/TestCustomer.java b/component-test/src/main/java/io/mifos/customer/TestCustomer.java
index 7ddf59e..0df8a02 100644
--- a/component-test/src/main/java/io/mifos/customer/TestCustomer.java
+++ b/component-test/src/main/java/io/mifos/customer/TestCustomer.java
@@ -38,6 +38,9 @@ public class TestCustomer extends AbstractCustomerTest {
 
     this.eventRecorder.wait(CustomerEventConstants.POST_CUSTOMER, customer.getIdentifier());
 
+    //Pending customer is not in good standing.
+    Assert.assertFalse(this.customerManager.isCustomerInGoodStanding(customer.getIdentifier()));
+
     final Customer createdCustomer = this.customerManager.findCustomer(customer.getIdentifier());
     Assert.assertNotNull(createdCustomer);
   }
@@ -97,6 +100,11 @@ public class TestCustomer extends AbstractCustomerTest {
   }
 
   @Test
+  public void shouldFindNonExistentCustomerIsNotInGoodStanding() throws Exception {
+    Assert.assertFalse(this.customerManager.isCustomerInGoodStanding(testEnvironment.generateUniqueIdentifer("don")));
+  }
+
+  @Test
   public void shouldFetchCustomers() throws Exception {
     Stream.of(
         CustomerGenerator.createRandomCustomer(),
@@ -164,6 +172,8 @@ public class TestCustomer extends AbstractCustomerTest {
     this.customerManager.customerCommand(customer.getIdentifier(), CommandGenerator.create(Command.Action.ACTIVATE, "Test"));
     this.eventRecorder.wait(CustomerEventConstants.ACTIVATE_CUSTOMER, customer.getIdentifier());
 
+    Assert.assertTrue(this.customerManager.isCustomerInGoodStanding(customer.getIdentifier()));
+
     final Customer activatedCustomer = this.customerManager.findCustomer(customer.getIdentifier());
     Assert.assertEquals(Customer.State.ACTIVE.name(), activatedCustomer.getCurrentState());
   }
@@ -178,9 +188,13 @@ public class TestCustomer extends AbstractCustomerTest {
     this.customerManager.customerCommand(customer.getIdentifier(), CommandGenerator.create(Command.Action.ACTIVATE, "Test"));
     this.eventRecorder.wait(CustomerEventConstants.ACTIVATE_CUSTOMER, customer.getIdentifier());
 
+    Assert.assertTrue(this.customerManager.isCustomerInGoodStanding(customer.getIdentifier()));
+
     this.customerManager.customerCommand(customer.getIdentifier(), CommandGenerator.create(Command.Action.LOCK, "Test"));
     this.eventRecorder.wait(CustomerEventConstants.LOCK_CUSTOMER, customer.getIdentifier());
 
+    Assert.assertFalse(this.customerManager.isCustomerInGoodStanding(customer.getIdentifier()));
+
     final Customer lockedCustomer = this.customerManager.findCustomer(customer.getIdentifier());
     Assert.assertEquals(Customer.State.LOCKED.name(), lockedCustomer.getCurrentState());
   }
@@ -195,12 +209,18 @@ public class TestCustomer extends AbstractCustomerTest {
     this.customerManager.customerCommand(customer.getIdentifier(), CommandGenerator.create(Command.Action.ACTIVATE, "Test"));
     this.eventRecorder.wait(CustomerEventConstants.ACTIVATE_CUSTOMER, customer.getIdentifier());
 
+    Assert.assertTrue(this.customerManager.isCustomerInGoodStanding(customer.getIdentifier()));
+
     this.customerManager.customerCommand(customer.getIdentifier(), CommandGenerator.create(Command.Action.LOCK, "Test"));
     this.eventRecorder.wait(CustomerEventConstants.LOCK_CUSTOMER, customer.getIdentifier());
 
+    Assert.assertFalse(this.customerManager.isCustomerInGoodStanding(customer.getIdentifier()));
+
     this.customerManager.customerCommand(customer.getIdentifier(), CommandGenerator.create(Command.Action.UNLOCK, "Test"));
     this.eventRecorder.wait(CustomerEventConstants.UNLOCK_CUSTOMER, customer.getIdentifier());
 
+    Assert.assertTrue(this.customerManager.isCustomerInGoodStanding(customer.getIdentifier()));
+
     final Customer unlockedCustomer = this.customerManager.findCustomer(customer.getIdentifier());
     Assert.assertEquals(Customer.State.ACTIVE.name(), unlockedCustomer.getCurrentState());
   }
@@ -215,9 +235,13 @@ public class TestCustomer extends AbstractCustomerTest {
     this.customerManager.customerCommand(customer.getIdentifier(), CommandGenerator.create(Command.Action.ACTIVATE, "Test"));
     this.eventRecorder.wait(CustomerEventConstants.ACTIVATE_CUSTOMER, customer.getIdentifier());
 
+    Assert.assertTrue(this.customerManager.isCustomerInGoodStanding(customer.getIdentifier()));
+
     this.customerManager.customerCommand(customer.getIdentifier(), CommandGenerator.create(Command.Action.CLOSE, "Test"));
     this.eventRecorder.wait(CustomerEventConstants.CLOSE_CUSTOMER, customer.getIdentifier());
 
+    Assert.assertFalse(this.customerManager.isCustomerInGoodStanding(customer.getIdentifier()));
+
     final Customer closedCustomer = this.customerManager.findCustomer(customer.getIdentifier());
     Assert.assertEquals(Customer.State.CLOSED.name(), closedCustomer.getCurrentState());
   }
@@ -232,12 +256,18 @@ public class TestCustomer extends AbstractCustomerTest {
     this.customerManager.customerCommand(customer.getIdentifier(), CommandGenerator.create(Command.Action.ACTIVATE, "Test"));
     this.eventRecorder.wait(CustomerEventConstants.ACTIVATE_CUSTOMER, customer.getIdentifier());
 
+    Assert.assertTrue(this.customerManager.isCustomerInGoodStanding(customer.getIdentifier()));
+
     this.customerManager.customerCommand(customer.getIdentifier(), CommandGenerator.create(Command.Action.CLOSE, "Test"));
     this.eventRecorder.wait(CustomerEventConstants.CLOSE_CUSTOMER, customer.getIdentifier());
 
+    Assert.assertFalse(this.customerManager.isCustomerInGoodStanding(customer.getIdentifier()));
+
     this.customerManager.customerCommand(customer.getIdentifier(), CommandGenerator.create(Command.Action.REOPEN, "Test"));
     this.eventRecorder.wait(CustomerEventConstants.REOPEN_CUSTOMER, customer.getIdentifier());
 
+    Assert.assertTrue(this.customerManager.isCustomerInGoodStanding(customer.getIdentifier()));
+
     final Customer reopenedCustomer = this.customerManager.findCustomer(customer.getIdentifier());
     Assert.assertEquals(Customer.State.ACTIVE.name(), reopenedCustomer.getCurrentState());
   }

-- 
To stop receiving notification emails like this one, please contact
myrle@apache.org.

[fineract-cn-customer] 23/46: added date range validation to expiration date

Posted by my...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

myrle pushed a commit to branch develop
in repository https://gitbox.apache.org/repos/asf/fineract-cn-customer.git

commit 38ab00d7e196cb8bcb5a1ba7818846e8f05b368e
Author: mgeiss <mg...@mifos.org>
AuthorDate: Tue Sep 19 10:43:29 2017 +0200

    added date range validation to expiration date
---
 .../main/java/io/mifos/customer/api/v1/domain/ExpirationDate.java | 8 ++++++++
 1 file changed, 8 insertions(+)

diff --git a/api/src/main/java/io/mifos/customer/api/v1/domain/ExpirationDate.java b/api/src/main/java/io/mifos/customer/api/v1/domain/ExpirationDate.java
index 4196d0f..973e01c 100644
--- a/api/src/main/java/io/mifos/customer/api/v1/domain/ExpirationDate.java
+++ b/api/src/main/java/io/mifos/customer/api/v1/domain/ExpirationDate.java
@@ -15,16 +15,24 @@
  */
 package io.mifos.customer.api.v1.domain;
 
+import javax.validation.constraints.Max;
+import javax.validation.constraints.Min;
 import javax.validation.constraints.NotNull;
 import java.time.LocalDate;
 
 public class ExpirationDate {
 
   @NotNull
+  @Min(1000L)
+  @Max(9999L)
   private Integer year;
   @NotNull
+  @Min(1L)
+  @Max(12L)
   private Integer month;
   @NotNull
+  @Min(1L)
+  @Max(31L)
   private Integer day;
 
   public ExpirationDate() {

-- 
To stop receiving notification emails like this one, please contact
myrle@apache.org.

[fineract-cn-customer] 22/46: Merge pull request #8 from markusgeiss/develop

Posted by my...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

myrle pushed a commit to branch develop
in repository https://gitbox.apache.org/repos/asf/fineract-cn-customer.git

commit c717b50760997dade1383a2853628569c3dbc01d
Merge: b730e07 167341d
Author: Markus Geiss <ma...@kuelap.io>
AuthorDate: Mon Sep 18 17:11:39 2017 +0200

    Merge pull request #8 from markusgeiss/develop
    
    refactored payroll into own service

 .../customer/api/v1/CustomerEventConstants.java    |   3 -
 .../customer/api/v1/client/CustomerManager.java    |  26 -----
 .../PayrollDistributionValidationException.java    |  19 ---
 .../customer/api/v1/domain/PayrollAllocation.java  |  58 ----------
 .../api/v1/domain/PayrollDistribution.java         |  86 --------------
 .../io/mifos/customer/TestPayrollDistribution.java | 115 ------------------
 .../customer/listener/CustomerEventListener.java   |   9 --
 .../command/SetPayrollDistributionCommand.java     |  38 ------
 .../command/handler/CustomerAggregate.java         |  63 +---------
 .../internal/mapper/PayrollDistributionMapper.java |  55 ---------
 .../repository/PayrollAllocationEntity.java        |  89 --------------
 .../repository/PayrollAllocationRepository.java    |  22 ----
 .../repository/PayrollDistributionEntity.java      | 128 ---------------------
 .../repository/PayrollDistributionRepository.java  |  24 ----
 .../service/internal/service/CustomerService.java  |  13 +--
 .../rest/controller/CustomerRestController.java    |  34 ------
 .../mariadb/V6__drop_pyroll_distributions.sql      |  18 +++
 17 files changed, 20 insertions(+), 780 deletions(-)

-- 
To stop receiving notification emails like this one, please contact
myrle@apache.org.

[fineract-cn-customer] 30/46: Merge pull request #12 from markusgeiss/develop

Posted by my...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

myrle pushed a commit to branch develop
in repository https://gitbox.apache.org/repos/asf/fineract-cn-customer.git

commit b56018054762f7ef3d3c3ae9fc96eb0a39eb1405
Merge: 9936657 e86d2a1
Author: Markus Geiss <ma...@kuelap.io>
AuthorDate: Thu Sep 28 12:38:10 2017 +0200

    Merge pull request #12 from markusgeiss/develop
    
    using valid identifier for domain objects

 .../main/java/io/mifos/customer/catalog/api/v1/domain/Catalog.java   | 3 ++-
 api/src/main/java/io/mifos/customer/catalog/api/v1/domain/Field.java | 3 ++-
 api/src/main/java/io/mifos/customer/catalog/api/v1/domain/Value.java | 5 +++--
 3 files changed, 7 insertions(+), 4 deletions(-)

-- 
To stop receiving notification emails like this one, please contact
myrle@apache.org.

[fineract-cn-customer] 40/46: Merge pull request #16 from myrlen/develop

Posted by my...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

myrle pushed a commit to branch develop
in repository https://gitbox.apache.org/repos/asf/fineract-cn-customer.git

commit aacadcbf4d70458077fb296eb3f6240e3932574a
Merge: 1456f73 41749c5
Author: myrlen <my...@kuelap.io>
AuthorDate: Fri Oct 20 12:01:58 2017 +0200

    Merge pull request #16 from myrlen/develop
    
    added delete endpoint

 .../customer/api/v1/CustomerEventConstants.java    |  2 +
 .../api/v1/client/CustomerDocumentsManager.java    | 15 ++++++++
 .../main/java/io/mifos/customer/TestDocuments.java | 43 ++++++++++++++++++++-
 .../customer/listener/DocumentEventListener.java   | 10 +++++
 .../internal/command/DeleteDocumentCommand.java    | 45 ++++++++++++++++++++++
 .../command/handler/DocumentCommandHandler.java    | 16 ++++++++
 .../rest/controller/DocumentsRestController.java   | 24 ++++++++++++
 7 files changed, 154 insertions(+), 1 deletion(-)

-- 
To stop receiving notification emails like this one, please contact
myrle@apache.org.

[fineract-cn-customer] 46/46: Merge pull request #19 from markusgeiss/develop

Posted by my...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

myrle pushed a commit to branch develop
in repository https://gitbox.apache.org/repos/asf/fineract-cn-customer.git

commit 3962e1885372803d9af814376e8fd905b9ba98a8
Merge: 47897f0 dfad7e2
Author: Markus Geiss <ma...@kuelap.io>
AuthorDate: Tue Oct 24 13:02:15 2017 +0200

    Merge pull request #19 from markusgeiss/develop
    
    added time zone 'Z' handling to ease UI usage

 .../src/main/java/io/mifos/customer/TestCustomer.java        |  2 +-
 .../customer/service/internal/mapper/CustomerMapper.java     | 12 ++++++++++--
 2 files changed, 11 insertions(+), 3 deletions(-)

-- 
To stop receiving notification emails like this one, please contact
myrle@apache.org.

[fineract-cn-customer] 13/46: replaced delimiter in update task

Posted by my...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

myrle pushed a commit to branch develop
in repository https://gitbox.apache.org/repos/asf/fineract-cn-customer.git

commit 136016e776223a095da3b3b18547bf28f75b6bf5
Author: mgeiss <mg...@mifos.org>
AuthorDate: Wed Sep 6 12:15:30 2017 +0200

    replaced delimiter in update task
---
 .../customer/service/internal/command/handler/TaskAggregate.java     | 5 ++---
 1 file changed, 2 insertions(+), 3 deletions(-)

diff --git a/service/src/main/java/io/mifos/customer/service/internal/command/handler/TaskAggregate.java b/service/src/main/java/io/mifos/customer/service/internal/command/handler/TaskAggregate.java
index 00b86b3..9a0417c 100644
--- a/service/src/main/java/io/mifos/customer/service/internal/command/handler/TaskAggregate.java
+++ b/service/src/main/java/io/mifos/customer/service/internal/command/handler/TaskAggregate.java
@@ -79,7 +79,7 @@ public class TaskAggregate {
     final TaskDefinition updatedTaskDefinition = updateTaskDefinitionCommand.taskDefinition();
     taskDefinitionEntity.setName(updatedTaskDefinition.getName());
     taskDefinitionEntity.setDescription(updatedTaskDefinition.getDescription());
-    taskDefinitionEntity.setAssignedCommands(StringUtils.join(updatedTaskDefinition.getCommands(), ","));
+    taskDefinitionEntity.setAssignedCommands(StringUtils.join(updatedTaskDefinition.getCommands(), ";"));
     taskDefinitionEntity.setMandatory(updatedTaskDefinition.getMandatory());
     taskDefinitionEntity.setPredefined(updatedTaskDefinition.getPredefined());
 
@@ -153,8 +153,7 @@ public class TaskAggregate {
       return taskInstanceEntities
           .stream()
               .filter(taskInstanceEntity -> taskInstanceEntity.getTaskDefinition().isMandatory())
-              .filter(taskInstanceEntity -> taskInstanceEntity.getExecutedBy() == null)
-          .findAny().isPresent();
+              .anyMatch(taskInstanceEntity -> taskInstanceEntity.getExecutedBy() == null);
     } else {
       return false;
     }

-- 
To stop receiving notification emails like this one, please contact
myrle@apache.org.

[fineract-cn-customer] 18/46: Merge pull request #6 from markusgeiss/develop

Posted by my...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

myrle pushed a commit to branch develop
in repository https://gitbox.apache.org/repos/asf/fineract-cn-customer.git

commit dd821906f82266c8432a7a644275896964f511f9
Merge: 81c0404 5d94ca2
Author: Markus Geiss <ma...@kuelap.io>
AuthorDate: Fri Sep 8 09:35:39 2017 +0200

    Merge pull request #6 from markusgeiss/develop
    
    renamed customer endpoint to customers

 .../customer/api/v1/client/CustomerManager.java    |  6 +-
 .../java/io/mifos/customer/TestTaskInstance.java   | 73 +++++++++++++++++++++-
 .../internal/command/handler/TaskAggregate.java    |  5 +-
 .../rest/controller/CustomerRestController.java    |  6 +-
 4 files changed, 80 insertions(+), 10 deletions(-)

-- 
To stop receiving notification emails like this one, please contact
myrle@apache.org.

[fineract-cn-customer] 43/46: added application date for customer

Posted by my...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

myrle pushed a commit to branch develop
in repository https://gitbox.apache.org/repos/asf/fineract-cn-customer.git

commit be60ba631a8f6c76cfe84ac5ff2ec1713c6d7cec
Author: mgeiss <mg...@mifos.org>
AuthorDate: Sun Oct 22 07:51:00 2017 +0200

    added application date for customer
---
 .../io/mifos/customer/api/v1/domain/Customer.java     |  9 +++++++++
 .../src/main/java/io/mifos/customer/TestCustomer.java |  7 +++++++
 .../internal/command/handler/CustomerAggregate.java   |  3 +++
 .../service/internal/mapper/CustomerMapper.java       |  6 ++++++
 .../service/internal/repository/CustomerEntity.java   | 13 +++++++++++++
 .../mariadb/V9__add_application_date_to_customer.sql  | 19 +++++++++++++++++++
 6 files changed, 57 insertions(+)

diff --git a/api/src/main/java/io/mifos/customer/api/v1/domain/Customer.java b/api/src/main/java/io/mifos/customer/api/v1/domain/Customer.java
index ee130b9..8dc20d8 100644
--- a/api/src/main/java/io/mifos/customer/api/v1/domain/Customer.java
+++ b/api/src/main/java/io/mifos/customer/api/v1/domain/Customer.java
@@ -60,6 +60,7 @@ public final class Customer {
   @Valid
   private List<ContactDetail> contactDetails;
   private State currentState;
+  private String applicationDate;
   private List<Value> customValues;
   private String createdBy;
   private String createdOn;
@@ -182,6 +183,14 @@ public final class Customer {
     this.currentState = State.valueOf(currentState);
   }
 
+  public String getApplicationDate() {
+    return this.applicationDate;
+  }
+
+  public void setApplicationDate(final String applicationDate) {
+    this.applicationDate = applicationDate;
+  }
+
   public List<Value> getCustomValues() {
     return this.customValues;
   }
diff --git a/component-test/src/main/java/io/mifos/customer/TestCustomer.java b/component-test/src/main/java/io/mifos/customer/TestCustomer.java
index 5ce3f66..f5e53f3 100644
--- a/component-test/src/main/java/io/mifos/customer/TestCustomer.java
+++ b/component-test/src/main/java/io/mifos/customer/TestCustomer.java
@@ -15,6 +15,7 @@
  */
 package io.mifos.customer;
 
+import io.mifos.core.lang.DateConverter;
 import io.mifos.customer.api.v1.CustomerEventConstants;
 import io.mifos.customer.api.v1.client.CustomerAlreadyExistsException;
 import io.mifos.customer.api.v1.client.CustomerNotFoundException;
@@ -37,6 +38,8 @@ import org.junit.Test;
 import org.springframework.http.MediaType;
 import org.springframework.mock.web.MockMultipartFile;
 
+import java.time.Clock;
+import java.time.LocalDate;
 import java.util.Collections;
 import java.util.List;
 import java.util.stream.Stream;
@@ -188,6 +191,7 @@ public class TestCustomer extends AbstractCustomerTest {
 
     final Customer activatedCustomer = this.customerManager.findCustomer(customer.getIdentifier());
     Assert.assertEquals(Customer.State.ACTIVE.name(), activatedCustomer.getCurrentState());
+    Assert.assertNotNull(activatedCustomer.getApplicationDate());
   }
 
   @Test
@@ -214,6 +218,8 @@ public class TestCustomer extends AbstractCustomerTest {
   @Test
   public void shouldUnlockClient() throws Exception {
     final Customer customer = CustomerGenerator.createRandomCustomer();
+    final String applicationDate = DateConverter.toIsoString(LocalDate.now(Clock.systemUTC()));
+    customer.setApplicationDate(applicationDate);
     this.customerManager.createCustomer(customer);
 
     this.eventRecorder.wait(CustomerEventConstants.POST_CUSTOMER, customer.getIdentifier());
@@ -235,6 +241,7 @@ public class TestCustomer extends AbstractCustomerTest {
 
     final Customer unlockedCustomer = this.customerManager.findCustomer(customer.getIdentifier());
     Assert.assertEquals(Customer.State.ACTIVE.name(), unlockedCustomer.getCurrentState());
+    Assert.assertEquals(applicationDate, unlockedCustomer.getApplicationDate());
   }
 
   @Test
diff --git a/service/src/main/java/io/mifos/customer/service/internal/command/handler/CustomerAggregate.java b/service/src/main/java/io/mifos/customer/service/internal/command/handler/CustomerAggregate.java
index 07e5ebc..3f67a20 100644
--- a/service/src/main/java/io/mifos/customer/service/internal/command/handler/CustomerAggregate.java
+++ b/service/src/main/java/io/mifos/customer/service/internal/command/handler/CustomerAggregate.java
@@ -179,6 +179,9 @@ public class CustomerAggregate {
     }
 
     customerEntity.setCurrentState(Customer.State.ACTIVE.name());
+    if (customerEntity.getApplicationDate() == null) {
+      customerEntity.setApplicationDate(LocalDate.now(Clock.systemUTC()));
+    }
     customerEntity.setLastModifiedBy(UserContextHolder.checkedGetUser());
     customerEntity.setLastModifiedOn(LocalDateTime.now(Clock.systemUTC()));
 
diff --git a/service/src/main/java/io/mifos/customer/service/internal/mapper/CustomerMapper.java b/service/src/main/java/io/mifos/customer/service/internal/mapper/CustomerMapper.java
index c319c01..6cf7569 100644
--- a/service/src/main/java/io/mifos/customer/service/internal/mapper/CustomerMapper.java
+++ b/service/src/main/java/io/mifos/customer/service/internal/mapper/CustomerMapper.java
@@ -45,6 +45,9 @@ public final class CustomerMapper {
     customerEntity.setAssignedOffice(customer.getAssignedOffice());
     customerEntity.setAssignedEmployee(customer.getAssignedEmployee());
     customerEntity.setCurrentState(customer.getCurrentState());
+    if (customer.getApplicationDate() != null) {
+      customerEntity.setApplicationDate(DateConverter.dateFromIsoString(customer.getApplicationDate()));
+    }
     customerEntity.setCreatedBy(UserContextHolder.checkedGetUser());
     customerEntity.setCreatedOn(LocalDateTime.now(Clock.systemUTC()));
     return customerEntity;
@@ -64,6 +67,9 @@ public final class CustomerMapper {
     customer.setAssignedOffice(customerEntity.getAssignedOffice());
     customer.setAssignedEmployee(customerEntity.getAssignedEmployee());
     customer.setCurrentState(customerEntity.getCurrentState());
+    if (customerEntity.getApplicationDate() != null) {
+      customer.setApplicationDate(DateConverter.toIsoString(customerEntity.getApplicationDate()));
+    }
     customer.setCreatedBy(customerEntity.getCreatedBy());
     customer.setCreatedOn(DateConverter.toIsoString(customerEntity.getCreatedOn()));
 
diff --git a/service/src/main/java/io/mifos/customer/service/internal/repository/CustomerEntity.java b/service/src/main/java/io/mifos/customer/service/internal/repository/CustomerEntity.java
index 438eb4a..efa4c88 100644
--- a/service/src/main/java/io/mifos/customer/service/internal/repository/CustomerEntity.java
+++ b/service/src/main/java/io/mifos/customer/service/internal/repository/CustomerEntity.java
@@ -15,6 +15,7 @@
  */
 package io.mifos.customer.service.internal.repository;
 
+import io.mifos.core.mariadb.util.LocalDateConverter;
 import io.mifos.core.mariadb.util.LocalDateTimeConverter;
 
 import javax.persistence.CascadeType;
@@ -29,6 +30,7 @@ import javax.persistence.JoinColumn;
 import javax.persistence.OneToOne;
 import javax.persistence.Table;
 import java.sql.Date;
+import java.time.LocalDate;
 import java.time.LocalDateTime;
 
 @Entity
@@ -63,6 +65,9 @@ public class CustomerEntity {
   private String assignedEmployee;
   @Column(name = "current_state")
   private String currentState;
+  @Column(name = "application_date")
+  @Convert(converter = LocalDateConverter.class)
+  private LocalDate applicationDate;
   @OneToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
   @JoinColumn(name = "address_id")
   private AddressEntity address;
@@ -185,6 +190,14 @@ public class CustomerEntity {
     this.currentState = currentState;
   }
 
+  public LocalDate getApplicationDate() {
+    return this.applicationDate;
+  }
+
+  public void setApplicationDate(final LocalDate applicationDate) {
+    this.applicationDate = applicationDate;
+  }
+
   public AddressEntity getAddress() {
     return this.address;
   }
diff --git a/service/src/main/resources/db/migrations/mariadb/V9__add_application_date_to_customer.sql b/service/src/main/resources/db/migrations/mariadb/V9__add_application_date_to_customer.sql
new file mode 100644
index 0000000..e6fef03
--- /dev/null
+++ b/service/src/main/resources/db/migrations/mariadb/V9__add_application_date_to_customer.sql
@@ -0,0 +1,19 @@
+--
+-- Copyright 2017 The Mifos Initiative.
+--
+-- Licensed under the Apache License, Version 2.0 (the "License");
+-- you may not use this file except in compliance with the License.
+-- You may obtain a copy of the License at
+--
+--    http://www.apache.org/licenses/LICENSE-2.0
+--
+-- Unless required by applicable law or agreed to in writing, software
+-- distributed under the License is distributed on an "AS IS" BASIS,
+-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+-- See the License for the specific language governing permissions and
+-- limitations under the License.
+--
+
+ALTER TABLE maat_customers ADD application_date DATE NULL;
+
+UPDATE maat_customers set application_date = CURDATE() WHERE current_state <> 'PENDING';

-- 
To stop receiving notification emails like this one, please contact
myrle@apache.org.

[fineract-cn-customer] 38/46: Added missing completed check to change document.

Posted by my...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

myrle pushed a commit to branch develop
in repository https://gitbox.apache.org/repos/asf/fineract-cn-customer.git

commit 647cbf1927a30f9a57212c5acebbf842ab5b01a0
Author: Myrle Krantz <my...@apache.org>
AuthorDate: Fri Oct 20 11:45:43 2017 +0200

    Added missing completed check to change document.
---
 .../io/mifos/customer/api/v1/client/CustomerDocumentsManager.java    | 1 +
 component-test/src/main/java/io/mifos/customer/TestDocuments.java    | 5 +++++
 .../customer/service/rest/controller/DocumentsRestController.java    | 2 ++
 3 files changed, 8 insertions(+)

diff --git a/api/src/main/java/io/mifos/customer/api/v1/client/CustomerDocumentsManager.java b/api/src/main/java/io/mifos/customer/api/v1/client/CustomerDocumentsManager.java
index 96fdf02..56ba88a 100644
--- a/api/src/main/java/io/mifos/customer/api/v1/client/CustomerDocumentsManager.java
+++ b/api/src/main/java/io/mifos/customer/api/v1/client/CustomerDocumentsManager.java
@@ -78,6 +78,7 @@ public interface CustomerDocumentsManager {
       consumes = MediaType.APPLICATION_JSON_VALUE
   )
   @ThrowsExceptions({
+      @ThrowsException(status = HttpStatus.CONFLICT, exception = CompletedDocumentCannotBeChangedException.class),
       @ThrowsException(status = HttpStatus.BAD_REQUEST, exception = DocumentValidationException.class)
   })
   void changeDocument(
diff --git a/component-test/src/main/java/io/mifos/customer/TestDocuments.java b/component-test/src/main/java/io/mifos/customer/TestDocuments.java
index 071f247..8b8e6d1 100644
--- a/component-test/src/main/java/io/mifos/customer/TestDocuments.java
+++ b/component-test/src/main/java/io/mifos/customer/TestDocuments.java
@@ -139,6 +139,11 @@ public class TestDocuments extends AbstractCustomerTest {
       Assert.fail("Deleting a page after the document is completed shouldn't be possible.");
     }
     catch (final CompletedDocumentCannotBeChangedException ignored) {}
+    try {
+      customerDocumentsManager.changeDocument(customer.getIdentifier(), customerDocument.getIdentifier(), customerDocument);
+      Assert.fail("Changing a document after it is completed shouldn't be possible.");
+    }
+    catch (final CompletedDocumentCannotBeChangedException ignored) {}
 
 
     logger.info("Check that document can't be uncompleted");
diff --git a/service/src/main/java/io/mifos/customer/service/rest/controller/DocumentsRestController.java b/service/src/main/java/io/mifos/customer/service/rest/controller/DocumentsRestController.java
index 79c6aae..c0db7c8 100644
--- a/service/src/main/java/io/mifos/customer/service/rest/controller/DocumentsRestController.java
+++ b/service/src/main/java/io/mifos/customer/service/rest/controller/DocumentsRestController.java
@@ -125,6 +125,8 @@ public class DocumentsRestController {
     throwIfCustomerNotExists(customerIdentifier);
     throwIfCustomerDocumentNotExists(customerIdentifier, documentIdentifier);
 
+    throwIfDocumentCompleted(customerIdentifier, documentIdentifier);
+
     if (!instance.getIdentifier().equals(documentIdentifier))
       throw ServiceException.badRequest("Document identifier in request body must match document identifier in request path.");
 

-- 
To stop receiving notification emails like this one, please contact
myrle@apache.org.

[fineract-cn-customer] 25/46: fixed NPE when checking four eye task

Posted by my...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

myrle pushed a commit to branch develop
in repository https://gitbox.apache.org/repos/asf/fineract-cn-customer.git

commit 48bfd66f9f7851aad38ac0a04bd4a73c35f6a8da
Author: mgeiss <mg...@mifos.org>
AuthorDate: Thu Sep 21 13:11:44 2017 +0200

    fixed NPE when checking four eye task
---
 .../java/io/mifos/customer/TestTaskInstance.java   | 33 +++++++++++++++++++++-
 .../rest/controller/CustomerRestController.java    | 13 +++++----
 2 files changed, 39 insertions(+), 7 deletions(-)

diff --git a/component-test/src/main/java/io/mifos/customer/TestTaskInstance.java b/component-test/src/main/java/io/mifos/customer/TestTaskInstance.java
index edce92c..53a6ba6 100644
--- a/component-test/src/main/java/io/mifos/customer/TestTaskInstance.java
+++ b/component-test/src/main/java/io/mifos/customer/TestTaskInstance.java
@@ -120,6 +120,10 @@ public class TestTaskInstance extends AbstractCustomerTest {
     final List<TaskDefinition> tasksForCustomer = this.customerManager.findTasksForCustomer(randomCustomer.getIdentifier(), false);
 
     Assert.assertEquals(1, tasksForCustomer.size());
+
+    taskDefinition.setPredefined(false);
+    this.customerManager.updateTask(taskDefinition.getIdentifier(), taskDefinition);
+    this.eventRecorder.wait(CustomerEventConstants.PUT_TASK, taskDefinition.getIdentifier());
   }
 
   @Test
@@ -197,7 +201,34 @@ public class TestTaskInstance extends AbstractCustomerTest {
     this.eventRecorder.wait(CustomerEventConstants.PUT_TASK, customTask1.getIdentifier());
 
     customTask2.setPredefined(false);
-    this.customerManager.updateTask(customTask2.getIdentifier(), customTask1);
+    this.customerManager.updateTask(customTask2.getIdentifier(), customTask2);
     this.eventRecorder.wait(CustomerEventConstants.PUT_TASK, customTask2.getIdentifier());
   }
+
+  @Test(expected = TaskExecutionException.class)
+  public void shouldNotProceedFourEyesWrongSigner() throws Exception {
+    final TaskDefinition fourEyesTask = new TaskDefinition();
+    fourEyesTask.setIdentifier("4-eyes-task-1");
+    fourEyesTask.setType(TaskDefinition.Type.FOUR_EYES.name());
+    fourEyesTask.setName("Do the barrel roll");
+    fourEyesTask.setCommands(
+        TaskDefinition.Command.ACTIVATE.name()
+    );
+    fourEyesTask.setPredefined(Boolean.TRUE);
+    fourEyesTask.setMandatory(Boolean.TRUE);
+
+    this.customerManager.createTask(fourEyesTask);
+    this.eventRecorder.wait(CustomerEventConstants.POST_TASK, fourEyesTask.getIdentifier());
+
+    final Customer randomCustomer = CustomerGenerator.createRandomCustomer();
+    this.customerManager.createCustomer(randomCustomer);
+    this.eventRecorder.wait(CustomerEventConstants.POST_CUSTOMER, randomCustomer.getIdentifier());
+
+    this.customerManager.taskForCustomerExecuted(randomCustomer.getIdentifier(), fourEyesTask.getIdentifier());
+    this.eventRecorder.wait(CustomerEventConstants.PUT_CUSTOMER, randomCustomer.getIdentifier());
+
+    fourEyesTask.setPredefined(false);
+    this.customerManager.updateTask(fourEyesTask.getIdentifier(), fourEyesTask);
+    this.eventRecorder.wait(CustomerEventConstants.PUT_TASK, fourEyesTask.getIdentifier());
+  }
 }
diff --git a/service/src/main/java/io/mifos/customer/service/rest/controller/CustomerRestController.java b/service/src/main/java/io/mifos/customer/service/rest/controller/CustomerRestController.java
index c3c6e94..d07124e 100644
--- a/service/src/main/java/io/mifos/customer/service/rest/controller/CustomerRestController.java
+++ b/service/src/main/java/io/mifos/customer/service/rest/controller/CustomerRestController.java
@@ -307,10 +307,12 @@ public class CustomerRestController {
   @ResponseBody
   ResponseEntity<Void> taskForCustomerExecuted(@PathVariable("identifier") final String identifier,
                                                @PathVariable("taskIdentifier") final String taskIdentifier) {
-    if (this.customerService.customerExists(identifier)) {
-      if (this.taskService.taskDefinitionExists(taskIdentifier)) {
-        final TaskDefinition taskDefinition = this.taskService.findByIdentifier(taskIdentifier).get();
-        final Customer customer;
+    final Optional<Customer> optionalCustomer = this.customerService.findCustomer(identifier);
+    if (optionalCustomer.isPresent()) {
+      final Customer customer = optionalCustomer.get();
+      final Optional<TaskDefinition> optionalTaskDefinition = this.taskService.findByIdentifier(taskIdentifier);
+      if (optionalTaskDefinition.isPresent()) {
+        final TaskDefinition taskDefinition = optionalTaskDefinition.get();
         switch (TaskDefinition.Type.valueOf(taskDefinition.getType())) {
           case ID_CARD:
             final List<IdentificationCard> identificationCards = this.customerService.fetchIdentificationCardsByCustomer(identifier);
@@ -319,8 +321,7 @@ public class CustomerRestController {
             }
             break;
           case FOUR_EYES:
-            customer = this.customerService.findCustomer(identifier).get();
-            if (customer.getAssignedEmployee().equals(UserContextHolder.checkedGetUser())) {
+            if (customer.getCreatedBy().equals(UserContextHolder.checkedGetUser())) {
               throw ServiceException.conflict("Signing user must be different than creator.");
             }
             break;

-- 
To stop receiving notification emails like this one, please contact
myrle@apache.org.

[fineract-cn-customer] 16/46: Merge pull request #5 from markusgeiss/develop

Posted by my...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

myrle pushed a commit to branch develop
in repository https://gitbox.apache.org/repos/asf/fineract-cn-customer.git

commit 81c0404f0bac5e0c3178d56fee371662deb2e8e8
Merge: 3ed6029 f078702
Author: Markus Geiss <ma...@kuelap.io>
AuthorDate: Wed Sep 6 13:54:28 2017 +0200

    Merge pull request #5 from markusgeiss/develop
    
    added filtering for task based on command

 .../service/internal/command/handler/CustomerAggregate.java         | 6 +++---
 .../customer/service/internal/command/handler/TaskAggregate.java    | 3 ++-
 2 files changed, 5 insertions(+), 4 deletions(-)

-- 
To stop receiving notification emails like this one, please contact
myrle@apache.org.

[fineract-cn-customer] 35/46: Implementing document descriptions for customers.

Posted by my...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

myrle pushed a commit to branch develop
in repository https://gitbox.apache.org/repos/asf/fineract-cn-customer.git

commit 2a84c2a2ba0f3c101b15aba9036f655d9259a4a6
Author: Myrle Krantz <my...@apache.org>
AuthorDate: Fri Oct 20 11:13:27 2017 +0200

    Implementing document descriptions for customers.
---
 api/build.gradle                                   |  4 ++
 .../customer/api/v1/domain/CustomerDocument.java   | 18 +++++-
 .../api/v1/domain/CustomerDocumentTest.java        | 70 ++++++++++++++++++++++
 .../io/mifos/customer/AbstractCustomerTest.java    | 18 ++++++
 .../main/java/io/mifos/customer/TestDocuments.java | 36 +++++------
 .../customer/util/CustomerDocumentGenerator.java   |  1 +
 .../service/internal/mapper/DocumentMapper.java    |  2 +
 .../internal/repository/DocumentEntity.java        | 14 ++++-
 .../mariadb/V8__documents_description.sql          | 17 ++++++
 9 files changed, 159 insertions(+), 21 deletions(-)

diff --git a/api/build.gradle b/api/build.gradle
index 7bce588..1e1726e 100644
--- a/api/build.gradle
+++ b/api/build.gradle
@@ -22,6 +22,10 @@ dependencies {
             [group: 'io.github.openfeign.form', name: 'feign-form', version: '2.1.0'],
             [group: 'io.github.openfeign.form', name: 'feign-form-spring', version: '2.1.0']
     )
+
+    testCompile(
+            [group: 'io.mifos.core', name: 'test', version: versions.frameworktest],
+    )
 }
 
 publishing {
diff --git a/api/src/main/java/io/mifos/customer/api/v1/domain/CustomerDocument.java b/api/src/main/java/io/mifos/customer/api/v1/domain/CustomerDocument.java
index e6d5857..529ad76 100644
--- a/api/src/main/java/io/mifos/customer/api/v1/domain/CustomerDocument.java
+++ b/api/src/main/java/io/mifos/customer/api/v1/domain/CustomerDocument.java
@@ -16,6 +16,7 @@
 package io.mifos.customer.api.v1.domain;
 
 import io.mifos.core.lang.validation.constraints.ValidIdentifier;
+import org.hibernate.validator.constraints.Length;
 
 import java.util.Objects;
 
@@ -26,6 +27,9 @@ public class CustomerDocument {
   @ValidIdentifier
   private String identifier;
 
+  @Length(max = 4096)
+  private String description;
+
   private boolean completed;
   private String createdBy;
   private String createdOn;
@@ -41,6 +45,14 @@ public class CustomerDocument {
     this.identifier = identifier;
   }
 
+  public String getDescription() {
+    return description;
+  }
+
+  public void setDescription(String description) {
+    this.description = description;
+  }
+
   public boolean isCompleted() {
     return completed;
   }
@@ -71,18 +83,20 @@ public class CustomerDocument {
     if (o == null || getClass() != o.getClass()) return false;
     CustomerDocument that = (CustomerDocument) o;
     return completed == that.completed &&
-        Objects.equals(identifier, that.identifier);
+        Objects.equals(identifier, that.identifier) &&
+        Objects.equals(description, that.description);
   }
 
   @Override
   public int hashCode() {
-    return Objects.hash(identifier, completed);
+    return Objects.hash(identifier, description, completed);
   }
 
   @Override
   public String toString() {
     return "CustomerDocument{" +
         "identifier='" + identifier + '\'' +
+        ", description='" + description + '\'' +
         ", completed=" + completed +
         ", createdBy='" + createdBy + '\'' +
         ", createdOn='" + createdOn + '\'' +
diff --git a/api/src/test/java/io/mifos/customer/api/v1/domain/CustomerDocumentTest.java b/api/src/test/java/io/mifos/customer/api/v1/domain/CustomerDocumentTest.java
new file mode 100644
index 0000000..41007bc
--- /dev/null
+++ b/api/src/test/java/io/mifos/customer/api/v1/domain/CustomerDocumentTest.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright 2017 The Mifos Initiative.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.mifos.customer.api.v1.domain;
+
+import io.mifos.core.test.domain.ValidationTest;
+import io.mifos.core.test.domain.ValidationTestCase;
+import org.apache.commons.lang3.RandomStringUtils;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+import java.util.ArrayList;
+import java.util.Collection;
+
+/**
+ * @author Myrle Krantz
+ */
+@RunWith(Parameterized.class)
+public class CustomerDocumentTest extends ValidationTest<CustomerDocument> {
+
+  public CustomerDocumentTest(final ValidationTestCase<CustomerDocument> testCase)
+  {
+    super(testCase);
+  }
+
+  @Override
+  protected CustomerDocument createValidTestSubject() {
+    final CustomerDocument ret = new CustomerDocument();
+    ret.setIdentifier(RandomStringUtils.randomAlphanumeric(8));
+    ret.setCompleted(false);
+    ret.setCreatedOn(null);
+    ret.setCreatedBy(null);
+    ret.setDescription(RandomStringUtils.random(5));
+    return ret;
+  }
+
+  @Parameterized.Parameters
+  public static Collection testCases() {
+    final Collection<ValidationTestCase> ret = new ArrayList<>();
+
+    ret.add(new ValidationTestCase<CustomerDocument>("valid"));
+    ret.add(new ValidationTestCase<CustomerDocument>("null description")
+        .adjustment(x -> x.setDescription(null))
+        .valid(true));
+    ret.add(new ValidationTestCase<CustomerDocument>("minimum length description")
+        .adjustment(x -> x.setDescription(RandomStringUtils.random(0)))
+        .valid(true));
+    ret.add(new ValidationTestCase<CustomerDocument>("maximum length description")
+        .adjustment(x -> x.setDescription(RandomStringUtils.random(4096)))
+        .valid(true));
+    ret.add(new ValidationTestCase<CustomerDocument>("too long description")
+        .adjustment(x -> x.setDescription(RandomStringUtils.random(4097)))
+        .valid(false));
+
+    return ret;
+  }
+
+}
diff --git a/component-test/src/main/java/io/mifos/customer/AbstractCustomerTest.java b/component-test/src/main/java/io/mifos/customer/AbstractCustomerTest.java
index 2649c13..1435366 100644
--- a/component-test/src/main/java/io/mifos/customer/AbstractCustomerTest.java
+++ b/component-test/src/main/java/io/mifos/customer/AbstractCustomerTest.java
@@ -29,10 +29,14 @@ 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;
@@ -46,6 +50,7 @@ import org.springframework.test.context.junit4.SpringRunner;
     classes = {AbstractCustomerTest.TestConfiguration.class})
 public class AbstractCustomerTest extends SuiteTestEnvironment {
   static final String TEST_USER = "maatkare";
+  private static final String LOGGER_NAME = "test-logger";
 
   @Configuration
   @EnableEventRecording
@@ -61,6 +66,11 @@ public class AbstractCustomerTest extends SuiteTestEnvironment {
     public TestConfiguration() {
       super();
     }
+
+    @Bean(name = LOGGER_NAME)
+    public Logger logger() {
+      return LoggerFactory.getLogger(LOGGER_NAME);
+    }
   }
 
   @ClassRule
@@ -70,15 +80,23 @@ public class AbstractCustomerTest extends SuiteTestEnvironment {
   public final TenantApplicationSecurityEnvironmentTestRule tenantApplicationSecurityEnvironment
       = new TenantApplicationSecurityEnvironmentTestRule(testEnvironment, this::waitForInitialize);
 
+  @SuppressWarnings("SpringAutowiredFieldsWarningInspection")
   @Autowired
   CustomerManager customerManager;
 
+  @SuppressWarnings("SpringAutowiredFieldsWarningInspection")
   @Autowired
   CustomerDocumentsManager customerDocumentsManager;
 
+  @SuppressWarnings({"SpringAutowiredFieldsWarningInspection", "SpringJavaAutowiringInspection"})
   @Autowired
   EventRecorder eventRecorder;
 
+  @SuppressWarnings("SpringAutowiredFieldsWarningInspection")
+  @Autowired
+  @Qualifier(LOGGER_NAME)
+  Logger logger;
+
   private AutoUserContext userContext;
 
   public AbstractCustomerTest() {
diff --git a/component-test/src/main/java/io/mifos/customer/TestDocuments.java b/component-test/src/main/java/io/mifos/customer/TestDocuments.java
index 7c5f4d2..fa44409 100644
--- a/component-test/src/main/java/io/mifos/customer/TestDocuments.java
+++ b/component-test/src/main/java/io/mifos/customer/TestDocuments.java
@@ -45,24 +45,24 @@ public class TestDocuments extends AbstractCustomerTest {
 
   @Test
   public void shouldUploadEditThenCompleteDocument() throws InterruptedException, IOException {
-    //Prepare test.
+    logger.info("Prepare test");
     final Customer customer = CustomerGenerator.createRandomCustomer();
     customerManager.createCustomer(customer);
-    eventRecorder.wait(CustomerEventConstants.POST_CUSTOMER, customer.getIdentifier());
+    Assert.assertTrue(eventRecorder.wait(CustomerEventConstants.POST_CUSTOMER, customer.getIdentifier()));
 
 
-    //Check that document "stub" can be created.
+    logger.info("Check that document \"stub\" can be created");
     final CustomerDocument customerDocument = CustomerDocumentGenerator.createRandomCustomerDocument();
     customerDocumentsManager.createDocument(customer.getIdentifier(), customerDocument.getIdentifier(), customerDocument);
-    eventRecorder.wait(CustomerEventConstants.POST_DOCUMENT,
-        new DocumentEvent(customer.getIdentifier(), customerDocument.getIdentifier()));
+    Assert.assertTrue(eventRecorder.wait(CustomerEventConstants.POST_DOCUMENT,
+        new DocumentEvent(customer.getIdentifier(), customerDocument.getIdentifier())));
 
     final CustomerDocument createdCustomerDocument = customerDocumentsManager.getDocument(
         customer.getIdentifier(), customerDocument.getIdentifier());
     Assert.assertEquals(customerDocument, createdCustomerDocument);
 
 
-    //Check that pages can be created.
+    logger.info("Check that pages can be created");
     for (int i = 0; i < 10; i++) {
       createDocumentPage(customer.getIdentifier(), customerDocument.getIdentifier(), i);
     }
@@ -73,10 +73,10 @@ public class TestDocuments extends AbstractCustomerTest {
     Assert.assertEquals(expectedPageNumbers, pageNumbers);
 
 
-    //Check that a page can be deleted.
+    logger.info("Check that a page can be deleted");
     customerDocumentsManager.deleteDocumentPage(customer.getIdentifier(), customerDocument.getIdentifier(), 9);
-    eventRecorder.wait(CustomerEventConstants.DELETE_DOCUMENT_PAGE,
-        new DocumentPageEvent(customer.getIdentifier(), customerDocument.getIdentifier(), 9));
+    Assert.assertTrue(eventRecorder.wait(CustomerEventConstants.DELETE_DOCUMENT_PAGE,
+        new DocumentPageEvent(customer.getIdentifier(), customerDocument.getIdentifier(), 9)));
 
     final List<Integer> changedPageNumbers = customerDocumentsManager.getDocumentPageNumbers(
         customer.getIdentifier(), customerDocument.getIdentifier());
@@ -90,10 +90,10 @@ public class TestDocuments extends AbstractCustomerTest {
     catch (final NotFoundException ignored) {}
 
 
-    //Check that a document which is missing pages cannot be completed
+    logger.info("Check that a document which is missing pages cannot be completed");
     customerDocumentsManager.deleteDocumentPage(customer.getIdentifier(), customerDocument.getIdentifier(), 2);
-    eventRecorder.wait(CustomerEventConstants.DELETE_DOCUMENT_PAGE,
-        new DocumentPageEvent(customer.getIdentifier(), customerDocument.getIdentifier(), 2));
+    Assert.assertTrue(eventRecorder.wait(CustomerEventConstants.DELETE_DOCUMENT_PAGE,
+        new DocumentPageEvent(customer.getIdentifier(), customerDocument.getIdentifier(), 2)));
 
     try {
       customerDocumentsManager.completeDocument(customer.getIdentifier(), customerDocument.getIdentifier(), true);
@@ -104,11 +104,11 @@ public class TestDocuments extends AbstractCustomerTest {
     createDocumentPage(customer.getIdentifier(), customerDocument.getIdentifier(), 2);
 
 
-    //Check that a valid document can be completed.
+    logger.info("Check that a valid document can be completed");
     final TimeStampChecker timeStampChecker = TimeStampChecker.roughlyNow();
     customerDocumentsManager.completeDocument(customer.getIdentifier(), customerDocument.getIdentifier(), true);
-    eventRecorder.wait(CustomerEventConstants.POST_DOCUMENT_COMPLETE,
-        new DocumentPageEvent(customer.getIdentifier(), customerDocument.getIdentifier(), 9));
+    Assert.assertTrue(eventRecorder.wait(CustomerEventConstants.POST_DOCUMENT_COMPLETE,
+        new DocumentEvent(customer.getIdentifier(), customerDocument.getIdentifier())));
 
     final CustomerDocument completedDocument = customerDocumentsManager.getDocument(
         customer.getIdentifier(), customerDocument.getIdentifier());
@@ -116,7 +116,7 @@ public class TestDocuments extends AbstractCustomerTest {
     timeStampChecker.assertCorrect(completedDocument.getCreatedOn());
 
 
-    //Check that document can't be changed after completion
+    logger.info("Check that document can't be changed after completion");
     try {
       createDocumentPage(customer.getIdentifier(), customerDocument.getIdentifier(), 9);
       Assert.fail("Adding another page after the document is completed shouldn't be possible.");
@@ -129,7 +129,7 @@ public class TestDocuments extends AbstractCustomerTest {
     catch (final CompletedDocumentCannotBeChangedException ignored) {}
 
 
-    //Check that document can't be uncompleted.
+    logger.info("Check that document can't be uncompleted");
     try {
       customerDocumentsManager.completeDocument(customer.getIdentifier(), customerDocument.getIdentifier(), false);
       Assert.fail("It shouldn't be possible to change a document from completed to uncompleted.");
@@ -137,7 +137,7 @@ public class TestDocuments extends AbstractCustomerTest {
     catch (final CompletedDocumentCannotBeChangedException ignored) {}
 
 
-    //Check that document is in the list.
+    logger.info("Check that document is in the list");
     final List<CustomerDocument> documents = customerDocumentsManager.getDocuments(customer.getIdentifier());
     final boolean documentIsInList = documents.stream().anyMatch(x ->
         (x.getIdentifier().equals(customerDocument.getIdentifier())) &&
diff --git a/component-test/src/main/java/io/mifos/customer/util/CustomerDocumentGenerator.java b/component-test/src/main/java/io/mifos/customer/util/CustomerDocumentGenerator.java
index 4a7ee13..f82fb07 100644
--- a/component-test/src/main/java/io/mifos/customer/util/CustomerDocumentGenerator.java
+++ b/component-test/src/main/java/io/mifos/customer/util/CustomerDocumentGenerator.java
@@ -27,6 +27,7 @@ public final class CustomerDocumentGenerator {
   public static CustomerDocument createRandomCustomerDocument() {
     final CustomerDocument ret = new CustomerDocument();
     ret.setIdentifier(RandomStringUtils.randomAlphanumeric(8));
+    ret.setDescription(RandomStringUtils.randomAlphanumeric(4096));
     return ret;
   }
 }
diff --git a/service/src/main/java/io/mifos/customer/service/internal/mapper/DocumentMapper.java b/service/src/main/java/io/mifos/customer/service/internal/mapper/DocumentMapper.java
index 6ea88e9..5bcf41e 100644
--- a/service/src/main/java/io/mifos/customer/service/internal/mapper/DocumentMapper.java
+++ b/service/src/main/java/io/mifos/customer/service/internal/mapper/DocumentMapper.java
@@ -55,6 +55,7 @@ public class DocumentMapper {
     ret.setCreatedBy(documentEntity.getCreatedBy());
     ret.setCreatedOn(DateConverter.toIsoString(documentEntity.getCreatedOn()));
     ret.setIdentifier(documentEntity.getIdentifier());
+    ret.setDescription(documentEntity.getDescription());
     return ret;
   }
 
@@ -65,6 +66,7 @@ public class DocumentMapper {
     ret.setCreatedBy(UserContextHolder.checkedGetUser());
     ret.setCreatedOn(LocalDateTime.now(Clock.systemUTC()));
     ret.setIdentifier(customerDocument.getIdentifier());
+    ret.setDescription(customerDocument.getDescription());
     return ret;
   }
 }
diff --git a/service/src/main/java/io/mifos/customer/service/internal/repository/DocumentEntity.java b/service/src/main/java/io/mifos/customer/service/internal/repository/DocumentEntity.java
index ae2513f..60dbd26 100644
--- a/service/src/main/java/io/mifos/customer/service/internal/repository/DocumentEntity.java
+++ b/service/src/main/java/io/mifos/customer/service/internal/repository/DocumentEntity.java
@@ -37,9 +37,13 @@ public class DocumentEntity {
   @JoinColumn(name = "customer_id")
   private CustomerEntity customer;
 
-  @Column(name = "identifier")
+  @Column(name = "identifier", nullable = false)
   private String identifier;
 
+  @SuppressWarnings("DefaultAnnotationParam")
+  @Column(name = "description", nullable = true)
+  private String description;
+
   @Column(name = "is_completed", nullable = false)
   private Boolean completed;
 
@@ -77,6 +81,14 @@ public class DocumentEntity {
     this.identifier = identifier;
   }
 
+  public String getDescription() {
+    return description;
+  }
+
+  public void setDescription(String description) {
+    this.description = description;
+  }
+
   public Boolean getCompleted() {
     return completed;
   }
diff --git a/service/src/main/resources/db/migrations/mariadb/V8__documents_description.sql b/service/src/main/resources/db/migrations/mariadb/V8__documents_description.sql
new file mode 100644
index 0000000..a9cd216
--- /dev/null
+++ b/service/src/main/resources/db/migrations/mariadb/V8__documents_description.sql
@@ -0,0 +1,17 @@
+--
+-- Copyright 2017 The Mifos Initiative.
+--
+-- Licensed under the Apache License, Version 2.0 (the "License");
+-- you may not use this file except in compliance with the License.
+-- You may obtain a copy of the License at
+--
+--    http://www.apache.org/licenses/LICENSE-2.0
+--
+-- Unless required by applicable law or agreed to in writing, software
+-- distributed under the License is distributed on an "AS IS" BASIS,
+-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+-- See the License for the specific language governing permissions and
+-- limitations under the License.
+--
+
+ALTER TABLE maat_documents ADD description VARCHAR(4096) NULL;
\ No newline at end of file

-- 
To stop receiving notification emails like this one, please contact
myrle@apache.org.

[fineract-cn-customer] 29/46: using valid identifier for domain objects

Posted by my...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

myrle pushed a commit to branch develop
in repository https://gitbox.apache.org/repos/asf/fineract-cn-customer.git

commit e86d2a166ab0d794a3f9a28da00df96e2960d2c0
Author: mgeiss <mg...@mifos.org>
AuthorDate: Thu Sep 28 12:36:40 2017 +0200

    using valid identifier for domain objects
---
 .../main/java/io/mifos/customer/catalog/api/v1/domain/Catalog.java   | 3 ++-
 api/src/main/java/io/mifos/customer/catalog/api/v1/domain/Field.java | 3 ++-
 api/src/main/java/io/mifos/customer/catalog/api/v1/domain/Value.java | 5 +++--
 3 files changed, 7 insertions(+), 4 deletions(-)

diff --git a/api/src/main/java/io/mifos/customer/catalog/api/v1/domain/Catalog.java b/api/src/main/java/io/mifos/customer/catalog/api/v1/domain/Catalog.java
index cf0a8e0..81f0610 100644
--- a/api/src/main/java/io/mifos/customer/catalog/api/v1/domain/Catalog.java
+++ b/api/src/main/java/io/mifos/customer/catalog/api/v1/domain/Catalog.java
@@ -15,6 +15,7 @@
  */
 package io.mifos.customer.catalog.api.v1.domain;
 
+import io.mifos.core.lang.validation.constraints.ValidIdentifier;
 import org.hibernate.validator.constraints.NotEmpty;
 
 import javax.validation.Valid;
@@ -22,7 +23,7 @@ import java.util.List;
 
 public class Catalog {
 
-  @NotEmpty
+  @ValidIdentifier
   private String identifier;
   @NotEmpty
   private String name;
diff --git a/api/src/main/java/io/mifos/customer/catalog/api/v1/domain/Field.java b/api/src/main/java/io/mifos/customer/catalog/api/v1/domain/Field.java
index 61aebd4..d758b95 100644
--- a/api/src/main/java/io/mifos/customer/catalog/api/v1/domain/Field.java
+++ b/api/src/main/java/io/mifos/customer/catalog/api/v1/domain/Field.java
@@ -15,6 +15,7 @@
  */
 package io.mifos.customer.catalog.api.v1.domain;
 
+import io.mifos.core.lang.validation.constraints.ValidIdentifier;
 import org.hibernate.validator.constraints.NotEmpty;
 
 import javax.validation.Valid;
@@ -31,7 +32,7 @@ public class Field {
     MULTI_SELECTION
   }
 
-  @NotEmpty
+  @ValidIdentifier
   private String identifier;
   @NotNull
   private DataType dataType;
diff --git a/api/src/main/java/io/mifos/customer/catalog/api/v1/domain/Value.java b/api/src/main/java/io/mifos/customer/catalog/api/v1/domain/Value.java
index c6e688d..03d486f 100644
--- a/api/src/main/java/io/mifos/customer/catalog/api/v1/domain/Value.java
+++ b/api/src/main/java/io/mifos/customer/catalog/api/v1/domain/Value.java
@@ -15,13 +15,14 @@
  */
 package io.mifos.customer.catalog.api.v1.domain;
 
+import io.mifos.core.lang.validation.constraints.ValidIdentifier;
 import org.hibernate.validator.constraints.NotEmpty;
 
 public class Value {
 
-  @NotEmpty
+  @ValidIdentifier
   private String catalogIdentifier;
-  @NotEmpty
+  @ValidIdentifier
   private String fieldIdentifier;
   @NotEmpty
   private String value;

-- 
To stop receiving notification emails like this one, please contact
myrle@apache.org.

[fineract-cn-customer] 10/46: Merge pull request #2 from markusgeiss/develop

Posted by my...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

myrle pushed a commit to branch develop
in repository https://gitbox.apache.org/repos/asf/fineract-cn-customer.git

commit 73def979f891c55e38b2b68de13177e8025d9fa3
Merge: fd4bf02 aaa22f2
Author: Markus Geiss <ma...@kuelap.io>
AuthorDate: Tue Sep 5 13:56:17 2017 +0200

    Merge pull request #2 from markusgeiss/develop
    
    added ability to manage payroll distribution per customer

 .../customer/api/v1/CustomerEventConstants.java    |   3 +
 .../customer/api/v1/client/CustomerManager.java    |  42 ++++++-
 ...=> PayrollDistributionValidationException.java} |   4 +-
 .../api/v1/client/ScanAlreadyExistsException.java  |   2 +-
 .../api/v1/client/ScanNotFoundException.java       |   2 +-
 .../api/v1/domain/IdentificationCardScan.java      |   2 +-
 .../customer/api/v1/domain/PayrollAllocation.java  |  58 ++++++++++
 .../api/v1/domain/PayrollDistribution.java         |  86 ++++++++++++++
 .../io/mifos/customer/api/v1/events/ScanEvent.java |   2 +-
 .../main/java/io/mifos/customer/TestCustomer.java  |  13 ++-
 .../io/mifos/customer/TestIdentificationCards.java |   2 +-
 .../io/mifos/customer/TestPayrollDistribution.java | 115 ++++++++++++++++++
 .../io/mifos/customer/catalog/TestCatalog.java     |   7 +-
 .../customer/listener/CustomerEventListener.java   |   9 ++
 .../java/io/mifos/customer/util/ScanGenerator.java |   2 +-
 .../resources/{logback.xml => logback-test.xml}    |   0
 .../internal/command/handler/CatalogAggregate.java |   4 +-
 .../internal/service/FieldValueValidator.java      |   2 +-
 .../rest/controller/CatalogRestController.java     |   2 +-
 .../CreateIdentificationCardScanCommand.java       |   2 +-
 .../DeleteIdentificationCardScanCommand.java       |   2 +-
 .../command/SetPayrollDistributionCommand.java     |  38 ++++++
 .../command/handler/CustomerAggregate.java         | 111 +++++++++++++++++-
 .../mapper/IdentificationCardScanMapper.java       |   2 +-
 .../internal/mapper/PayrollDistributionMapper.java |  55 +++++++++
 .../internal/mapper/TaskInstanceMapper.java        |   2 +-
 .../repository/IdentificationCardScanEntity.java   |  15 ++-
 .../IdentificationCardScanRepository.java          |   2 +-
 .../repository/PayrollAllocationEntity.java        |  89 ++++++++++++++
 .../repository/PayrollAllocationRepository.java    |   9 +-
 .../repository/PayrollDistributionEntity.java      | 128 +++++++++++++++++++++
 .../repository/PayrollDistributionRepository.java  |  11 +-
 .../internal/repository/PortraitEntity.java        |  11 +-
 .../service/internal/service/CustomerService.java  |  44 ++++++-
 .../service/internal/service/TaskService.java      |   2 +-
 .../rest/controller/CustomerRestController.java    |  75 +++++++++++-
 .../mariadb/V5__add_payroll_distributions.sql      |  39 +++++++
 37 files changed, 948 insertions(+), 46 deletions(-)

-- 
To stop receiving notification emails like this one, please contact
myrle@apache.org.

[fineract-cn-customer] 31/46: Fix issue not updating fields by flushing field value repository after delete Throw conflict instead of bad request when field or catalog is in use Adapted tests to use new exceptions

Posted by my...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

myrle pushed a commit to branch develop
in repository https://gitbox.apache.org/repos/asf/fineract-cn-customer.git

commit a280d78da30683843bd0c24f6af4771c0151d69d
Author: Mark <ma...@gmail.com>
AuthorDate: Fri Sep 29 17:56:34 2017 +0200

    Fix issue not updating fields by flushing field value repository after delete
    Throw conflict instead of bad request when field or catalog is in use
    Adapted tests to use new exceptions
---
 .../api/v1/client/CatalogAlreadyInUseException.java   | 19 +++++++++++++++++++
 .../catalog/api/v1/client/CatalogManager.java         |  9 ++++++---
 .../api/v1/client/FieldAlreadyInUseException.java     | 19 +++++++++++++++++++
 .../java/io/mifos/customer/catalog/TestCatalog.java   |  6 ++++--
 .../rest/controller/CatalogRestController.java        |  6 +++---
 .../internal/command/handler/CustomerAggregate.java   |  1 +
 6 files changed, 52 insertions(+), 8 deletions(-)

diff --git a/api/src/main/java/io/mifos/customer/catalog/api/v1/client/CatalogAlreadyInUseException.java b/api/src/main/java/io/mifos/customer/catalog/api/v1/client/CatalogAlreadyInUseException.java
new file mode 100644
index 0000000..7136606
--- /dev/null
+++ b/api/src/main/java/io/mifos/customer/catalog/api/v1/client/CatalogAlreadyInUseException.java
@@ -0,0 +1,19 @@
+/*
+ * Copyright 2017 The Mifos Initiative.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.mifos.customer.catalog.api.v1.client;
+
+public class CatalogAlreadyInUseException extends RuntimeException {
+}
diff --git a/api/src/main/java/io/mifos/customer/catalog/api/v1/client/CatalogManager.java b/api/src/main/java/io/mifos/customer/catalog/api/v1/client/CatalogManager.java
index edf7341..a434f81 100644
--- a/api/src/main/java/io/mifos/customer/catalog/api/v1/client/CatalogManager.java
+++ b/api/src/main/java/io/mifos/customer/catalog/api/v1/client/CatalogManager.java
@@ -73,7 +73,8 @@ public interface CatalogManager {
   )
   @ThrowsExceptions({
       @ThrowsException(status = HttpStatus.NOT_FOUND, exception = CatalogNotFoundException.class),
-      @ThrowsException(status = HttpStatus.BAD_REQUEST, exception = CatalogValidationException.class)
+      @ThrowsException(status = HttpStatus.BAD_REQUEST, exception = CatalogValidationException.class),
+      @ThrowsException(status = HttpStatus.CONFLICT, exception = CatalogAlreadyInUseException.class)
   })
   void deleteCatalog(@PathVariable("identifier") final String identifier);
 
@@ -85,7 +86,8 @@ public interface CatalogManager {
   )
   @ThrowsExceptions({
       @ThrowsException(status = HttpStatus.NOT_FOUND, exception = CatalogNotFoundException.class),
-      @ThrowsException(status = HttpStatus.BAD_REQUEST, exception = CatalogValidationException.class)
+      @ThrowsException(status = HttpStatus.BAD_REQUEST, exception = CatalogValidationException.class),
+      @ThrowsException(status = HttpStatus.CONFLICT, exception = FieldAlreadyInUseException.class)
   })
   void updateField(@PathVariable("catalogIdentifier") final String catalogIdentifier,
                    @PathVariable("fieldIdentifier") final String fieldIdentifier,
@@ -99,7 +101,8 @@ public interface CatalogManager {
   )
   @ThrowsExceptions({
       @ThrowsException(status = HttpStatus.NOT_FOUND, exception = CatalogNotFoundException.class),
-      @ThrowsException(status = HttpStatus.BAD_REQUEST, exception = CatalogValidationException.class)
+      @ThrowsException(status = HttpStatus.BAD_REQUEST, exception = CatalogValidationException.class),
+      @ThrowsException(status = HttpStatus.CONFLICT, exception = FieldAlreadyInUseException.class)
   })
   void deleteField(@PathVariable("catalogIdentifier") final String catalogIdentifier,
                    @PathVariable("fieldIdentifier") final String fieldIdentifier);
diff --git a/api/src/main/java/io/mifos/customer/catalog/api/v1/client/FieldAlreadyInUseException.java b/api/src/main/java/io/mifos/customer/catalog/api/v1/client/FieldAlreadyInUseException.java
new file mode 100644
index 0000000..968e1e7
--- /dev/null
+++ b/api/src/main/java/io/mifos/customer/catalog/api/v1/client/FieldAlreadyInUseException.java
@@ -0,0 +1,19 @@
+/*
+ * Copyright 2017 The Mifos Initiative.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.mifos.customer.catalog.api.v1.client;
+
+public class FieldAlreadyInUseException extends RuntimeException {
+}
diff --git a/component-test/src/main/java/io/mifos/customer/catalog/TestCatalog.java b/component-test/src/main/java/io/mifos/customer/catalog/TestCatalog.java
index 4fd679d..c4fdc4d 100644
--- a/component-test/src/main/java/io/mifos/customer/catalog/TestCatalog.java
+++ b/component-test/src/main/java/io/mifos/customer/catalog/TestCatalog.java
@@ -28,8 +28,10 @@ import io.mifos.customer.api.v1.CustomerEventConstants;
 import io.mifos.customer.api.v1.client.CustomerManager;
 import io.mifos.customer.api.v1.domain.Customer;
 import io.mifos.customer.catalog.api.v1.CatalogEventConstants;
+import io.mifos.customer.catalog.api.v1.client.CatalogAlreadyInUseException;
 import io.mifos.customer.catalog.api.v1.client.CatalogManager;
 import io.mifos.customer.catalog.api.v1.client.CatalogValidationException;
+import io.mifos.customer.catalog.api.v1.client.FieldAlreadyInUseException;
 import io.mifos.customer.catalog.api.v1.domain.Catalog;
 import io.mifos.customer.catalog.api.v1.domain.Field;
 import io.mifos.customer.catalog.api.v1.domain.Option;
@@ -219,7 +221,7 @@ public class TestCatalog {
     Assert.assertTrue(this.eventRecorder.wait(CatalogEventConstants.DELETE_CATALOG, catalog.getIdentifier()));
   }
 
-  @Test(expected = CatalogValidationException.class)
+  @Test(expected = CatalogAlreadyInUseException.class)
   public void shouldNotDeleteCatalogUsed() throws Exception {
     final Catalog catalog = CatalogGenerator.createRandomCatalog();
 
@@ -272,7 +274,7 @@ public class TestCatalog {
     Assert.assertEquals(1, savedCatalog.getFields().size());
   }
 
-  @Test(expected = CatalogValidationException.class)
+  @Test(expected = FieldAlreadyInUseException.class)
   public void shouldNotDeleteField() throws Exception {
     final Catalog catalog = CatalogGenerator.createRandomCatalog();
 
diff --git a/service/src/main/java/io/mifos/customer/catalog/service/rest/controller/CatalogRestController.java b/service/src/main/java/io/mifos/customer/catalog/service/rest/controller/CatalogRestController.java
index 6b0081e..9f4f744 100644
--- a/service/src/main/java/io/mifos/customer/catalog/service/rest/controller/CatalogRestController.java
+++ b/service/src/main/java/io/mifos/customer/catalog/service/rest/controller/CatalogRestController.java
@@ -118,7 +118,7 @@ public class CatalogRestController {
   @ResponseBody
   ResponseEntity<Void> deleteCatalog(@PathVariable("identifier") final String identifier) {
     if (this.catalogService.catalogInUse(identifier)) {
-      throw ServiceException.badRequest("Catalog {0} in use.", identifier);
+      throw ServiceException.conflict("Catalog {0} in use.", identifier);
     }
 
     this.commandGateway.process(new DeleteCatalogCommand(identifier));
@@ -139,7 +139,7 @@ public class CatalogRestController {
                                    @PathVariable("fieldIdentifier") final String fieldIdentifier,
                                    @RequestBody @Valid Field field) {
     if (this.catalogService.fieldInUse(catalogIdentifier, fieldIdentifier)) {
-      throw ServiceException.badRequest("Catalog {0} in use.", catalogIdentifier);
+      throw ServiceException.conflict("Field {0} in use.", fieldIdentifier);
     }
 
     this.commandGateway.process(new ChangeFieldCommand(catalogIdentifier, field));
@@ -159,7 +159,7 @@ public class CatalogRestController {
   ResponseEntity<Void> deleteField(@PathVariable("catalogIdentifier") final String catalogIdentifier,
                                      @PathVariable("fieldIdentifier") final String fieldIdentifier) {
     if (this.catalogService.fieldInUse(catalogIdentifier, fieldIdentifier)) {
-      throw ServiceException.badRequest("Catalog {0} in use.", catalogIdentifier);
+      throw ServiceException.conflict("Field {0} in use.", fieldIdentifier);
     }
 
     this.commandGateway.process(new DeleteFieldCommand(catalogIdentifier, fieldIdentifier));
diff --git a/service/src/main/java/io/mifos/customer/service/internal/command/handler/CustomerAggregate.java b/service/src/main/java/io/mifos/customer/service/internal/command/handler/CustomerAggregate.java
index c20252c..fa306a5 100644
--- a/service/src/main/java/io/mifos/customer/service/internal/command/handler/CustomerAggregate.java
+++ b/service/src/main/java/io/mifos/customer/service/internal/command/handler/CustomerAggregate.java
@@ -196,6 +196,7 @@ public class CustomerAggregate {
 
     if (customer.getCustomValues() != null) {
       this.fieldValueRepository.deleteByCustomer(customerEntity);
+      this.fieldValueRepository.flush();
       this.setCustomValues(customer, customerEntity);
     }
 

-- 
To stop receiving notification emails like this one, please contact
myrle@apache.org.

[fineract-cn-customer] 33/46: Implementing documents for customers.

Posted by my...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

myrle pushed a commit to branch develop
in repository https://gitbox.apache.org/repos/asf/fineract-cn-customer.git

commit d8b5bf794ab4f1fedfd62f55c5f87df59b36f1b5
Author: Myrle Krantz <my...@apache.org>
AuthorDate: Wed Oct 18 12:32:57 2017 +0200

    Implementing documents for customers.
---
 .../io/mifos/customer/PermittableGroupIds.java     |   1 +
 .../customer/api/v1/CustomerEventConstants.java    |  10 +
 ...CompletedDocumentCannotBeChangedException.java} |   5 +-
 .../api/v1/client/CustomerDocumentsManager.java    | 152 ++++++++++++
 .../customer/api/v1/client/CustomerManager.java    |   2 +-
 ...ption.java => DocumentValidationException.java} |   2 +-
 .../customer/api/v1/domain/CustomerDocument.java   |  91 ++++++++
 .../customer/api/v1/events/DocumentEvent.java      |  71 ++++++
 .../customer/api/v1/events/DocumentPageEvent.java  |  85 +++++++
 .../io/mifos/customer/AbstractCustomerTest.java    |   4 +
 .../main/java/io/mifos/customer/TestCustomer.java  |   4 +-
 .../main/java/io/mifos/customer/TestDocuments.java | 168 +++++++++++++
 .../src/main/java/io/mifos/customer/TestSuite.java |   1 +
 .../customer/listener/DocumentEventListener.java   |  81 +++++++
 .../customer/util/CustomerDocumentGenerator.java   |  21 +-
 .../internal/command/CompleteDocumentCommand.java  |  45 ++++
 .../internal/command/CreateDocumentCommand.java    |  49 ++++
 .../command/CreateDocumentPageCommand.java         |  55 +++++
 .../command/DeleteDocumentPageCommand.java         |  52 +++++
 .../command/handler/DocumentCommandHandler.java    | 118 ++++++++++
 .../service/internal/mapper/DocumentMapper.java    |  70 ++++++
 .../internal/repository/DocumentEntity.java        | 117 ++++++++++
 .../internal/repository/DocumentPageEntity.java    | 128 ++++++++++
 .../repository/DocumentPageRepository.java         |  38 +++
 .../internal/repository/DocumentRepository.java    |  39 ++++
 .../service/internal/service/DocumentService.java  | 103 ++++++++
 .../rest/config/CustomerRestConfiguration.java     |   2 +
 .../service/rest/config/UploadProperties.java      |  51 ++++
 .../rest/controller/DocumentsRestController.java   | 260 +++++++++++++++++++++
 .../db/migrations/mariadb/V7__documents.sql        |  39 ++++
 30 files changed, 1852 insertions(+), 12 deletions(-)

diff --git a/api/src/main/java/io/mifos/customer/PermittableGroupIds.java b/api/src/main/java/io/mifos/customer/PermittableGroupIds.java
index f4f4dec..4d1ed80 100644
--- a/api/src/main/java/io/mifos/customer/PermittableGroupIds.java
+++ b/api/src/main/java/io/mifos/customer/PermittableGroupIds.java
@@ -17,6 +17,7 @@ package io.mifos.customer;
 
 public interface PermittableGroupIds {
 
+  String DOCUMENTS = "customer__v1__documents";
   String CUSTOMER = "customer__v1__customer";
   String PORTRAIT = "customer__v1__portrait";
   String IDENTIFICATIONS = "customer__v1__identifications";
diff --git a/api/src/main/java/io/mifos/customer/api/v1/CustomerEventConstants.java b/api/src/main/java/io/mifos/customer/api/v1/CustomerEventConstants.java
index ab5a9a3..03854ad 100644
--- a/api/src/main/java/io/mifos/customer/api/v1/CustomerEventConstants.java
+++ b/api/src/main/java/io/mifos/customer/api/v1/CustomerEventConstants.java
@@ -47,6 +47,11 @@ public interface CustomerEventConstants {
   String POST_PORTRAIT = "post-portrait";
   String DELETE_PORTRAIT = "delete-portrait";
 
+  String POST_DOCUMENT = "post-document";
+  String POST_DOCUMENT_PAGE = "post-document-page";
+  String DELETE_DOCUMENT_PAGE = "delete-document-page";
+  String POST_DOCUMENT_COMPLETE = "post-document-complete";
+
   String SELECTOR_INITIALIZE = SELECTOR_NAME + " = '" + INITIALIZE + "'";
 
   String SELECTOR_POST_CUSTOMER = SELECTOR_NAME + " = '" + POST_CUSTOMER + "'";
@@ -72,4 +77,9 @@ public interface CustomerEventConstants {
 
   String SELECTOR_PUT_PORTRAIT = SELECTOR_NAME + " = '" + POST_PORTRAIT + "'";
   String SELECTOR_DELETE_PORTRAIT = SELECTOR_NAME + " = '" + DELETE_PORTRAIT + "'";
+
+  String SELECTOR_POST_DOCUMENT = SELECTOR_NAME + " = '" + POST_DOCUMENT + "'";
+  String SELECTOR_POST_DOCUMENT_PAGE = SELECTOR_NAME + " = '" + POST_DOCUMENT_PAGE + "'";
+  String SELECTOR_DELETE_DOCUMENT_PAGE = SELECTOR_NAME + " = '" + DELETE_DOCUMENT_PAGE + "'";
+  String SELECTOR_POST_DOCUMENT_COMPLETE = SELECTOR_NAME + " = '" + POST_DOCUMENT_COMPLETE + "'";
 }
diff --git a/api/src/main/java/io/mifos/customer/api/v1/client/PortraitValidationException.java b/api/src/main/java/io/mifos/customer/api/v1/client/CompletedDocumentCannotBeChangedException.java
similarity index 85%
copy from api/src/main/java/io/mifos/customer/api/v1/client/PortraitValidationException.java
copy to api/src/main/java/io/mifos/customer/api/v1/client/CompletedDocumentCannotBeChangedException.java
index cbe3a33..4182f46 100644
--- a/api/src/main/java/io/mifos/customer/api/v1/client/PortraitValidationException.java
+++ b/api/src/main/java/io/mifos/customer/api/v1/client/CompletedDocumentCannotBeChangedException.java
@@ -15,5 +15,8 @@
  */
 package io.mifos.customer.api.v1.client;
 
-public class PortraitValidationException extends RuntimeException {
+/**
+ * @author Myrle Krantz
+ */
+public class CompletedDocumentCannotBeChangedException extends RuntimeException {
 }
diff --git a/api/src/main/java/io/mifos/customer/api/v1/client/CustomerDocumentsManager.java b/api/src/main/java/io/mifos/customer/api/v1/client/CustomerDocumentsManager.java
new file mode 100644
index 0000000..35b9be9
--- /dev/null
+++ b/api/src/main/java/io/mifos/customer/api/v1/client/CustomerDocumentsManager.java
@@ -0,0 +1,152 @@
+/*
+ * Copyright 2017 The Mifos Initiative.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.mifos.customer.api.v1.client;
+
+import io.mifos.core.api.annotation.ThrowsException;
+import io.mifos.core.api.annotation.ThrowsExceptions;
+import io.mifos.customer.api.v1.config.CustomerFeignClientConfig;
+import io.mifos.customer.api.v1.domain.CustomerDocument;
+import org.hibernate.validator.constraints.Range;
+import org.springframework.cloud.netflix.feign.FeignClient;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.MediaType;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.util.List;
+
+/**
+ * @author Myrle Krantz
+ */
+@FeignClient(name="customer-v1", path="/customer/v1", configuration= CustomerFeignClientConfig.class)
+public interface CustomerDocumentsManager {
+
+  @RequestMapping(
+      value = "/customers/{customeridentifier}/documents",
+      method = RequestMethod.GET,
+      produces = MediaType.ALL_VALUE,
+      consumes = MediaType.APPLICATION_JSON_VALUE
+  )
+  List<CustomerDocument> getDocuments(
+      @PathVariable("customeridentifier") final String customerIdentifier);
+
+
+
+  @RequestMapping(
+      value = "/customers/{customeridentifier}/documents/{documentidentifier}",
+      method = RequestMethod.GET,
+      produces = MediaType.ALL_VALUE
+  )
+  CustomerDocument getDocument(
+      @PathVariable("customeridentifier") final String customerIdentifier,
+      @PathVariable("documentidentifier") final String documentIdentifier);
+
+
+
+  @RequestMapping(
+      value = "/customers/{customeridentifier}/documents/{documentidentifier}",
+      method = RequestMethod.POST,
+      produces = MediaType.APPLICATION_JSON_VALUE,
+      consumes = MediaType.APPLICATION_JSON_VALUE
+  )
+  @ThrowsExceptions({
+      @ThrowsException(status = HttpStatus.BAD_REQUEST, exception = DocumentValidationException.class)
+  })
+  void createDocument(
+      @PathVariable("customeridentifier") final String customerIdentifier,
+      @PathVariable("documentidentifier") final String documentIdentifier,
+      @RequestBody final CustomerDocument customerDocument);
+
+
+  /**
+   * Once a document is "completed" its name and images cannot be changed again.  Only completed
+   * documents should be referenced by other services.
+   *
+   * @param completed once this is set to true it cannot be changed back again.
+   */
+  @RequestMapping(
+      value = "/customers/{customeridentifier}/documents/{documentidentifier}/completed",
+      method = RequestMethod.POST,
+      produces = MediaType.APPLICATION_JSON_VALUE,
+      consumes = MediaType.APPLICATION_JSON_VALUE
+  )
+  @ThrowsExceptions({
+      @ThrowsException(status = HttpStatus.CONFLICT, exception = CompletedDocumentCannotBeChangedException.class),
+      @ThrowsException(status = HttpStatus.BAD_REQUEST, exception = DocumentValidationException.class),
+  })
+  void completeDocument(
+      @PathVariable("customeridentifier") final String customerIdentifier,
+      @PathVariable("documentidentifier") final String documentIdentifier,
+      @RequestBody final Boolean completed);
+
+
+
+  @RequestMapping(
+      value = "/customers/{customeridentifier}/documents/{documentidentifier}/pages",
+      method = RequestMethod.GET,
+      produces = MediaType.ALL_VALUE,
+      consumes = MediaType.APPLICATION_JSON_VALUE
+  )
+  List<Integer> getDocumentPageNumbers(
+      @PathVariable("customeridentifier") final String customerIdentifier,
+      @PathVariable("documentidentifier") final String documentIdentifier);
+
+
+
+  @RequestMapping(
+      value = "/customers/{customeridentifier}/documents/{documentidentifier}/pages/{pagenumber}",
+      method = RequestMethod.GET,
+      produces = MediaType.ALL_VALUE,
+      consumes = MediaType.APPLICATION_JSON_VALUE
+  )
+  byte[] getDocumentPage(
+      @PathVariable("customeridentifier") final String customerIdentifier,
+      @PathVariable("documentidentifier") final String documentIdentifier,
+      @PathVariable("pagenumber") final Integer pageNumber);
+
+
+
+  @RequestMapping(
+      value = "/customers/{customeridentifier}/documents/{documentidentifier}/pages/{pagenumber}",
+      method = RequestMethod.POST,
+      produces = MediaType.ALL_VALUE,
+      consumes = MediaType.MULTIPART_FORM_DATA_VALUE
+  )
+  @ThrowsExceptions({
+      @ThrowsException(status = HttpStatus.CONFLICT, exception = CompletedDocumentCannotBeChangedException.class),
+      @ThrowsException(status = HttpStatus.BAD_REQUEST, exception = DocumentValidationException.class),
+  })
+  void createDocumentPage(
+      @PathVariable("customeridentifier") final String customerIdentifier,
+      @PathVariable("documentidentifier") final String documentIdentifier,
+      @PathVariable("pagenumber") @Range(min=0) final Integer pageNumber,
+      @RequestBody final MultipartFile page);
+
+
+  @RequestMapping(
+      value = "/customers/{customeridentifier}/documents/{documentidentifier}/pages/{pagenumber}",
+      method = RequestMethod.DELETE,
+      produces = MediaType.ALL_VALUE,
+      consumes = MediaType.APPLICATION_JSON_VALUE
+  )
+  @ThrowsExceptions({
+      @ThrowsException(status = HttpStatus.CONFLICT, exception = CompletedDocumentCannotBeChangedException.class)
+  })
+  void deleteDocumentPage(
+      @PathVariable("customeridentifier") final String customerIdentifier,
+      @PathVariable("documentidentifier") final String documentIdentifier,
+      @PathVariable("pagenumber") @Range(min=0) final Integer pageNumber);
+}
\ No newline at end of file
diff --git a/api/src/main/java/io/mifos/customer/api/v1/client/CustomerManager.java b/api/src/main/java/io/mifos/customer/api/v1/client/CustomerManager.java
index 1d56aea..9b91502 100644
--- a/api/src/main/java/io/mifos/customer/api/v1/client/CustomerManager.java
+++ b/api/src/main/java/io/mifos/customer/api/v1/client/CustomerManager.java
@@ -329,7 +329,7 @@ public interface CustomerManager {
   )
   @ThrowsExceptions({
           @ThrowsException(status = HttpStatus.NOT_FOUND, exception = CustomerNotFoundException.class),
-          @ThrowsException(status = HttpStatus.BAD_REQUEST, exception = PortraitValidationException.class),
+          @ThrowsException(status = HttpStatus.BAD_REQUEST, exception = DocumentValidationException.class),
   })
   void postPortrait(@PathVariable("identifier") final String identifier,
                    @RequestBody final MultipartFile portrait);
diff --git a/api/src/main/java/io/mifos/customer/api/v1/client/PortraitValidationException.java b/api/src/main/java/io/mifos/customer/api/v1/client/DocumentValidationException.java
similarity index 91%
rename from api/src/main/java/io/mifos/customer/api/v1/client/PortraitValidationException.java
rename to api/src/main/java/io/mifos/customer/api/v1/client/DocumentValidationException.java
index cbe3a33..6c0c60a 100644
--- a/api/src/main/java/io/mifos/customer/api/v1/client/PortraitValidationException.java
+++ b/api/src/main/java/io/mifos/customer/api/v1/client/DocumentValidationException.java
@@ -15,5 +15,5 @@
  */
 package io.mifos.customer.api.v1.client;
 
-public class PortraitValidationException extends RuntimeException {
+public class DocumentValidationException extends RuntimeException {
 }
diff --git a/api/src/main/java/io/mifos/customer/api/v1/domain/CustomerDocument.java b/api/src/main/java/io/mifos/customer/api/v1/domain/CustomerDocument.java
new file mode 100644
index 0000000..e6d5857
--- /dev/null
+++ b/api/src/main/java/io/mifos/customer/api/v1/domain/CustomerDocument.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright 2017 The Mifos Initiative.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.mifos.customer.api.v1.domain;
+
+import io.mifos.core.lang.validation.constraints.ValidIdentifier;
+
+import java.util.Objects;
+
+/**
+ * @author Myrle Krantz
+ */
+public class CustomerDocument {
+  @ValidIdentifier
+  private String identifier;
+
+  private boolean completed;
+  private String createdBy;
+  private String createdOn;
+
+  public CustomerDocument() {
+  }
+
+  public String getIdentifier() {
+    return identifier;
+  }
+
+  public void setIdentifier(String identifier) {
+    this.identifier = identifier;
+  }
+
+  public boolean isCompleted() {
+    return completed;
+  }
+
+  public void setCompleted(boolean completed) {
+    this.completed = completed;
+  }
+
+  public String getCreatedBy() {
+    return createdBy;
+  }
+
+  public void setCreatedBy(String createdBy) {
+    this.createdBy = createdBy;
+  }
+
+  public String getCreatedOn() {
+    return createdOn;
+  }
+
+  public void setCreatedOn(String createdOn) {
+    this.createdOn = createdOn;
+  }
+
+  @Override
+  public boolean equals(Object o) {
+    if (this == o) return true;
+    if (o == null || getClass() != o.getClass()) return false;
+    CustomerDocument that = (CustomerDocument) o;
+    return completed == that.completed &&
+        Objects.equals(identifier, that.identifier);
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hash(identifier, completed);
+  }
+
+  @Override
+  public String toString() {
+    return "CustomerDocument{" +
+        "identifier='" + identifier + '\'' +
+        ", completed=" + completed +
+        ", createdBy='" + createdBy + '\'' +
+        ", createdOn='" + createdOn + '\'' +
+        '}';
+  }
+}
diff --git a/api/src/main/java/io/mifos/customer/api/v1/events/DocumentEvent.java b/api/src/main/java/io/mifos/customer/api/v1/events/DocumentEvent.java
new file mode 100644
index 0000000..8e178fc
--- /dev/null
+++ b/api/src/main/java/io/mifos/customer/api/v1/events/DocumentEvent.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright 2017 The Mifos Initiative.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.mifos.customer.api.v1.events;
+
+import java.util.Objects;
+
+/**
+ * @author Myrle Krantz
+ */
+public class DocumentEvent {
+
+  private String customerIdentifier;
+
+  private String documentIdentifier;
+
+  public DocumentEvent(String customerIdentifier, String documentIdentifier) {
+    this.customerIdentifier = customerIdentifier;
+    this.documentIdentifier = documentIdentifier;
+  }
+
+  public String getCustomerIdentifier() {
+    return customerIdentifier;
+  }
+
+  public void setCustomerIdentifier(String customerIdentifier) {
+    this.customerIdentifier = customerIdentifier;
+  }
+
+  public String getDocumentIdentifier() {
+    return documentIdentifier;
+  }
+
+  public void setDocumentIdentifier(String documentIdentifier) {
+    this.documentIdentifier = documentIdentifier;
+  }
+
+  @Override
+  public boolean equals(Object o) {
+    if (this == o) return true;
+    if (o == null || getClass() != o.getClass()) return false;
+    DocumentEvent that = (DocumentEvent) o;
+    return Objects.equals(customerIdentifier, that.customerIdentifier) &&
+        Objects.equals(documentIdentifier, that.documentIdentifier);
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hash(customerIdentifier, documentIdentifier);
+  }
+
+  @Override
+  public String toString() {
+    return "DocumentEvent{" +
+        "customerIdentifier='" + customerIdentifier + '\'' +
+        ", documentIdentifier='" + documentIdentifier + '\'' +
+        '}';
+  }
+}
diff --git a/api/src/main/java/io/mifos/customer/api/v1/events/DocumentPageEvent.java b/api/src/main/java/io/mifos/customer/api/v1/events/DocumentPageEvent.java
new file mode 100644
index 0000000..9a747ef
--- /dev/null
+++ b/api/src/main/java/io/mifos/customer/api/v1/events/DocumentPageEvent.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright 2017 The Mifos Initiative.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.mifos.customer.api.v1.events;
+
+import java.util.Objects;
+
+/**
+ * @author Myrle Krantz
+ */
+@SuppressWarnings("unused")
+public class DocumentPageEvent {
+
+  private String customerIdentifier;
+
+  private String documentIdentifier;
+
+  private int pageNumber;
+
+  public DocumentPageEvent(String customerIdentifier, String documentIdentifier, int pageNumber) {
+    this.customerIdentifier = customerIdentifier;
+    this.documentIdentifier = documentIdentifier;
+    this.pageNumber = pageNumber;
+  }
+
+  public String getCustomerIdentifier() {
+    return customerIdentifier;
+  }
+
+  public void setCustomerIdentifier(String customerIdentifier) {
+    this.customerIdentifier = customerIdentifier;
+  }
+
+  public String getDocumentIdentifier() {
+    return documentIdentifier;
+  }
+
+  public void setDocumentIdentifier(String documentIdentifier) {
+    this.documentIdentifier = documentIdentifier;
+  }
+
+  public int getPageNumber() {
+    return pageNumber;
+  }
+
+  public void setPageNumber(int pageNumber) {
+    this.pageNumber = pageNumber;
+  }
+
+  @Override
+  public boolean equals(Object o) {
+    if (this == o) return true;
+    if (o == null || getClass() != o.getClass()) return false;
+    DocumentPageEvent that = (DocumentPageEvent) o;
+    return pageNumber == that.pageNumber &&
+        Objects.equals(customerIdentifier, that.customerIdentifier) &&
+        Objects.equals(documentIdentifier, that.documentIdentifier);
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hash(customerIdentifier, documentIdentifier, pageNumber);
+  }
+
+  @Override
+  public String toString() {
+    return "DocumentPageEvent{" +
+        "customerIdentifier='" + customerIdentifier + '\'' +
+        ", documentIdentifier='" + documentIdentifier + '\'' +
+        ", pageNumber=" + pageNumber +
+        '}';
+  }
+}
diff --git a/component-test/src/main/java/io/mifos/customer/AbstractCustomerTest.java b/component-test/src/main/java/io/mifos/customer/AbstractCustomerTest.java
index 58cbad4..2649c13 100644
--- a/component-test/src/main/java/io/mifos/customer/AbstractCustomerTest.java
+++ b/component-test/src/main/java/io/mifos/customer/AbstractCustomerTest.java
@@ -21,6 +21,7 @@ import io.mifos.core.test.fixture.TenantDataStoreContextTestRule;
 import io.mifos.core.test.listener.EnableEventRecording;
 import io.mifos.core.test.listener.EventRecorder;
 import io.mifos.customer.api.v1.CustomerEventConstants;
+import io.mifos.customer.api.v1.client.CustomerDocumentsManager;
 import io.mifos.customer.api.v1.client.CustomerManager;
 import io.mifos.customer.service.rest.config.CustomerRestConfiguration;
 import org.junit.After;
@@ -73,6 +74,9 @@ public class AbstractCustomerTest extends SuiteTestEnvironment {
   CustomerManager customerManager;
 
   @Autowired
+  CustomerDocumentsManager customerDocumentsManager;
+
+  @Autowired
   EventRecorder eventRecorder;
 
   private AutoUserContext userContext;
diff --git a/component-test/src/main/java/io/mifos/customer/TestCustomer.java b/component-test/src/main/java/io/mifos/customer/TestCustomer.java
index 36bf013..d11d5a8 100644
--- a/component-test/src/main/java/io/mifos/customer/TestCustomer.java
+++ b/component-test/src/main/java/io/mifos/customer/TestCustomer.java
@@ -20,7 +20,7 @@ import io.mifos.customer.api.v1.client.CustomerAlreadyExistsException;
 import io.mifos.customer.api.v1.client.CustomerNotFoundException;
 import io.mifos.customer.api.v1.client.CustomerValidationException;
 import io.mifos.customer.api.v1.client.PortraitNotFoundException;
-import io.mifos.customer.api.v1.client.PortraitValidationException;
+import io.mifos.customer.api.v1.client.DocumentValidationException;
 import io.mifos.customer.api.v1.domain.Address;
 import io.mifos.customer.api.v1.domain.Command;
 import io.mifos.customer.api.v1.domain.ContactDetail;
@@ -392,7 +392,7 @@ public class TestCustomer extends AbstractCustomerTest {
     Assert.assertArrayEquals(secondFile.getBytes(), portrait);
   }
 
-  @Test(expected = PortraitValidationException.class)
+  @Test(expected = DocumentValidationException.class)
   public void shouldThrowIfPortraitExceedsMaxSize() throws Exception {
     final Customer customer = CustomerGenerator.createRandomCustomer();
 
diff --git a/component-test/src/main/java/io/mifos/customer/TestDocuments.java b/component-test/src/main/java/io/mifos/customer/TestDocuments.java
new file mode 100644
index 0000000..7c5f4d2
--- /dev/null
+++ b/component-test/src/main/java/io/mifos/customer/TestDocuments.java
@@ -0,0 +1,168 @@
+/*
+ * Copyright 2017 The Mifos Initiative.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.mifos.customer;
+
+import io.mifos.core.api.util.NotFoundException;
+import io.mifos.core.test.domain.TimeStampChecker;
+import io.mifos.customer.api.v1.CustomerEventConstants;
+import io.mifos.customer.api.v1.client.CompletedDocumentCannotBeChangedException;
+import io.mifos.customer.api.v1.client.DocumentValidationException;
+import io.mifos.customer.api.v1.domain.Customer;
+import io.mifos.customer.api.v1.domain.CustomerDocument;
+import io.mifos.customer.api.v1.events.DocumentEvent;
+import io.mifos.customer.api.v1.events.DocumentPageEvent;
+import io.mifos.customer.util.CustomerDocumentGenerator;
+import io.mifos.customer.util.CustomerGenerator;
+import org.apache.commons.lang3.RandomStringUtils;
+import org.junit.Assert;
+import org.junit.Test;
+import org.springframework.http.MediaType;
+import org.springframework.mock.web.MockMultipartFile;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.List;
+import java.util.stream.Collectors;
+import java.util.stream.IntStream;
+
+/**
+ * @author Myrle Krantz
+ */
+public class TestDocuments extends AbstractCustomerTest {
+
+  @Test
+  public void shouldUploadEditThenCompleteDocument() throws InterruptedException, IOException {
+    //Prepare test.
+    final Customer customer = CustomerGenerator.createRandomCustomer();
+    customerManager.createCustomer(customer);
+    eventRecorder.wait(CustomerEventConstants.POST_CUSTOMER, customer.getIdentifier());
+
+
+    //Check that document "stub" can be created.
+    final CustomerDocument customerDocument = CustomerDocumentGenerator.createRandomCustomerDocument();
+    customerDocumentsManager.createDocument(customer.getIdentifier(), customerDocument.getIdentifier(), customerDocument);
+    eventRecorder.wait(CustomerEventConstants.POST_DOCUMENT,
+        new DocumentEvent(customer.getIdentifier(), customerDocument.getIdentifier()));
+
+    final CustomerDocument createdCustomerDocument = customerDocumentsManager.getDocument(
+        customer.getIdentifier(), customerDocument.getIdentifier());
+    Assert.assertEquals(customerDocument, createdCustomerDocument);
+
+
+    //Check that pages can be created.
+    for (int i = 0; i < 10; i++) {
+      createDocumentPage(customer.getIdentifier(), customerDocument.getIdentifier(), i);
+    }
+
+    List<Integer> pageNumbers = customerDocumentsManager.getDocumentPageNumbers(
+        customer.getIdentifier(), customerDocument.getIdentifier());
+    final List<Integer> expectedPageNumbers = IntStream.range(0, 10).boxed().collect(Collectors.toList());
+    Assert.assertEquals(expectedPageNumbers, pageNumbers);
+
+
+    //Check that a page can be deleted.
+    customerDocumentsManager.deleteDocumentPage(customer.getIdentifier(), customerDocument.getIdentifier(), 9);
+    eventRecorder.wait(CustomerEventConstants.DELETE_DOCUMENT_PAGE,
+        new DocumentPageEvent(customer.getIdentifier(), customerDocument.getIdentifier(), 9));
+
+    final List<Integer> changedPageNumbers = customerDocumentsManager.getDocumentPageNumbers(
+        customer.getIdentifier(), customerDocument.getIdentifier());
+    final List<Integer> changedExpectedPageNumbers = IntStream.range(0, 9).boxed().collect(Collectors.toList());
+    Assert.assertEquals(changedExpectedPageNumbers, changedPageNumbers);
+
+    try {
+      customerDocumentsManager.getDocumentPage(customer.getIdentifier(), customerDocument.getIdentifier(), 9);
+      Assert.fail("Getting the 9th document page should throw a NotFoundException after the 9th page was removed.");
+    }
+    catch (final NotFoundException ignored) {}
+
+
+    //Check that a document which is missing pages cannot be completed
+    customerDocumentsManager.deleteDocumentPage(customer.getIdentifier(), customerDocument.getIdentifier(), 2);
+    eventRecorder.wait(CustomerEventConstants.DELETE_DOCUMENT_PAGE,
+        new DocumentPageEvent(customer.getIdentifier(), customerDocument.getIdentifier(), 2));
+
+    try {
+      customerDocumentsManager.completeDocument(customer.getIdentifier(), customerDocument.getIdentifier(), true);
+      Assert.fail("It shouldn't be possible to complete a document with missing pages.");
+    }
+    catch (final DocumentValidationException ignored) {}
+
+    createDocumentPage(customer.getIdentifier(), customerDocument.getIdentifier(), 2);
+
+
+    //Check that a valid document can be completed.
+    final TimeStampChecker timeStampChecker = TimeStampChecker.roughlyNow();
+    customerDocumentsManager.completeDocument(customer.getIdentifier(), customerDocument.getIdentifier(), true);
+    eventRecorder.wait(CustomerEventConstants.POST_DOCUMENT_COMPLETE,
+        new DocumentPageEvent(customer.getIdentifier(), customerDocument.getIdentifier(), 9));
+
+    final CustomerDocument completedDocument = customerDocumentsManager.getDocument(
+        customer.getIdentifier(), customerDocument.getIdentifier());
+    Assert.assertEquals(true, completedDocument.isCompleted());
+    timeStampChecker.assertCorrect(completedDocument.getCreatedOn());
+
+
+    //Check that document can't be changed after completion
+    try {
+      createDocumentPage(customer.getIdentifier(), customerDocument.getIdentifier(), 9);
+      Assert.fail("Adding another page after the document is completed shouldn't be possible.");
+    }
+    catch (final CompletedDocumentCannotBeChangedException ignored) {}
+    try {
+      customerDocumentsManager.deleteDocumentPage(customer.getIdentifier(), customerDocument.getIdentifier(), 8);
+      Assert.fail("Deleting a page after the document is completed shouldn't be possible.");
+    }
+    catch (final CompletedDocumentCannotBeChangedException ignored) {}
+
+
+    //Check that document can't be uncompleted.
+    try {
+      customerDocumentsManager.completeDocument(customer.getIdentifier(), customerDocument.getIdentifier(), false);
+      Assert.fail("It shouldn't be possible to change a document from completed to uncompleted.");
+    }
+    catch (final CompletedDocumentCannotBeChangedException ignored) {}
+
+
+    //Check that document is in the list.
+    final List<CustomerDocument> documents = customerDocumentsManager.getDocuments(customer.getIdentifier());
+    final boolean documentIsInList = documents.stream().anyMatch(x ->
+        (x.getIdentifier().equals(customerDocument.getIdentifier())) &&
+            (x.isCompleted()));
+    Assert.assertTrue("The document we just completed should be in the list", documentIsInList);
+  }
+
+
+  private void createDocumentPage(
+      final String customerIdentifier,
+      final String documentIdentifier,
+      final int pageNumber) throws InterruptedException, IOException {
+    final MockMultipartFile page = new MockMultipartFile(
+        "page",
+        "test.png",
+        MediaType.IMAGE_PNG_VALUE,
+        RandomStringUtils.randomAlphanumeric(20).getBytes());
+
+    customerDocumentsManager.createDocumentPage(customerIdentifier, documentIdentifier, pageNumber, page);
+    eventRecorder.wait(CustomerEventConstants.POST_DOCUMENT_PAGE,
+        new DocumentPageEvent(customerIdentifier, documentIdentifier, pageNumber));
+
+    Thread.sleep(100);
+
+    final byte[] uploadedPage = customerDocumentsManager.getDocumentPage(customerIdentifier, documentIdentifier, pageNumber);
+    Assert.assertTrue("Page " + pageNumber, Arrays.equals(page.getBytes(), uploadedPage));
+  }
+}
diff --git a/component-test/src/main/java/io/mifos/customer/TestSuite.java b/component-test/src/main/java/io/mifos/customer/TestSuite.java
index 4ed78bb..391fbbf 100644
--- a/component-test/src/main/java/io/mifos/customer/TestSuite.java
+++ b/component-test/src/main/java/io/mifos/customer/TestSuite.java
@@ -28,6 +28,7 @@ import org.junit.runners.Suite;
     TestInfrastructure.class,
     TestTaskDefinition.class,
     TestTaskInstance.class,
+    TestDocuments.class
 })
 public class TestSuite extends SuiteTestEnvironment {
 }
diff --git a/component-test/src/main/java/io/mifos/customer/listener/DocumentEventListener.java b/component-test/src/main/java/io/mifos/customer/listener/DocumentEventListener.java
new file mode 100644
index 0000000..6875afd
--- /dev/null
+++ b/component-test/src/main/java/io/mifos/customer/listener/DocumentEventListener.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright 2017 The Mifos Initiative.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.mifos.customer.listener;
+
+import io.mifos.core.lang.config.TenantHeaderFilter;
+import io.mifos.core.test.listener.EventRecorder;
+import io.mifos.customer.api.v1.CustomerEventConstants;
+import io.mifos.customer.api.v1.events.DocumentEvent;
+import io.mifos.customer.api.v1.events.DocumentPageEvent;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.jms.annotation.JmsListener;
+import org.springframework.messaging.handler.annotation.Header;
+import org.springframework.stereotype.Component;
+
+/**
+ * @author Myrle Krantz
+ */
+@Component
+public class DocumentEventListener {
+
+  private final EventRecorder eventRecorder;
+
+  @Autowired
+  public DocumentEventListener(final EventRecorder eventRecorder) {
+    super();
+    this.eventRecorder = eventRecorder;
+  }
+
+  @JmsListener(
+      destination = CustomerEventConstants.DESTINATION,
+      selector = CustomerEventConstants.SELECTOR_POST_DOCUMENT
+  )
+  public void postDocumentEvent(
+      @Header(TenantHeaderFilter.TENANT_HEADER) final String tenant,
+      final String payload) {
+    this.eventRecorder.event(tenant, CustomerEventConstants.POST_DOCUMENT, payload, DocumentEvent.class);
+  }
+
+  @JmsListener(
+      destination = CustomerEventConstants.DESTINATION,
+      selector = CustomerEventConstants.SELECTOR_POST_DOCUMENT_PAGE
+  )
+  public void postDocumentPageEvent(
+      @Header(TenantHeaderFilter.TENANT_HEADER) final String tenant,
+      final String payload) {
+    this.eventRecorder.event(tenant, CustomerEventConstants.POST_DOCUMENT_PAGE, payload, DocumentPageEvent.class);
+  }
+
+  @JmsListener(
+      destination = CustomerEventConstants.DESTINATION,
+      selector = CustomerEventConstants.SELECTOR_DELETE_DOCUMENT_PAGE
+  )
+  public void deleteDocumentPageEvent(
+      @Header(TenantHeaderFilter.TENANT_HEADER) final String tenant,
+      final String payload) {
+    this.eventRecorder.event(tenant, CustomerEventConstants.DELETE_DOCUMENT_PAGE, payload, DocumentPageEvent.class);
+  }
+
+  @JmsListener(
+      destination = CustomerEventConstants.DESTINATION,
+      selector = CustomerEventConstants.SELECTOR_POST_DOCUMENT_COMPLETE
+  )
+  public void postDocumentComplete(
+      @Header(TenantHeaderFilter.TENANT_HEADER) final String tenant,
+      final String payload) {
+    this.eventRecorder.event(tenant, CustomerEventConstants.POST_DOCUMENT_COMPLETE, payload, DocumentEvent.class);
+  }
+}
diff --git a/api/src/main/java/io/mifos/customer/PermittableGroupIds.java b/component-test/src/main/java/io/mifos/customer/util/CustomerDocumentGenerator.java
similarity index 57%
copy from api/src/main/java/io/mifos/customer/PermittableGroupIds.java
copy to component-test/src/main/java/io/mifos/customer/util/CustomerDocumentGenerator.java
index f4f4dec..4a7ee13 100644
--- a/api/src/main/java/io/mifos/customer/PermittableGroupIds.java
+++ b/component-test/src/main/java/io/mifos/customer/util/CustomerDocumentGenerator.java
@@ -13,13 +13,20 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package io.mifos.customer;
+package io.mifos.customer.util;
 
-public interface PermittableGroupIds {
+import io.mifos.customer.api.v1.domain.CustomerDocument;
+import org.apache.commons.lang3.RandomStringUtils;
 
-  String CUSTOMER = "customer__v1__customer";
-  String PORTRAIT = "customer__v1__portrait";
-  String IDENTIFICATIONS = "customer__v1__identifications";
-  String TASK = "customer__v1__task";
-  String CATALOG = "catalog__v1__catalog";
+public final class CustomerDocumentGenerator {
+
+  private CustomerDocumentGenerator() {
+    super();
+  }
+
+  public static CustomerDocument createRandomCustomerDocument() {
+    final CustomerDocument ret = new CustomerDocument();
+    ret.setIdentifier(RandomStringUtils.randomAlphanumeric(8));
+    return ret;
+  }
 }
diff --git a/service/src/main/java/io/mifos/customer/service/internal/command/CompleteDocumentCommand.java b/service/src/main/java/io/mifos/customer/service/internal/command/CompleteDocumentCommand.java
new file mode 100644
index 0000000..1f60a9f
--- /dev/null
+++ b/service/src/main/java/io/mifos/customer/service/internal/command/CompleteDocumentCommand.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2017 The Mifos Initiative.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.mifos.customer.service.internal.command;
+
+/**
+ * @author Myrle Krantz
+ */
+public class CompleteDocumentCommand {
+  private final String customerIdentifier;
+  private final String documentIdentifier;
+
+  public CompleteDocumentCommand(String customerIdentifier, String documentIdentifier) {
+    this.customerIdentifier = customerIdentifier;
+    this.documentIdentifier = documentIdentifier;
+  }
+
+  public String getCustomerIdentifier() {
+    return customerIdentifier;
+  }
+
+  public String getDocumentIdentifier() {
+    return documentIdentifier;
+  }
+
+  @Override
+  public String toString() {
+    return "CompleteDocumentCommand{" +
+        "customerIdentifier='" + customerIdentifier + '\'' +
+        ", documentIdentifier='" + documentIdentifier + '\'' +
+        '}';
+  }
+}
diff --git a/service/src/main/java/io/mifos/customer/service/internal/command/CreateDocumentCommand.java b/service/src/main/java/io/mifos/customer/service/internal/command/CreateDocumentCommand.java
new file mode 100644
index 0000000..3311eee
--- /dev/null
+++ b/service/src/main/java/io/mifos/customer/service/internal/command/CreateDocumentCommand.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2017 The Mifos Initiative.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.mifos.customer.service.internal.command;
+
+import io.mifos.customer.api.v1.domain.CustomerDocument;
+
+/**
+ * @author Myrle Krantz
+ */
+public class CreateDocumentCommand {
+  private final String customerIdentifier;
+  private final CustomerDocument customerDocument;
+
+  public CreateDocumentCommand(
+      final String customerIdentifier,
+      final CustomerDocument customerDocument) {
+    this.customerIdentifier = customerIdentifier;
+    this.customerDocument = customerDocument;
+  }
+
+  public String getCustomerIdentifier() {
+    return customerIdentifier;
+  }
+
+  public CustomerDocument getCustomerDocument() {
+    return customerDocument;
+  }
+
+  @Override
+  public String toString() {
+    return "CreateDocumentCommand{" +
+        "customerIdentifier='" + customerIdentifier + '\'' +
+        ", customerDocument=" + customerDocument.getIdentifier() +
+        '}';
+  }
+}
diff --git a/service/src/main/java/io/mifos/customer/service/internal/command/CreateDocumentPageCommand.java b/service/src/main/java/io/mifos/customer/service/internal/command/CreateDocumentPageCommand.java
new file mode 100644
index 0000000..75ed8af
--- /dev/null
+++ b/service/src/main/java/io/mifos/customer/service/internal/command/CreateDocumentPageCommand.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2017 The Mifos Initiative.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.mifos.customer.service.internal.command;
+
+import org.springframework.web.multipart.MultipartFile;
+
+/**
+ * @author Myrle Krantz
+ */
+public class CreateDocumentPageCommand {
+  private final String customerIdentifier;
+  private final String documentIdentifier;
+  private final Integer pageNumber;
+  private final MultipartFile document;
+
+  public CreateDocumentPageCommand(
+      final String customerIdentifier,
+      final String documentIdentifier,
+      final int pageNumber,
+      final MultipartFile document) {
+    this.customerIdentifier = customerIdentifier;
+    this.documentIdentifier = documentIdentifier;
+    this.pageNumber = pageNumber;
+    this.document = document;
+  }
+
+  public String getCustomerIdentifier() {
+    return customerIdentifier;
+  }
+
+  public String getDocumentIdentifier() {
+    return documentIdentifier;
+  }
+
+  public Integer getPageNumber() {
+    return pageNumber;
+  }
+
+  public MultipartFile getDocument() {
+    return document;
+  }
+}
diff --git a/service/src/main/java/io/mifos/customer/service/internal/command/DeleteDocumentPageCommand.java b/service/src/main/java/io/mifos/customer/service/internal/command/DeleteDocumentPageCommand.java
new file mode 100644
index 0000000..e7dd435
--- /dev/null
+++ b/service/src/main/java/io/mifos/customer/service/internal/command/DeleteDocumentPageCommand.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2017 The Mifos Initiative.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.mifos.customer.service.internal.command;
+
+/**
+ * @author Myrle Krantz
+ */
+public class DeleteDocumentPageCommand {
+  private final String customerIdentifier;
+  private final String documentIdentifier;
+  private final Integer pageNumber;
+
+  public DeleteDocumentPageCommand(String customerIdentifier, String documentIdentifier, Integer pageNumber) {
+    this.customerIdentifier = customerIdentifier;
+    this.documentIdentifier = documentIdentifier;
+    this.pageNumber = pageNumber;
+  }
+
+  public String getCustomerIdentifier() {
+    return customerIdentifier;
+  }
+
+  public String getDocumentIdentifier() {
+    return documentIdentifier;
+  }
+
+  public Integer getPageNumber() {
+    return pageNumber;
+  }
+
+  @Override
+  public String toString() {
+    return "DeleteDocumentPageCommand{" +
+        "customerIdentifier='" + customerIdentifier + '\'' +
+        ", documentIdentifier='" + documentIdentifier + '\'' +
+        ", pageNumber=" + pageNumber +
+        '}';
+  }
+}
diff --git a/service/src/main/java/io/mifos/customer/service/internal/command/handler/DocumentCommandHandler.java b/service/src/main/java/io/mifos/customer/service/internal/command/handler/DocumentCommandHandler.java
new file mode 100644
index 0000000..2ee4498
--- /dev/null
+++ b/service/src/main/java/io/mifos/customer/service/internal/command/handler/DocumentCommandHandler.java
@@ -0,0 +1,118 @@
+/*
+ * Copyright 2017 The Mifos Initiative.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.mifos.customer.service.internal.command.handler;
+
+import io.mifos.core.api.util.UserContextHolder;
+import io.mifos.core.command.annotation.Aggregate;
+import io.mifos.core.command.annotation.CommandHandler;
+import io.mifos.core.command.annotation.EventEmitter;
+import io.mifos.core.lang.ServiceException;
+import io.mifos.customer.api.v1.CustomerEventConstants;
+import io.mifos.customer.api.v1.events.DocumentEvent;
+import io.mifos.customer.api.v1.events.DocumentPageEvent;
+import io.mifos.customer.service.internal.command.CompleteDocumentCommand;
+import io.mifos.customer.service.internal.command.CreateDocumentCommand;
+import io.mifos.customer.service.internal.command.CreateDocumentPageCommand;
+import io.mifos.customer.service.internal.command.DeleteDocumentPageCommand;
+import io.mifos.customer.service.internal.mapper.DocumentMapper;
+import io.mifos.customer.service.internal.repository.*;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.io.IOException;
+import java.time.Clock;
+import java.time.LocalDateTime;
+
+/**
+ * @author Myrle Krantz
+ */
+@Aggregate
+public class DocumentCommandHandler {
+  private final DocumentRepository documentRepository;
+  private final DocumentPageRepository documentPageRepository;
+  private final CustomerRepository customerRepository;
+
+  @Autowired
+  public DocumentCommandHandler(
+      final DocumentRepository documentRepository,
+      final DocumentPageRepository documentPageRepository,
+      final CustomerRepository customerRepository) {
+    this.documentRepository = documentRepository;
+    this.documentPageRepository = documentPageRepository;
+    this.customerRepository = customerRepository;
+  }
+
+  @Transactional
+  @CommandHandler
+  @EventEmitter(selectorName = CustomerEventConstants.SELECTOR_NAME, selectorValue = CustomerEventConstants.POST_DOCUMENT_PAGE)
+  public DocumentPageEvent process(final CreateDocumentPageCommand command) throws IOException {
+    final DocumentEntity documentEntity = documentRepository.findByCustomerIdAndDocumentIdentifier(
+        command.getCustomerIdentifier(),
+        command.getDocumentIdentifier())
+        .orElseThrow(() -> ServiceException.badRequest("Document not found"));
+
+    final DocumentPageEntity documentPageEntity = DocumentMapper.map(command.getDocument(), command.getPageNumber(), documentEntity);
+    documentPageRepository.save(documentPageEntity);
+
+    return new DocumentPageEvent(command.getCustomerIdentifier(), command.getDocumentIdentifier(), command.getPageNumber());
+  }
+
+  @Transactional
+  @CommandHandler
+  @EventEmitter(selectorName = CustomerEventConstants.SELECTOR_NAME, selectorValue = CustomerEventConstants.POST_DOCUMENT)
+  public DocumentEvent process(final CreateDocumentCommand command) throws IOException {
+
+
+    final CustomerEntity customerEntity = customerRepository.findByIdentifier(command.getCustomerIdentifier());
+    final DocumentEntity documentEntity = DocumentMapper.map(command.getCustomerDocument(), customerEntity);
+    documentRepository.save(documentEntity);
+
+    return new DocumentEvent(command.getCustomerIdentifier(), command.getCustomerDocument().getIdentifier());
+  }
+
+  @Transactional
+  @CommandHandler
+  @EventEmitter(selectorName = CustomerEventConstants.SELECTOR_NAME, selectorValue = CustomerEventConstants.POST_DOCUMENT_COMPLETE)
+  public DocumentEvent process(final CompleteDocumentCommand command) throws IOException {
+    final DocumentEntity documentEntity = documentRepository.findByCustomerIdAndDocumentIdentifier(
+        command.getCustomerIdentifier(),
+        command.getDocumentIdentifier())
+        .orElseThrow(() -> ServiceException.badRequest("Document not found"));
+
+    documentEntity.setCreatedOn(LocalDateTime.now(Clock.systemUTC()));
+    documentEntity.setCreatedBy(UserContextHolder.checkedGetUser());
+    documentEntity.setCompleted(true);
+    documentRepository.save(documentEntity);
+
+
+    return new DocumentEvent(command.getCustomerIdentifier(), command.getDocumentIdentifier());
+  }
+
+  @Transactional
+  @CommandHandler
+  @EventEmitter(selectorName = CustomerEventConstants.SELECTOR_NAME, selectorValue = CustomerEventConstants.DELETE_DOCUMENT_PAGE)
+  public DocumentPageEvent process(final DeleteDocumentPageCommand command) throws IOException {
+    documentPageRepository.findByCustomerIdAndDocumentIdentifierAndPageNumber(
+        command.getCustomerIdentifier(),
+        command.getDocumentIdentifier(),
+        command.getPageNumber())
+        .ifPresent(documentPageRepository::delete);
+
+    //No exception if it's not present, because why bother.  It's not present.  That was the goal.
+
+    return new DocumentPageEvent(command.getCustomerIdentifier(), command.getDocumentIdentifier(), command.getPageNumber());
+  }
+}
diff --git a/service/src/main/java/io/mifos/customer/service/internal/mapper/DocumentMapper.java b/service/src/main/java/io/mifos/customer/service/internal/mapper/DocumentMapper.java
new file mode 100644
index 0000000..6ea88e9
--- /dev/null
+++ b/service/src/main/java/io/mifos/customer/service/internal/mapper/DocumentMapper.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright 2017 The Mifos Initiative.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.mifos.customer.service.internal.mapper;
+
+import io.mifos.core.api.util.UserContextHolder;
+import io.mifos.core.lang.DateConverter;
+import io.mifos.customer.api.v1.domain.CustomerDocument;
+import io.mifos.customer.service.internal.repository.CustomerEntity;
+import io.mifos.customer.service.internal.repository.DocumentEntity;
+import io.mifos.customer.service.internal.repository.DocumentPageEntity;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.io.IOException;
+import java.time.Clock;
+import java.time.LocalDateTime;
+
+/**
+ * @author Myrle Krantz
+ */
+public class DocumentMapper {
+  private DocumentMapper() {
+    super();
+  }
+
+
+  public static DocumentPageEntity map(
+      final MultipartFile multipartFile,
+      final int pageNumber,
+      final DocumentEntity documentEntity) throws IOException {
+    final DocumentPageEntity ret = new DocumentPageEntity();
+    ret.setDocument(documentEntity);
+    ret.setPageNumber(pageNumber);
+    ret.setImage(multipartFile.getBytes());
+    ret.setSize(multipartFile.getSize());
+    ret.setContentType(multipartFile.getContentType());
+    return ret;
+  }
+
+  public static CustomerDocument map(final DocumentEntity documentEntity) {
+    final CustomerDocument ret = new CustomerDocument();
+    ret.setCompleted(documentEntity.getCompleted());
+    ret.setCreatedBy(documentEntity.getCreatedBy());
+    ret.setCreatedOn(DateConverter.toIsoString(documentEntity.getCreatedOn()));
+    ret.setIdentifier(documentEntity.getIdentifier());
+    return ret;
+  }
+
+  public static DocumentEntity map(final CustomerDocument customerDocument, final CustomerEntity customerEntity) {
+    final DocumentEntity ret = new DocumentEntity();
+    ret.setCustomer(customerEntity);
+    ret.setCompleted(false);
+    ret.setCreatedBy(UserContextHolder.checkedGetUser());
+    ret.setCreatedOn(LocalDateTime.now(Clock.systemUTC()));
+    ret.setIdentifier(customerDocument.getIdentifier());
+    return ret;
+  }
+}
diff --git a/service/src/main/java/io/mifos/customer/service/internal/repository/DocumentEntity.java b/service/src/main/java/io/mifos/customer/service/internal/repository/DocumentEntity.java
new file mode 100644
index 0000000..ae2513f
--- /dev/null
+++ b/service/src/main/java/io/mifos/customer/service/internal/repository/DocumentEntity.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright 2017 The Mifos Initiative.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.mifos.customer.service.internal.repository;
+
+import io.mifos.core.mariadb.util.LocalDateTimeConverter;
+
+import javax.persistence.*;
+import java.time.LocalDateTime;
+import java.util.Objects;
+
+/**
+ * @author Myrle Krantz
+ */
+@Entity
+@Table(name = "maat_documents")
+public class DocumentEntity {
+
+  @Id
+  @GeneratedValue(strategy = GenerationType.IDENTITY)
+  @Column(name = "id")
+  private Long id;
+
+  @OneToOne(fetch = FetchType.LAZY, optional = false)
+  @JoinColumn(name = "customer_id")
+  private CustomerEntity customer;
+
+  @Column(name = "identifier")
+  private String identifier;
+
+  @Column(name = "is_completed", nullable = false)
+  private Boolean completed;
+
+  @Column(name = "created_by")
+  private String createdBy;
+
+  @Column(name = "created_on")
+  @Convert(converter = LocalDateTimeConverter.class)
+  private LocalDateTime createdOn;
+
+  public DocumentEntity() {
+  }
+
+  public Long getId() {
+    return id;
+  }
+
+  public void setId(Long id) {
+    this.id = id;
+  }
+
+  public CustomerEntity getCustomer() {
+    return customer;
+  }
+
+  public void setCustomer(CustomerEntity customer) {
+    this.customer = customer;
+  }
+
+  public String getIdentifier() {
+    return identifier;
+  }
+
+  public void setIdentifier(String identifier) {
+    this.identifier = identifier;
+  }
+
+  public Boolean getCompleted() {
+    return completed;
+  }
+
+  public void setCompleted(Boolean completed) {
+    this.completed = completed;
+  }
+
+  public String getCreatedBy() {
+    return createdBy;
+  }
+
+  public void setCreatedBy(String createdBy) {
+    this.createdBy = createdBy;
+  }
+
+  public LocalDateTime getCreatedOn() {
+    return createdOn;
+  }
+
+  public void setCreatedOn(LocalDateTime createdOn) {
+    this.createdOn = createdOn;
+  }
+
+  @Override
+  public boolean equals(Object o) {
+    if (this == o) return true;
+    if (o == null || getClass() != o.getClass()) return false;
+    DocumentEntity that = (DocumentEntity) o;
+    return Objects.equals(customer, that.customer) &&
+        Objects.equals(identifier, that.identifier);
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hash(customer, identifier);
+  }
+}
diff --git a/service/src/main/java/io/mifos/customer/service/internal/repository/DocumentPageEntity.java b/service/src/main/java/io/mifos/customer/service/internal/repository/DocumentPageEntity.java
new file mode 100644
index 0000000..32e5731
--- /dev/null
+++ b/service/src/main/java/io/mifos/customer/service/internal/repository/DocumentPageEntity.java
@@ -0,0 +1,128 @@
+/*
+ * Copyright 2017 The Mifos Initiative.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.mifos.customer.service.internal.repository;
+
+import javax.persistence.*;
+import java.util.Arrays;
+import java.util.Objects;
+
+/**
+ * @author Myrle Krantz
+ */
+@Entity
+@Table(name = "maat_document_pages")
+public class DocumentPageEntity {
+  @Id
+  @GeneratedValue(strategy = GenerationType.IDENTITY)
+  @Column(name = "id")
+  private Long id;
+
+  @OneToOne(fetch = FetchType.LAZY, optional = false)
+  @JoinColumn(name = "document_id")
+  private DocumentEntity document;
+
+  @Column(name = "page_number")
+  private Integer pageNumber;
+
+  @Column(name = "content_type")
+  private String contentType;
+
+
+  @Column(name = "size")
+  private Long size;
+
+  @Lob
+  @Column(name = "image")
+  private byte[] image;
+
+  public DocumentPageEntity() {
+  }
+
+  public Long getId() {
+    return id;
+  }
+
+  public void setId(Long id) {
+    this.id = id;
+  }
+
+  @SuppressWarnings("unused")
+  public DocumentEntity getDocument() {
+    return document;
+  }
+
+  public void setDocument(DocumentEntity document) {
+    this.document = document;
+  }
+
+  public Integer getPageNumber() {
+    return pageNumber;
+  }
+
+  public void setPageNumber(Integer pageNumber) {
+    this.pageNumber = pageNumber;
+  }
+
+  public String getContentType() {
+    return contentType;
+  }
+
+  public void setContentType(String contentType) {
+    this.contentType = contentType;
+  }
+
+  public Long getSize() {
+    return size;
+  }
+
+  public void setSize(Long size) {
+    this.size = size;
+  }
+
+  public byte[] getImage() {
+    return image;
+  }
+
+  public void setImage(byte[] image) {
+    this.image = image;
+  }
+
+  @Override
+  public boolean equals(Object o) {
+    if (this == o) return true;
+    if (o == null || getClass() != o.getClass()) return false;
+    DocumentPageEntity that = (DocumentPageEntity) o;
+    return Objects.equals(document, that.document) &&
+        Objects.equals(pageNumber, that.pageNumber);
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hash(document, pageNumber);
+  }
+
+  @Override
+  public String toString() {
+    return "DocumentPageEntity{" +
+        "id=" + id +
+        ", document=" + document +
+        ", pageNumber=" + pageNumber +
+        ", contentType='" + contentType + '\'' +
+        ", size=" + size +
+        ", image=" + Arrays.toString(image) +
+        '}';
+  }
+}
diff --git a/service/src/main/java/io/mifos/customer/service/internal/repository/DocumentPageRepository.java b/service/src/main/java/io/mifos/customer/service/internal/repository/DocumentPageRepository.java
new file mode 100644
index 0000000..bb35d09
--- /dev/null
+++ b/service/src/main/java/io/mifos/customer/service/internal/repository/DocumentPageRepository.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2017 The Mifos Initiative.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.mifos.customer.service.internal.repository;
+
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.Query;
+import org.springframework.data.repository.query.Param;
+import org.springframework.stereotype.Repository;
+
+import java.util.Optional;
+import java.util.stream.Stream;
+
+/**
+ * @author Myrle Krantz
+ */
+@Repository
+public interface DocumentPageRepository extends JpaRepository<DocumentPageEntity, Long> {
+  @Query("SELECT d FROM DocumentPageEntity d WHERE d.document.customer.identifier = :customerIdentifier AND d.document.identifier = :documentIdentifier AND d.pageNumber = :pageNumber")
+  Optional<DocumentPageEntity> findByCustomerIdAndDocumentIdentifierAndPageNumber(
+      @Param("customerIdentifier") String customerIdentifier, @Param("documentIdentifier") String documentIdentifier, @Param("pageNumber") Integer pageNumber);
+
+  @Query("SELECT d FROM DocumentPageEntity d WHERE d.document.customer.identifier = :customerIdentifier AND d.document.identifier = :documentIdentifier")
+  Stream<DocumentPageEntity> findByCustomerIdAndDocumentIdentifier(
+      @Param("customerIdentifier") String customerIdentifier, @Param("documentIdentifier") String documentIdentifier);
+}
diff --git a/service/src/main/java/io/mifos/customer/service/internal/repository/DocumentRepository.java b/service/src/main/java/io/mifos/customer/service/internal/repository/DocumentRepository.java
new file mode 100644
index 0000000..70ea703
--- /dev/null
+++ b/service/src/main/java/io/mifos/customer/service/internal/repository/DocumentRepository.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2017 The Mifos Initiative.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.mifos.customer.service.internal.repository;
+
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.Query;
+import org.springframework.data.repository.query.Param;
+import org.springframework.stereotype.Repository;
+
+import java.util.Optional;
+import java.util.stream.Stream;
+
+/**
+ * @author Myrle Krantz
+ */
+@Repository
+public interface DocumentRepository extends JpaRepository<DocumentEntity, Long> {
+
+  @Query("SELECT d FROM DocumentEntity d WHERE d.customer.identifier = :customerIdentifier AND d.identifier = :documentIdentifier")
+  Optional<DocumentEntity> findByCustomerIdAndDocumentIdentifier(
+      @Param("customerIdentifier") String customerIdentifier, @Param("documentIdentifier") String documentIdentifier);
+
+  @Query("SELECT d FROM DocumentEntity d WHERE d.customer.identifier = :customerIdentifier")
+  Stream<DocumentEntity> findByCustomerId(
+      @Param("customerIdentifier") String customerIdentifier);
+}
diff --git a/service/src/main/java/io/mifos/customer/service/internal/service/DocumentService.java b/service/src/main/java/io/mifos/customer/service/internal/service/DocumentService.java
new file mode 100644
index 0000000..88b6fcd
--- /dev/null
+++ b/service/src/main/java/io/mifos/customer/service/internal/service/DocumentService.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright 2017 The Mifos Initiative.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.mifos.customer.service.internal.service;
+
+import io.mifos.customer.api.v1.domain.CustomerDocument;
+import io.mifos.customer.service.internal.mapper.DocumentMapper;
+import io.mifos.customer.service.internal.repository.DocumentEntity;
+import io.mifos.customer.service.internal.repository.DocumentPageEntity;
+import io.mifos.customer.service.internal.repository.DocumentPageRepository;
+import io.mifos.customer.service.internal.repository.DocumentRepository;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.util.List;
+import java.util.Optional;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+/**
+ * @author Myrle Krantz
+ */
+@Service
+public class DocumentService {
+  private final DocumentRepository documentRepository;
+  private final DocumentPageRepository documentPageRepository;
+
+  @Autowired
+  public DocumentService(
+      final DocumentRepository documentRepository,
+      final DocumentPageRepository documentPageRepository) {
+    this.documentRepository = documentRepository;
+    this.documentPageRepository = documentPageRepository;
+  }
+
+  public Optional<DocumentPageEntity> findPage(
+      final String customerIdentifier,
+      final String documentIdentifier,
+      final Integer pageNumber) {
+    return this.documentPageRepository.findByCustomerIdAndDocumentIdentifierAndPageNumber(
+        customerIdentifier,
+        documentIdentifier,
+        pageNumber);
+  }
+
+  public Stream<CustomerDocument> find(final String customerIdentifier) {
+    final Stream<DocumentEntity> preMappedRet = this.documentRepository.findByCustomerId(customerIdentifier);
+    return preMappedRet.map(DocumentMapper::map);
+  }
+
+  public Optional<CustomerDocument> findDocument(
+      final String customerIdentifier,
+      final String documentIdentifier) {
+    return this.documentRepository.findByCustomerIdAndDocumentIdentifier(customerIdentifier, documentIdentifier)
+        .map(DocumentMapper::map);
+  }
+
+  public boolean documentExists(
+      final String customerIdentifier,
+      final String documentIdentifier) {
+    return findDocument(customerIdentifier, documentIdentifier).isPresent();
+  }
+
+  public Stream<Integer> findPageNumbers(
+      final String customerIdentifier,
+      final String documentIdentifier) {
+    return documentPageRepository.findByCustomerIdAndDocumentIdentifier(customerIdentifier, documentIdentifier)
+        .map(DocumentPageEntity::getPageNumber);
+  }
+
+  public boolean isDocumentCompleted(
+      final String customerIdentifier,
+      final String documentIdentifier) {
+    return documentRepository.findByCustomerIdAndDocumentIdentifier(customerIdentifier, documentIdentifier)
+        .map(DocumentEntity::getCompleted).orElse(true);
+  }
+
+  public boolean isDocumentMissingPages(
+      final String customerIdentifier,
+      final String documentIdentifier) {
+    final List<Integer> pageNumbers = findPageNumbers(customerIdentifier, documentIdentifier)
+        .sorted(Integer::compareTo)
+        .collect(Collectors.toList());
+    for (int i = 0; i < pageNumbers.size(); i++) {
+      if (i != pageNumbers.get(i))
+        return true;
+    }
+
+    return false;
+  }
+}
\ No newline at end of file
diff --git a/service/src/main/java/io/mifos/customer/service/rest/config/CustomerRestConfiguration.java b/service/src/main/java/io/mifos/customer/service/rest/config/CustomerRestConfiguration.java
index e426f15..51b2494 100644
--- a/service/src/main/java/io/mifos/customer/service/rest/config/CustomerRestConfiguration.java
+++ b/service/src/main/java/io/mifos/customer/service/rest/config/CustomerRestConfiguration.java
@@ -29,6 +29,7 @@ import io.mifos.customer.service.internal.config.CustomerServiceConfiguration;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
 import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.ComponentScan;
@@ -54,6 +55,7 @@ import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter
     CatalogRestConfiguration.class,
     CustomerServiceConfiguration.class
 })
+@EnableConfigurationProperties({UploadProperties.class})
 public class CustomerRestConfiguration extends WebMvcConfigurerAdapter {
 
   public CustomerRestConfiguration() {
diff --git a/service/src/main/java/io/mifos/customer/service/rest/config/UploadProperties.java b/service/src/main/java/io/mifos/customer/service/rest/config/UploadProperties.java
new file mode 100644
index 0000000..0503cf0
--- /dev/null
+++ b/service/src/main/java/io/mifos/customer/service/rest/config/UploadProperties.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2017 The Mifos Initiative.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.mifos.customer.service.rest.config;
+
+import org.hibernate.validator.constraints.Range;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.stereotype.Component;
+import org.springframework.validation.annotation.Validated;
+
+import javax.validation.Valid;
+
+/**
+ * @author Myrle Krantz
+ */
+@Component
+@ConfigurationProperties(prefix="upload")
+@Validated
+public class UploadProperties {
+  @Valid
+  private final Image image = new Image();
+
+  public static class Image {
+    @Range(min = 0L)
+    private long maxSize;
+
+    public long getMaxSize() {
+      return maxSize;
+    }
+
+    public void setMaxSize(long maxSize) {
+      this.maxSize = maxSize;
+    }
+  }
+
+  public Image getImage() {
+    return image;
+  }
+}
diff --git a/service/src/main/java/io/mifos/customer/service/rest/controller/DocumentsRestController.java b/service/src/main/java/io/mifos/customer/service/rest/controller/DocumentsRestController.java
new file mode 100644
index 0000000..8cd97d8
--- /dev/null
+++ b/service/src/main/java/io/mifos/customer/service/rest/controller/DocumentsRestController.java
@@ -0,0 +1,260 @@
+/*
+ * Copyright 2017 The Mifos Initiative.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.mifos.customer.service.rest.controller;
+
+import io.mifos.anubis.annotation.AcceptedTokenType;
+import io.mifos.anubis.annotation.Permittable;
+import io.mifos.core.command.gateway.CommandGateway;
+import io.mifos.core.lang.ServiceException;
+import io.mifos.customer.PermittableGroupIds;
+import io.mifos.customer.api.v1.domain.CustomerDocument;
+import io.mifos.customer.service.internal.command.CompleteDocumentCommand;
+import io.mifos.customer.service.internal.command.CreateDocumentCommand;
+import io.mifos.customer.service.internal.command.CreateDocumentPageCommand;
+import io.mifos.customer.service.internal.command.DeleteDocumentPageCommand;
+import io.mifos.customer.service.internal.repository.DocumentPageEntity;
+import io.mifos.customer.service.internal.service.CustomerService;
+import io.mifos.customer.service.internal.service.DocumentService;
+import org.hibernate.validator.constraints.Range;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.MediaType;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.web.multipart.MultipartFile;
+
+import javax.validation.Valid;
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * @author Myrle Krantz
+ */
+@RestController
+@RequestMapping("/customers/{customeridentifier}/documents")
+public class DocumentsRestController {
+  private final CommandGateway commandGateway;
+  private final CustomerService customerService;
+  private final DocumentService documentService;
+
+  @Autowired
+  public DocumentsRestController(
+      final CommandGateway commandGateway,
+      final CustomerService customerService,
+      final DocumentService documentService) {
+    this.commandGateway = commandGateway;
+    this.customerService = customerService;
+    this.documentService = documentService;
+  }
+
+  @Permittable(value = AcceptedTokenType.TENANT, groupId = PermittableGroupIds.DOCUMENTS)
+  @RequestMapping(
+      method = RequestMethod.GET,
+      produces = MediaType.APPLICATION_JSON_VALUE,
+      consumes = MediaType.ALL_VALUE
+  )
+  public ResponseEntity<List<CustomerDocument>> getDocuments(
+      @PathVariable("customeridentifier") final String customerIdentifier) {
+    throwIfCustomerNotExists(customerIdentifier);
+
+    return ResponseEntity.ok(documentService.find(customerIdentifier).collect(Collectors.toList()));
+  }
+
+
+  @Permittable(value = AcceptedTokenType.TENANT, groupId = PermittableGroupIds.DOCUMENTS)
+  @RequestMapping(
+      value = "/{documentidentifier}",
+      method = RequestMethod.GET,
+      produces = MediaType.APPLICATION_JSON_VALUE,
+      consumes = MediaType.ALL_VALUE
+  )
+  public ResponseEntity<CustomerDocument> getDocument(
+      @PathVariable("customeridentifier") final String customerIdentifier,
+      @PathVariable("documentidentifier") final String documentIdentifier) {
+    return ResponseEntity
+        .ok(documentService.findDocument(customerIdentifier, documentIdentifier)
+            .orElseThrow(() -> ServiceException.notFound("Document ''{0}'' for customer ''{1}'' not found.",
+                documentIdentifier, customerIdentifier)));
+  }
+
+
+  @Permittable(value = AcceptedTokenType.TENANT, groupId = PermittableGroupIds.DOCUMENTS)
+  @RequestMapping(
+      value = "/{documentidentifier}",
+      method = RequestMethod.POST,
+      produces = MediaType.APPLICATION_JSON_VALUE,
+      consumes = MediaType.APPLICATION_JSON_VALUE
+  )
+  public @ResponseBody
+  ResponseEntity<Void> createDocument(
+      @PathVariable("customeridentifier") final String customerIdentifier,
+      @PathVariable("documentidentifier") final String documentIdentifier,
+      @RequestBody final @Valid CustomerDocument instance) {
+    throwIfCustomerNotExists(customerIdentifier);
+
+    if (!instance.getIdentifier().equals(documentIdentifier))
+      throw ServiceException.badRequest("Document identifier in request body must match document identifier in request path.");
+
+    commandGateway.process(new CreateDocumentCommand(customerIdentifier, instance));
+
+    return ResponseEntity.accepted().build();
+  }
+
+
+  @Permittable(value = AcceptedTokenType.TENANT, groupId = PermittableGroupIds.DOCUMENTS)
+  @RequestMapping(
+      value = "/{documentidentifier}/completed",
+      method = RequestMethod.POST,
+      produces = MediaType.APPLICATION_JSON_VALUE,
+      consumes = MediaType.APPLICATION_JSON_VALUE
+  )
+  public @ResponseBody
+  ResponseEntity<Void> completeDocument(
+      @PathVariable("customeridentifier") final String customerIdentifier,
+      @PathVariable("documentidentifier") final String documentIdentifier,
+      @RequestBody final @Valid Boolean completed) {
+    throwIfCustomerDocumentNotExists(customerIdentifier, documentIdentifier);
+
+    if (!completed)
+      throwIfDocumentCompleted(customerIdentifier, documentIdentifier);
+
+    throwIfPagesMissing(customerIdentifier, documentIdentifier);
+
+    if (completed)
+      commandGateway.process(new CompleteDocumentCommand(customerIdentifier, documentIdentifier));
+
+    return ResponseEntity.accepted().build();
+  }
+
+
+  @Permittable(value = AcceptedTokenType.TENANT, groupId = PermittableGroupIds.DOCUMENTS)
+  @RequestMapping(
+      value = "/{documentidentifier}/pages",
+      method = RequestMethod.GET,
+      produces = MediaType.APPLICATION_JSON_VALUE,
+      consumes = MediaType.ALL_VALUE
+  )
+  public @ResponseBody
+  ResponseEntity<List<Integer>> getDocumentPageNumbers(
+      @PathVariable("customeridentifier") final String customerIdentifier,
+      @PathVariable("documentidentifier") final String documentIdentifier) {
+    throwIfCustomerDocumentNotExists(customerIdentifier, documentIdentifier);
+
+    return ResponseEntity.ok(documentService.findPageNumbers(customerIdentifier, documentIdentifier).collect(Collectors.toList()));
+  }
+
+
+  @Permittable(value = AcceptedTokenType.TENANT, groupId = PermittableGroupIds.DOCUMENTS)
+  @RequestMapping(
+      value = "/{documentidentifier}/pages/{pagenumber}",
+      method = RequestMethod.GET,
+      produces = MediaType.APPLICATION_JSON_VALUE,
+      consumes = MediaType.ALL_VALUE
+  )
+  public ResponseEntity<byte[]> getDocumentPage(
+      @PathVariable("customeridentifier") final String customerIdentifier,
+      @PathVariable("documentidentifier") final String documentIdentifier,
+      @PathVariable("pagenumber") final Integer pageNumber) {
+    final DocumentPageEntity documentPageEntity = documentService.findPage(customerIdentifier, documentIdentifier, pageNumber)
+        .orElseThrow(() -> ServiceException.notFound("Page ''{0}'' of document ''{1}'' for customer ''{2}'' not found.",
+            pageNumber, documentIdentifier, customerIdentifier));
+
+    return ResponseEntity
+        .ok()
+        .contentType(MediaType.parseMediaType(documentPageEntity.getContentType()))
+        .contentLength(documentPageEntity.getImage().length)
+        .body(documentPageEntity.getImage());
+  }
+
+
+  @Permittable(value = AcceptedTokenType.TENANT, groupId = PermittableGroupIds.DOCUMENTS)
+  @RequestMapping(
+      value = "/{documentidentifier}/pages/{pagenumber}",
+      method = RequestMethod.POST,
+      produces = MediaType.APPLICATION_JSON_VALUE,
+      consumes = MediaType.MULTIPART_FORM_DATA_VALUE
+  )
+  public @ResponseBody
+  ResponseEntity<Void> createDocumentPage(
+      @PathVariable("customeridentifier") final String customerIdentifier,
+      @PathVariable("documentidentifier") final String documentIdentifier,
+      @PathVariable("pagenumber") @Range(min=0) final Integer pageNumber,
+      @RequestBody final MultipartFile page) {
+    if(page == null) {
+      throw ServiceException.badRequest("Document not found");
+    }
+
+    throwIfCustomerNotExists(customerIdentifier);
+    throwIfDocumentCompleted(customerIdentifier, documentIdentifier);
+    throwIfInvalidContentType(page.getContentType());
+
+    commandGateway.process(new CreateDocumentPageCommand(customerIdentifier, documentIdentifier, pageNumber, page));
+
+    return ResponseEntity.accepted().build();
+  }
+
+  @Permittable(value = AcceptedTokenType.TENANT, groupId = PermittableGroupIds.DOCUMENTS)
+  @RequestMapping(
+      value = "/{documentidentifier}/pages/{pagenumber}",
+      method = RequestMethod.DELETE,
+      produces = MediaType.APPLICATION_JSON_VALUE,
+      consumes = MediaType.ALL_VALUE
+  )
+  public @ResponseBody
+  ResponseEntity<Void>  deleteDocumentPage(
+      @PathVariable("customeridentifier") final String customerIdentifier,
+      @PathVariable("documentidentifier") final String documentIdentifier,
+      @PathVariable("pagenumber") final Integer pageNumber) {
+    throwIfCustomerDocumentNotExists(customerIdentifier, documentIdentifier);
+
+    throwIfDocumentCompleted(customerIdentifier, documentIdentifier);
+
+    commandGateway.process(new DeleteDocumentPageCommand(customerIdentifier, documentIdentifier, pageNumber));
+
+    return ResponseEntity.accepted().build();
+  }
+
+  private void throwIfCustomerNotExists(final String customerIdentifier) {
+    if (!this.customerService.customerExists(customerIdentifier)) {
+      throw ServiceException.notFound("Customer ''{0}'' not found.", customerIdentifier);
+    }
+  }
+
+  private void throwIfCustomerDocumentNotExists(final String customerIdentifier, final String documentIdentifier) {
+    if (!this.documentService.documentExists(customerIdentifier, documentIdentifier)) {
+      throw ServiceException.notFound("Customer ''{0}'' not found.", customerIdentifier);
+    }
+  }
+
+  private void throwIfInvalidContentType(final String contentType) {
+    if(!contentType.contains(MediaType.IMAGE_JPEG_VALUE)
+        && !contentType.contains(MediaType.IMAGE_PNG_VALUE)) {
+      throw ServiceException.badRequest("Image has contentType ''{0}'', but only content types ''{1}'' and ''{2}'' allowed.",
+          contentType, MediaType.IMAGE_JPEG_VALUE, MediaType.IMAGE_PNG_VALUE);
+    }
+  }
+
+  private void throwIfDocumentCompleted(final String customerIdentifier, final String documentIdentifier) {
+    if (documentService.isDocumentCompleted(customerIdentifier, documentIdentifier))
+      throw ServiceException.conflict("The document ''{0}'' for customer ''{1}'' is completed and cannot be uncompleted.",
+          documentIdentifier, customerIdentifier);
+  }
+
+  private void throwIfPagesMissing(final String customerIdentifier, final String documentIdentifier) {
+    if (documentService.isDocumentMissingPages(customerIdentifier, documentIdentifier))
+      throw ServiceException.badRequest("The document ''{0}'' for customer ''{1}'' is missing pages.",
+          documentIdentifier, customerIdentifier);
+  }
+}
diff --git a/service/src/main/resources/db/migrations/mariadb/V7__documents.sql b/service/src/main/resources/db/migrations/mariadb/V7__documents.sql
new file mode 100644
index 0000000..fecaa31
--- /dev/null
+++ b/service/src/main/resources/db/migrations/mariadb/V7__documents.sql
@@ -0,0 +1,39 @@
+--
+-- Copyright 2017 The Mifos Initiative.
+--
+-- Licensed under the Apache License, Version 2.0 (the "License");
+-- you may not use this file except in compliance with the License.
+-- You may obtain a copy of the License at
+--
+--    http://www.apache.org/licenses/LICENSE-2.0
+--
+-- Unless required by applicable law or agreed to in writing, software
+-- distributed under the License is distributed on an "AS IS" BASIS,
+-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+-- See the License for the specific language governing permissions and
+-- limitations under the License.
+--
+
+CREATE TABLE maat_documents (
+  id BIGINT NOT NULL AUTO_INCREMENT,
+  customer_id BIGINT NOT NULL,
+  identifier VARCHAR(32) NOT NULL,
+  is_completed BOOLEAN NOT NULL,
+  created_on TIMESTAMP(3) NOT NULL,
+  created_by VARCHAR(32) NOT NULL,
+  CONSTRAINT maat_documents_pk PRIMARY KEY (id),
+  CONSTRAINT maat_documents_uq UNIQUE (customer_id, identifier),
+  CONSTRAINT maat_documents_fk FOREIGN KEY (customer_id) REFERENCES maat_customers (id) ON UPDATE RESTRICT
+);
+
+CREATE TABLE maat_document_pages (
+  id BIGINT NOT NULL AUTO_INCREMENT,
+  document_id BIGINT NOT NULL,
+  page_number INT NOT NULL,
+  content_type VARCHAR(256) NOT NULL,
+  size BIGINT NOT NULL,
+  image MEDIUMBLOB NOT NULL,
+  CONSTRAINT maat_document_pages_pk PRIMARY KEY (id),
+  CONSTRAINT maat_document_pages_uq UNIQUE (document_id, page_number),
+  CONSTRAINT maat_document_pages_fk FOREIGN KEY (document_id) REFERENCES maat_documents (id)
+);

-- 
To stop receiving notification emails like this one, please contact
myrle@apache.org.

[fineract-cn-customer] 27/46: added delete and update for catalogs and fields

Posted by my...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

myrle pushed a commit to branch develop
in repository https://gitbox.apache.org/repos/asf/fineract-cn-customer.git

commit 02cda5acd170614eaa3ddd37c4ec0c801ea050cd
Author: mgeiss <mg...@mifos.org>
AuthorDate: Wed Sep 27 11:34:33 2017 +0200

    added delete and update for catalogs and fields
---
 .../catalog/api/v1/CatalogEventConstants.java      |   7 +-
 .../catalog/api/v1/client/CatalogManager.java      |  44 +++++++
 .../CatalogNotFoundException.java}                 |  12 +-
 .../io/mifos/customer/catalog/TestCatalog.java     | 144 +++++++++++++++++++++
 .../catalog/listener/CatalogEventListener.java     |  30 ++++-
 .../internal/command/ChangeFieldCommand.java       |  22 +++-
 .../internal/command/DeleteCatalogCommand.java     |  18 +--
 .../internal/command/DeleteFieldCommand.java       |  22 +++-
 .../internal/command/handler/CatalogAggregate.java |  82 +++++++++++-
 .../internal/repository/FieldValueRepository.java  |   3 +
 .../internal/repository/OptionRepository.java      |  12 +-
 .../service/internal/service/CatalogService.java   |  40 +++++-
 .../rest/controller/CatalogRestController.java     |  65 ++++++++++
 13 files changed, 458 insertions(+), 43 deletions(-)

diff --git a/api/src/main/java/io/mifos/customer/catalog/api/v1/CatalogEventConstants.java b/api/src/main/java/io/mifos/customer/catalog/api/v1/CatalogEventConstants.java
index 97b4955..7226fa0 100644
--- a/api/src/main/java/io/mifos/customer/catalog/api/v1/CatalogEventConstants.java
+++ b/api/src/main/java/io/mifos/customer/catalog/api/v1/CatalogEventConstants.java
@@ -22,6 +22,11 @@ public interface CatalogEventConstants {
   String SELECTOR_NAME = "action";
 
   String POST_CATALOG = "post-catalog";
-
   String SELECTOR_POST_CATALOG = SELECTOR_NAME + " = '" + POST_CATALOG + "'";
+  String DELETE_CATALOG = "delete-catalog";
+  String SELECTOR_DELETE_CATALOG = SELECTOR_NAME + " = '" + DELETE_CATALOG + "'";
+  String DELETE_FIELD = "delete-field";
+  String SELECTOR_DELETE_FIELD = SELECTOR_NAME + " = '" + DELETE_FIELD + "'";
+  String PUT_FIELD = "put-field";
+  String SELECTOR_PUT_FIELD = SELECTOR_NAME + " = '" + PUT_FIELD + "'";
 }
diff --git a/api/src/main/java/io/mifos/customer/catalog/api/v1/client/CatalogManager.java b/api/src/main/java/io/mifos/customer/catalog/api/v1/client/CatalogManager.java
index cc41531..edf7341 100644
--- a/api/src/main/java/io/mifos/customer/catalog/api/v1/client/CatalogManager.java
+++ b/api/src/main/java/io/mifos/customer/catalog/api/v1/client/CatalogManager.java
@@ -19,6 +19,7 @@ import io.mifos.core.api.annotation.ThrowsException;
 import io.mifos.core.api.annotation.ThrowsExceptions;
 import io.mifos.core.api.util.CustomFeignClientsConfiguration;
 import io.mifos.customer.catalog.api.v1.domain.Catalog;
+import io.mifos.customer.catalog.api.v1.domain.Field;
 import org.springframework.cloud.netflix.feign.FeignClient;
 import org.springframework.http.HttpStatus;
 import org.springframework.http.MediaType;
@@ -59,5 +60,48 @@ public interface CatalogManager {
       produces = MediaType.ALL_VALUE,
       consumes = MediaType.APPLICATION_JSON_VALUE
   )
+  @ThrowsExceptions({
+      @ThrowsException(status = HttpStatus.NOT_FOUND, exception = CatalogNotFoundException.class)
+  })
   Catalog findCatalog(@PathVariable("identifier") final String identifier);
+
+  @RequestMapping(
+      path = "/catalogs/{identifier}",
+      method = RequestMethod.DELETE,
+      produces = MediaType.ALL_VALUE,
+      consumes = MediaType.APPLICATION_JSON_VALUE
+  )
+  @ThrowsExceptions({
+      @ThrowsException(status = HttpStatus.NOT_FOUND, exception = CatalogNotFoundException.class),
+      @ThrowsException(status = HttpStatus.BAD_REQUEST, exception = CatalogValidationException.class)
+  })
+  void deleteCatalog(@PathVariable("identifier") final String identifier);
+
+  @RequestMapping(
+      path = "/catalogs/{catalogIdentifier}/fields/{fieldIdentifier}",
+      method = RequestMethod.PUT,
+      produces = MediaType.APPLICATION_JSON_VALUE,
+      consumes = MediaType.APPLICATION_JSON_VALUE
+  )
+  @ThrowsExceptions({
+      @ThrowsException(status = HttpStatus.NOT_FOUND, exception = CatalogNotFoundException.class),
+      @ThrowsException(status = HttpStatus.BAD_REQUEST, exception = CatalogValidationException.class)
+  })
+  void updateField(@PathVariable("catalogIdentifier") final String catalogIdentifier,
+                   @PathVariable("fieldIdentifier") final String fieldIdentifier,
+                   @RequestBody Field field);
+
+  @RequestMapping(
+      path = "/catalogs/{catalogIdentifier}/fields/{fieldIdentifier}",
+      method = RequestMethod.DELETE,
+      produces = MediaType.ALL_VALUE,
+      consumes = MediaType.APPLICATION_JSON_VALUE
+  )
+  @ThrowsExceptions({
+      @ThrowsException(status = HttpStatus.NOT_FOUND, exception = CatalogNotFoundException.class),
+      @ThrowsException(status = HttpStatus.BAD_REQUEST, exception = CatalogValidationException.class)
+  })
+  void deleteField(@PathVariable("catalogIdentifier") final String catalogIdentifier,
+                   @PathVariable("fieldIdentifier") final String fieldIdentifier);
+
 }
diff --git a/api/src/main/java/io/mifos/customer/catalog/api/v1/CatalogEventConstants.java b/api/src/main/java/io/mifos/customer/catalog/api/v1/client/CatalogNotFoundException.java
similarity index 69%
copy from api/src/main/java/io/mifos/customer/catalog/api/v1/CatalogEventConstants.java
copy to api/src/main/java/io/mifos/customer/catalog/api/v1/client/CatalogNotFoundException.java
index 97b4955..ac58730 100644
--- a/api/src/main/java/io/mifos/customer/catalog/api/v1/CatalogEventConstants.java
+++ b/api/src/main/java/io/mifos/customer/catalog/api/v1/client/CatalogNotFoundException.java
@@ -13,15 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package io.mifos.customer.catalog.api.v1;
+package io.mifos.customer.catalog.api.v1.client;
 
-public interface CatalogEventConstants {
-
-  String DESTINATION = "nun-v1";
-
-  String SELECTOR_NAME = "action";
-
-  String POST_CATALOG = "post-catalog";
-
-  String SELECTOR_POST_CATALOG = SELECTOR_NAME + " = '" + POST_CATALOG + "'";
+public class CatalogNotFoundException extends RuntimeException {
 }
diff --git a/component-test/src/main/java/io/mifos/customer/catalog/TestCatalog.java b/component-test/src/main/java/io/mifos/customer/catalog/TestCatalog.java
index 2fe10c6..4fd679d 100644
--- a/component-test/src/main/java/io/mifos/customer/catalog/TestCatalog.java
+++ b/component-test/src/main/java/io/mifos/customer/catalog/TestCatalog.java
@@ -15,6 +15,7 @@
  */
 package io.mifos.customer.catalog;
 
+import com.google.common.collect.Lists;
 import io.mifos.anubis.test.v1.TenantApplicationSecurityEnvironmentTestRule;
 import io.mifos.core.api.context.AutoUserContext;
 import io.mifos.core.test.env.TestEnvironment;
@@ -28,8 +29,10 @@ import io.mifos.customer.api.v1.client.CustomerManager;
 import io.mifos.customer.api.v1.domain.Customer;
 import io.mifos.customer.catalog.api.v1.CatalogEventConstants;
 import io.mifos.customer.catalog.api.v1.client.CatalogManager;
+import io.mifos.customer.catalog.api.v1.client.CatalogValidationException;
 import io.mifos.customer.catalog.api.v1.domain.Catalog;
 import io.mifos.customer.catalog.api.v1.domain.Field;
+import io.mifos.customer.catalog.api.v1.domain.Option;
 import io.mifos.customer.catalog.api.v1.domain.Value;
 import io.mifos.customer.catalog.util.CatalogGenerator;
 import io.mifos.customer.service.rest.config.CustomerRestConfiguration;
@@ -54,6 +57,7 @@ import org.springframework.test.context.junit4.SpringRunner;
 
 import java.util.Arrays;
 import java.util.List;
+import java.util.Optional;
 import java.util.stream.Collectors;
 
 @RunWith(SpringRunner.class)
@@ -203,4 +207,144 @@ public class TestCatalog {
     final Customer savedCustomer = this.customerManager.findCustomer(randomCustomer.getIdentifier());
     Assert.assertTrue(savedCustomer.getCustomValues().size() == 2);
   }
+
+  @Test
+  public void shouldDeleteCatalog() throws Exception {
+    final Catalog catalog = CatalogGenerator.createRandomCatalog();
+
+    this.catalogManager.createCatalog(catalog);
+    this.eventRecorder.wait(CatalogEventConstants.POST_CATALOG, catalog.getIdentifier());
+
+    this.catalogManager.deleteCatalog(catalog.getIdentifier());
+    Assert.assertTrue(this.eventRecorder.wait(CatalogEventConstants.DELETE_CATALOG, catalog.getIdentifier()));
+  }
+
+  @Test(expected = CatalogValidationException.class)
+  public void shouldNotDeleteCatalogUsed() throws Exception {
+    final Catalog catalog = CatalogGenerator.createRandomCatalog();
+
+    this.catalogManager.createCatalog(catalog);
+    this.eventRecorder.wait(CatalogEventConstants.POST_CATALOG, catalog.getIdentifier());
+
+    final Customer randomCustomer = CustomerGenerator.createRandomCustomer();
+    randomCustomer.setCustomValues(catalog.getFields()
+        .stream()
+        .map(field -> {
+          final Value value = new Value();
+          value.setCatalogIdentifier(catalog.getIdentifier());
+          value.setFieldIdentifier(field.getIdentifier());
+          switch (Field.DataType.valueOf(field.getDataType())) {
+            case NUMBER:
+              value.setValue("25.00");
+              break;
+            case SINGLE_SELECTION:
+              value.setValue("1");
+          }
+          return value;
+        })
+        .collect(Collectors.toList())
+    );
+
+    this.customerManager.createCustomer(randomCustomer);
+    this.eventRecorder.wait(CustomerEventConstants.POST_CUSTOMER, randomCustomer.getIdentifier());
+
+    this.catalogManager.deleteCatalog(catalog.getIdentifier());
+  }
+
+  @Test
+  public void shouldDeleteField() throws Exception {
+    final Catalog catalog = CatalogGenerator.createRandomCatalog();
+
+    this.catalogManager.createCatalog(catalog);
+    this.eventRecorder.wait(CatalogEventConstants.POST_CATALOG, catalog.getIdentifier());
+
+    final Optional<Field> optionalField = catalog.getFields().stream().findFirst();
+    optionalField.ifPresent(field -> {
+      this.catalogManager.deleteField(catalog.getIdentifier(), field.getIdentifier());
+      try {
+        Assert.assertTrue(this.eventRecorder.wait(CatalogEventConstants.DELETE_FIELD, field.getIdentifier()));
+      } catch (final InterruptedException iex) {
+        Assert.fail(iex.getMessage());
+      }
+    });
+
+    final Catalog savedCatalog = this.catalogManager.findCatalog(catalog.getIdentifier());
+    Assert.assertEquals(1, savedCatalog.getFields().size());
+  }
+
+  @Test(expected = CatalogValidationException.class)
+  public void shouldNotDeleteField() throws Exception {
+    final Catalog catalog = CatalogGenerator.createRandomCatalog();
+
+    this.catalogManager.createCatalog(catalog);
+    this.eventRecorder.wait(CatalogEventConstants.POST_CATALOG, catalog.getIdentifier());
+
+    final Customer randomCustomer = CustomerGenerator.createRandomCustomer();
+    randomCustomer.setCustomValues(catalog.getFields()
+        .stream()
+        .map(field -> {
+          final Value value = new Value();
+          value.setCatalogIdentifier(catalog.getIdentifier());
+          value.setFieldIdentifier(field.getIdentifier());
+          switch (Field.DataType.valueOf(field.getDataType())) {
+            case NUMBER:
+              value.setValue("37.00");
+              break;
+            case SINGLE_SELECTION:
+              value.setValue("1");
+          }
+          return value;
+        })
+        .collect(Collectors.toList())
+    );
+
+    this.customerManager.createCustomer(randomCustomer);
+    this.eventRecorder.wait(CustomerEventConstants.POST_CUSTOMER, randomCustomer.getIdentifier());
+
+    final Optional<Field> optionalField = catalog.getFields().stream().findFirst();
+    optionalField.ifPresent(field -> this.catalogManager.deleteField(catalog.getIdentifier(), field.getIdentifier()));
+  }
+
+  @Test
+  public void shouldUpdateField() throws Exception {
+    final Catalog catalog = CatalogGenerator.createRandomCatalog();
+
+    this.catalogManager.createCatalog(catalog);
+    this.eventRecorder.wait(CatalogEventConstants.POST_CATALOG, catalog.getIdentifier());
+
+    final Optional<Field> optionalField = catalog.getFields()
+        .stream()
+        .filter(field -> field.getDataType().equals(Field.DataType.SINGLE_SELECTION.name()))
+        .findFirst();
+
+    optionalField.ifPresent(field -> {
+      final Option option = new Option();
+      option.setLabel("new-option");
+      option.setValue(2);
+      field.setOptions(Lists.newArrayList(option));
+      this.catalogManager.updateField(catalog.getIdentifier(), field.getIdentifier(), field);
+      try {
+        Assert.assertTrue(this.eventRecorder.wait(CatalogEventConstants.PUT_FIELD, field.getIdentifier()));
+      } catch (final InterruptedException iex) {
+        Assert.fail(iex.getMessage());
+      }
+    });
+
+    final Catalog savedCatalog = this.catalogManager.findCatalog(catalog.getIdentifier());
+    final Optional<Field> optionalFetchedField = savedCatalog.getFields()
+        .stream()
+        .filter(field -> field.getDataType().equals(Field.DataType.SINGLE_SELECTION.name()))
+        .findFirst();
+
+    if (optionalFetchedField.isPresent()) {
+      final Field field = optionalFetchedField.get();
+      Assert.assertEquals(1, field.getOptions().size());
+      final Optional<Option> optionalOption = field.getOptions().stream().findFirst();
+      Assert.assertTrue(optionalOption.isPresent());
+      final Option option = optionalOption.get();
+      Assert.assertEquals(Integer.valueOf(2), option.getValue());
+    } else {
+      Assert.fail();
+    }
+  }
 }
diff --git a/component-test/src/main/java/io/mifos/customer/catalog/listener/CatalogEventListener.java b/component-test/src/main/java/io/mifos/customer/catalog/listener/CatalogEventListener.java
index ad4575d..36d08ee 100644
--- a/component-test/src/main/java/io/mifos/customer/catalog/listener/CatalogEventListener.java
+++ b/component-test/src/main/java/io/mifos/customer/catalog/listener/CatalogEventListener.java
@@ -42,4 +42,32 @@ public class CatalogEventListener {
   public void customerCreatedEvent(@Header(TenantHeaderFilter.TENANT_HEADER) final String tenant,
                                    final String payload) {
     this.eventRecorder.event(tenant, CatalogEventConstants.POST_CATALOG, payload, String.class);
-  }}
+  }
+
+  @JmsListener(
+      destination = CustomerEventConstants.DESTINATION,
+      selector = CatalogEventConstants.SELECTOR_DELETE_CATALOG
+  )
+  public void onDeleteCatalog(@Header(TenantHeaderFilter.TENANT_HEADER) final String tenant,
+                              final String payload) {
+    this.eventRecorder.event(tenant, CatalogEventConstants.DELETE_CATALOG, payload, String.class);
+  }
+
+  @JmsListener(
+      destination = CustomerEventConstants.DESTINATION,
+      selector = CatalogEventConstants.SELECTOR_DELETE_FIELD
+  )
+  public void onDeleteField(@Header(TenantHeaderFilter.TENANT_HEADER) final String tenant,
+                            final String payload) {
+    this.eventRecorder.event(tenant, CatalogEventConstants.DELETE_FIELD, payload, String.class);
+  }
+
+  @JmsListener(
+      destination = CustomerEventConstants.DESTINATION,
+      selector = CatalogEventConstants.SELECTOR_PUT_FIELD
+  )
+  public void onChangeField(@Header(TenantHeaderFilter.TENANT_HEADER) final String tenant,
+                            final String payload) {
+    this.eventRecorder.event(tenant, CatalogEventConstants.PUT_FIELD, payload, String.class);
+  }
+}
diff --git a/api/src/main/java/io/mifos/customer/catalog/api/v1/CatalogEventConstants.java b/service/src/main/java/io/mifos/customer/catalog/service/internal/command/ChangeFieldCommand.java
similarity index 54%
copy from api/src/main/java/io/mifos/customer/catalog/api/v1/CatalogEventConstants.java
copy to service/src/main/java/io/mifos/customer/catalog/service/internal/command/ChangeFieldCommand.java
index 97b4955..a0a100e 100644
--- a/api/src/main/java/io/mifos/customer/catalog/api/v1/CatalogEventConstants.java
+++ b/service/src/main/java/io/mifos/customer/catalog/service/internal/command/ChangeFieldCommand.java
@@ -13,15 +13,25 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package io.mifos.customer.catalog.api.v1;
+package io.mifos.customer.catalog.service.internal.command;
 
-public interface CatalogEventConstants {
+import io.mifos.customer.catalog.api.v1.domain.Field;
 
-  String DESTINATION = "nun-v1";
+public class ChangeFieldCommand {
+  private final String catalogIdentifier;
+  private final Field field;
 
-  String SELECTOR_NAME = "action";
+  public ChangeFieldCommand(final String catalogIdentifier, final Field field) {
+    super();
+    this.catalogIdentifier = catalogIdentifier;
+    this.field = field;
+  }
 
-  String POST_CATALOG = "post-catalog";
+  public String catalogIdentifier() {
+    return this.catalogIdentifier;
+  }
 
-  String SELECTOR_POST_CATALOG = SELECTOR_NAME + " = '" + POST_CATALOG + "'";
+  public Field field() {
+    return this.field;
+  }
 }
diff --git a/api/src/main/java/io/mifos/customer/catalog/api/v1/CatalogEventConstants.java b/service/src/main/java/io/mifos/customer/catalog/service/internal/command/DeleteCatalogCommand.java
similarity index 66%
copy from api/src/main/java/io/mifos/customer/catalog/api/v1/CatalogEventConstants.java
copy to service/src/main/java/io/mifos/customer/catalog/service/internal/command/DeleteCatalogCommand.java
index 97b4955..1545b09 100644
--- a/api/src/main/java/io/mifos/customer/catalog/api/v1/CatalogEventConstants.java
+++ b/service/src/main/java/io/mifos/customer/catalog/service/internal/command/DeleteCatalogCommand.java
@@ -13,15 +13,17 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package io.mifos.customer.catalog.api.v1;
+package io.mifos.customer.catalog.service.internal.command;
 
-public interface CatalogEventConstants {
+public class DeleteCatalogCommand {
+  private final String identifier;
 
-  String DESTINATION = "nun-v1";
+  public DeleteCatalogCommand(final String identifier) {
+    super();
+    this.identifier = identifier;
+  }
 
-  String SELECTOR_NAME = "action";
-
-  String POST_CATALOG = "post-catalog";
-
-  String SELECTOR_POST_CATALOG = SELECTOR_NAME + " = '" + POST_CATALOG + "'";
+  public String identifier() {
+    return this.identifier;
+  }
 }
diff --git a/api/src/main/java/io/mifos/customer/catalog/api/v1/CatalogEventConstants.java b/service/src/main/java/io/mifos/customer/catalog/service/internal/command/DeleteFieldCommand.java
similarity index 53%
copy from api/src/main/java/io/mifos/customer/catalog/api/v1/CatalogEventConstants.java
copy to service/src/main/java/io/mifos/customer/catalog/service/internal/command/DeleteFieldCommand.java
index 97b4955..28c1cf5 100644
--- a/api/src/main/java/io/mifos/customer/catalog/api/v1/CatalogEventConstants.java
+++ b/service/src/main/java/io/mifos/customer/catalog/service/internal/command/DeleteFieldCommand.java
@@ -13,15 +13,23 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package io.mifos.customer.catalog.api.v1;
+package io.mifos.customer.catalog.service.internal.command;
 
-public interface CatalogEventConstants {
+public class DeleteFieldCommand {
+  private final String catalogIdentifier;
+  private final String fieldIdentifier;
 
-  String DESTINATION = "nun-v1";
+  public DeleteFieldCommand(final String catalogIdentifier, final String fieldIdentifier) {
+    super();
+    this.catalogIdentifier = catalogIdentifier;
+    this.fieldIdentifier = fieldIdentifier;
+  }
 
-  String SELECTOR_NAME = "action";
+  public String catalogIdentifier() {
+    return this.catalogIdentifier;
+  }
 
-  String POST_CATALOG = "post-catalog";
-
-  String SELECTOR_POST_CATALOG = SELECTOR_NAME + " = '" + POST_CATALOG + "'";
+  public String fieldIdentifier() {
+    return this.fieldIdentifier;
+  }
 }
diff --git a/service/src/main/java/io/mifos/customer/catalog/service/internal/command/handler/CatalogAggregate.java b/service/src/main/java/io/mifos/customer/catalog/service/internal/command/handler/CatalogAggregate.java
index c464e3f..449f503 100644
--- a/service/src/main/java/io/mifos/customer/catalog/service/internal/command/handler/CatalogAggregate.java
+++ b/service/src/main/java/io/mifos/customer/catalog/service/internal/command/handler/CatalogAggregate.java
@@ -20,17 +20,26 @@ import io.mifos.core.command.annotation.CommandHandler;
 import io.mifos.core.command.annotation.EventEmitter;
 import io.mifos.customer.catalog.api.v1.CatalogEventConstants;
 import io.mifos.customer.catalog.api.v1.domain.Catalog;
+import io.mifos.customer.catalog.api.v1.domain.Field;
+import io.mifos.customer.catalog.service.internal.command.ChangeFieldCommand;
 import io.mifos.customer.catalog.service.internal.command.CreateCatalogCommand;
+import io.mifos.customer.catalog.service.internal.command.DeleteCatalogCommand;
+import io.mifos.customer.catalog.service.internal.command.DeleteFieldCommand;
 import io.mifos.customer.catalog.service.internal.mapper.CatalogMapper;
 import io.mifos.customer.catalog.service.internal.mapper.FieldMapper;
+import io.mifos.customer.catalog.service.internal.mapper.OptionMapper;
 import io.mifos.customer.catalog.service.internal.repository.CatalogEntity;
 import io.mifos.customer.catalog.service.internal.repository.CatalogRepository;
+import io.mifos.customer.catalog.service.internal.repository.FieldEntity;
+import io.mifos.customer.catalog.service.internal.repository.FieldRepository;
+import io.mifos.customer.catalog.service.internal.repository.OptionRepository;
 import io.mifos.customer.service.ServiceConstants;
 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 java.util.Optional;
 import java.util.stream.Collectors;
 
 @Aggregate
@@ -38,13 +47,19 @@ public class CatalogAggregate {
 
   private final Logger logger;
   private final CatalogRepository catalogRepository;
+  private final FieldRepository fieldRepository;
+  private final OptionRepository optionRepository;
 
   @Autowired
   public CatalogAggregate(@Qualifier(ServiceConstants.LOGGER_NAME) final Logger logger,
-                          final CatalogRepository catalogRepository) {
+                          final CatalogRepository catalogRepository,
+                          final FieldRepository fieldRepository,
+                          final OptionRepository optionRepository) {
     super();
     this.logger = logger;
     this.catalogRepository = catalogRepository;
+    this.fieldRepository = fieldRepository;
+    this.optionRepository = optionRepository;
   }
 
   @Transactional
@@ -61,4 +76,69 @@ public class CatalogAggregate {
     this.catalogRepository.save(catalogEntity);
     return catalog.getIdentifier();
   }
+
+  @Transactional
+  @CommandHandler
+  @EventEmitter(selectorName = CatalogEventConstants.SELECTOR_NAME, selectorValue = CatalogEventConstants.DELETE_CATALOG)
+  public String process(final DeleteCatalogCommand deleteCatalogCommand) {
+    final Optional<CatalogEntity> optionalCatalog = this.catalogRepository.findByIdentifier(deleteCatalogCommand.identifier());
+    if (optionalCatalog.isPresent()) {
+      this.catalogRepository.delete(optionalCatalog.get());
+      return deleteCatalogCommand.identifier();
+    }
+    return null;
+  }
+
+  @Transactional
+  @CommandHandler
+  @EventEmitter(selectorName = CatalogEventConstants.SELECTOR_NAME, selectorValue = CatalogEventConstants.DELETE_FIELD)
+  public String process(final DeleteFieldCommand deleteFieldCommand) {
+    final Optional<CatalogEntity> optionalCatalog = this.catalogRepository.findByIdentifier(deleteFieldCommand.catalogIdentifier());
+    if (optionalCatalog.isPresent()) {
+      final Optional<FieldEntity> optionalField =
+          this.fieldRepository.findByCatalogAndIdentifier(optionalCatalog.get(), deleteFieldCommand.fieldIdentifier());
+      if (optionalField.isPresent()) {
+        this.fieldRepository.delete(optionalField.get());
+        return deleteFieldCommand.fieldIdentifier();
+      }
+    }
+    return null;
+  }
+
+  @Transactional
+  @CommandHandler
+  @EventEmitter(selectorName = CatalogEventConstants.SELECTOR_NAME, selectorValue = CatalogEventConstants.PUT_FIELD)
+  public String process(final ChangeFieldCommand changeFieldCommand) {
+    final Optional<CatalogEntity> optionalCatalog = this.catalogRepository.findByIdentifier(changeFieldCommand.catalogIdentifier());
+    if (optionalCatalog.isPresent()) {
+      final Optional<FieldEntity> optionalField =
+          this.fieldRepository.findByCatalogAndIdentifier(optionalCatalog.get(), changeFieldCommand.field().getIdentifier());
+      if (optionalField.isPresent()) {
+        final FieldEntity fieldEntity = optionalField.get();
+
+        fieldEntity.setOptions(null);
+        final FieldEntity temporarySavedField = this.fieldRepository.saveAndFlush(fieldEntity);
+
+        this.optionRepository.deleteByField(temporarySavedField);
+        this.optionRepository.flush();
+
+        final Field field = changeFieldCommand.field();
+        temporarySavedField.setLabel(field.getLabel());
+        temporarySavedField.setHint(field.getHint());
+        temporarySavedField.setDescription(field.getDescription());
+        temporarySavedField.setMandatory(field.getMandatory());
+        if (field.getOptions() != null) {
+          temporarySavedField.setOptions(
+              field.getOptions()
+                  .stream()
+                  .map(option -> OptionMapper.map(temporarySavedField, option))
+                  .collect(Collectors.toList())
+          );
+        }
+        this.fieldRepository.save(temporarySavedField);
+        return changeFieldCommand.field().getIdentifier();
+      }
+    }
+    return null;
+  }
 }
diff --git a/service/src/main/java/io/mifos/customer/catalog/service/internal/repository/FieldValueRepository.java b/service/src/main/java/io/mifos/customer/catalog/service/internal/repository/FieldValueRepository.java
index af97cf6..01713b6 100644
--- a/service/src/main/java/io/mifos/customer/catalog/service/internal/repository/FieldValueRepository.java
+++ b/service/src/main/java/io/mifos/customer/catalog/service/internal/repository/FieldValueRepository.java
@@ -20,6 +20,7 @@ import org.springframework.data.jpa.repository.JpaRepository;
 import org.springframework.stereotype.Repository;
 
 import java.util.List;
+import java.util.Optional;
 
 @Repository
 public interface FieldValueRepository extends JpaRepository<FieldValueEntity, Long> {
@@ -27,4 +28,6 @@ public interface FieldValueRepository extends JpaRepository<FieldValueEntity, Lo
   List<FieldValueEntity> findByCustomer(final CustomerEntity customer);
 
   void deleteByCustomer(final CustomerEntity customer);
+
+  Optional<FieldValueEntity> findByField(final FieldEntity fieldEntity);
 }
diff --git a/api/src/main/java/io/mifos/customer/catalog/api/v1/CatalogEventConstants.java b/service/src/main/java/io/mifos/customer/catalog/service/internal/repository/OptionRepository.java
similarity index 69%
copy from api/src/main/java/io/mifos/customer/catalog/api/v1/CatalogEventConstants.java
copy to service/src/main/java/io/mifos/customer/catalog/service/internal/repository/OptionRepository.java
index 97b4955..d5d1bf3 100644
--- a/api/src/main/java/io/mifos/customer/catalog/api/v1/CatalogEventConstants.java
+++ b/service/src/main/java/io/mifos/customer/catalog/service/internal/repository/OptionRepository.java
@@ -13,15 +13,11 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package io.mifos.customer.catalog.api.v1;
+package io.mifos.customer.catalog.service.internal.repository;
 
-public interface CatalogEventConstants {
+import org.springframework.data.jpa.repository.JpaRepository;
 
-  String DESTINATION = "nun-v1";
+public interface OptionRepository extends JpaRepository<OptionEntity, Long> {
 
-  String SELECTOR_NAME = "action";
-
-  String POST_CATALOG = "post-catalog";
-
-  String SELECTOR_POST_CATALOG = SELECTOR_NAME + " = '" + POST_CATALOG + "'";
+  void deleteByField(final FieldEntity fieldEntity);
 }
diff --git a/service/src/main/java/io/mifos/customer/catalog/service/internal/service/CatalogService.java b/service/src/main/java/io/mifos/customer/catalog/service/internal/service/CatalogService.java
index 78cc511..0b395dc 100644
--- a/service/src/main/java/io/mifos/customer/catalog/service/internal/service/CatalogService.java
+++ b/service/src/main/java/io/mifos/customer/catalog/service/internal/service/CatalogService.java
@@ -15,16 +15,22 @@
  */
 package io.mifos.customer.catalog.service.internal.service;
 
+import io.mifos.core.lang.ServiceException;
 import io.mifos.customer.catalog.api.v1.domain.Catalog;
 import io.mifos.customer.catalog.service.internal.mapper.CatalogMapper;
 import io.mifos.customer.catalog.service.internal.mapper.FieldMapper;
+import io.mifos.customer.catalog.service.internal.repository.CatalogEntity;
 import io.mifos.customer.catalog.service.internal.repository.CatalogRepository;
+import io.mifos.customer.catalog.service.internal.repository.FieldEntity;
+import io.mifos.customer.catalog.service.internal.repository.FieldRepository;
+import io.mifos.customer.catalog.service.internal.repository.FieldValueRepository;
 import io.mifos.customer.service.ServiceConstants;
 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.ArrayList;
 import java.util.List;
 import java.util.Optional;
 import java.util.stream.Collectors;
@@ -34,13 +40,19 @@ public class CatalogService {
 
   private final Logger logger;
   private final CatalogRepository catalogRepository;
+  private final FieldRepository fieldRepository;
+  private final FieldValueRepository fieldValueRepository;
 
   @Autowired
   public CatalogService(@Qualifier(ServiceConstants.LOGGER_NAME) final Logger logger,
-                        final CatalogRepository catalogRepository) {
+                        final CatalogRepository catalogRepository,
+                        final FieldRepository fieldRepository,
+                        final FieldValueRepository fieldValueRepository) {
     super();
     this.logger = logger;
     this.catalogRepository = catalogRepository;
+    this.fieldRepository = fieldRepository;
+    this.fieldValueRepository = fieldValueRepository;
   }
 
   public Boolean catalogExists(final String identifier) {
@@ -76,4 +88,30 @@ public class CatalogService {
           return catalog;
         });
   }
+
+  public Boolean catalogInUse(final String identifier) {
+    final CatalogEntity catalogEntity = this.catalogRepository.findByIdentifier(identifier).orElseThrow(
+        () -> ServiceException.notFound("Catalog {0} not found.", identifier)
+    );
+
+    final ArrayList<Boolean> fieldUsageList = new ArrayList<>();
+    catalogEntity.getFields().forEach(fieldEntity -> {
+      fieldUsageList.add(this.fieldInUse(catalogEntity, fieldEntity.getIdentifier()));
+    });
+
+    return fieldUsageList.stream().anyMatch(aBoolean -> aBoolean.equals(Boolean.TRUE));
+  }
+
+  public Boolean fieldInUse(final String catalogIdentifier, final String fieldIdentifier) {
+    final CatalogEntity catalogEntity = this.catalogRepository.findByIdentifier(catalogIdentifier).orElseThrow(
+        () -> ServiceException.notFound("Catalog {0} not found.", catalogIdentifier)
+    );
+    return this.fieldInUse(catalogEntity, fieldIdentifier);
+  }
+
+  private Boolean fieldInUse(final CatalogEntity catalogEntity, final String fieldIdentifier) {
+    final FieldEntity fieldEntity = this.fieldRepository.findByCatalogAndIdentifier(catalogEntity, fieldIdentifier).orElseThrow(
+        () -> ServiceException.notFound("Field {0} of catalog {1} not found.", catalogEntity.getIdentifier(), fieldIdentifier));
+    return this.fieldValueRepository.findByField(fieldEntity).isPresent();
+  }
 }
diff --git a/service/src/main/java/io/mifos/customer/catalog/service/rest/controller/CatalogRestController.java b/service/src/main/java/io/mifos/customer/catalog/service/rest/controller/CatalogRestController.java
index 96fe00e..6b0081e 100644
--- a/service/src/main/java/io/mifos/customer/catalog/service/rest/controller/CatalogRestController.java
+++ b/service/src/main/java/io/mifos/customer/catalog/service/rest/controller/CatalogRestController.java
@@ -21,7 +21,11 @@ import io.mifos.core.command.gateway.CommandGateway;
 import io.mifos.core.lang.ServiceException;
 import io.mifos.customer.PermittableGroupIds;
 import io.mifos.customer.catalog.api.v1.domain.Catalog;
+import io.mifos.customer.catalog.api.v1.domain.Field;
+import io.mifos.customer.catalog.service.internal.command.ChangeFieldCommand;
 import io.mifos.customer.catalog.service.internal.command.CreateCatalogCommand;
+import io.mifos.customer.catalog.service.internal.command.DeleteCatalogCommand;
+import io.mifos.customer.catalog.service.internal.command.DeleteFieldCommand;
 import io.mifos.customer.catalog.service.internal.service.CatalogService;
 import io.mifos.customer.service.ServiceConstants;
 import org.slf4j.Logger;
@@ -36,6 +40,7 @@ import org.springframework.web.bind.annotation.RequestMethod;
 import org.springframework.web.bind.annotation.ResponseBody;
 import org.springframework.web.bind.annotation.RestController;
 
+import javax.validation.Valid;
 import java.util.List;
 
 @RestController
@@ -101,4 +106,64 @@ public class CatalogRestController {
             .orElseThrow(() -> ServiceException.notFound("Catalog {0} not found.", identifier))
     );
   }
+
+  @Permittable(value = AcceptedTokenType.TENANT, groupId = PermittableGroupIds.CATALOG)
+  @RequestMapping(
+      path = "/{identifier}",
+      method = RequestMethod.DELETE,
+      consumes = MediaType.ALL_VALUE,
+      produces = MediaType.APPLICATION_JSON_VALUE
+  )
+  public
+  @ResponseBody
+  ResponseEntity<Void> deleteCatalog(@PathVariable("identifier") final String identifier) {
+    if (this.catalogService.catalogInUse(identifier)) {
+      throw ServiceException.badRequest("Catalog {0} in use.", identifier);
+    }
+
+    this.commandGateway.process(new DeleteCatalogCommand(identifier));
+
+    return ResponseEntity.accepted().build();
+  }
+
+  @Permittable(value = AcceptedTokenType.TENANT, groupId = PermittableGroupIds.CATALOG)
+  @RequestMapping(
+      path = "/{catalogIdentifier}/fields/{fieldIdentifier}",
+      method = RequestMethod.PUT,
+      consumes = MediaType.APPLICATION_JSON_VALUE,
+      produces = MediaType.APPLICATION_JSON_VALUE
+  )
+  public
+  @ResponseBody
+  ResponseEntity<Void> updateField(@PathVariable("catalogIdentifier") final String catalogIdentifier,
+                                   @PathVariable("fieldIdentifier") final String fieldIdentifier,
+                                   @RequestBody @Valid Field field) {
+    if (this.catalogService.fieldInUse(catalogIdentifier, fieldIdentifier)) {
+      throw ServiceException.badRequest("Catalog {0} in use.", catalogIdentifier);
+    }
+
+    this.commandGateway.process(new ChangeFieldCommand(catalogIdentifier, field));
+
+    return ResponseEntity.accepted().build();
+  }
+
+  @Permittable(value = AcceptedTokenType.TENANT, groupId = PermittableGroupIds.CATALOG)
+  @RequestMapping(
+      path = "/{catalogIdentifier}/fields/{fieldIdentifier}",
+      method = RequestMethod.DELETE,
+      consumes = MediaType.ALL_VALUE,
+      produces = MediaType.APPLICATION_JSON_VALUE
+  )
+  public
+  @ResponseBody
+  ResponseEntity<Void> deleteField(@PathVariable("catalogIdentifier") final String catalogIdentifier,
+                                     @PathVariable("fieldIdentifier") final String fieldIdentifier) {
+    if (this.catalogService.fieldInUse(catalogIdentifier, fieldIdentifier)) {
+      throw ServiceException.badRequest("Catalog {0} in use.", catalogIdentifier);
+    }
+
+    this.commandGateway.process(new DeleteFieldCommand(catalogIdentifier, fieldIdentifier));
+
+    return ResponseEntity.accepted().build();
+  }
 }

-- 
To stop receiving notification emails like this one, please contact
myrle@apache.org.

[fineract-cn-customer] 34/46: Merge pull request #14 from myrlen/develop

Posted by my...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

myrle pushed a commit to branch develop
in repository https://gitbox.apache.org/repos/asf/fineract-cn-customer.git

commit 2071809f6066f8d0858bca70d312a85ac7a1908e
Merge: 5abd772 d8b5bf7
Author: myrlen <my...@kuelap.io>
AuthorDate: Wed Oct 18 12:33:58 2017 +0200

    Merge pull request #14 from myrlen/develop
    
    Implementing documents for customers.

 .../io/mifos/customer/PermittableGroupIds.java     |   1 +
 .../customer/api/v1/CustomerEventConstants.java    |  10 +
 ...CompletedDocumentCannotBeChangedException.java} |   5 +-
 .../api/v1/client/CustomerDocumentsManager.java    | 152 ++++++++++++
 .../customer/api/v1/client/CustomerManager.java    |   2 +-
 ...ption.java => DocumentValidationException.java} |   2 +-
 .../customer/api/v1/domain/CustomerDocument.java   |  91 ++++++++
 .../customer/api/v1/events/DocumentEvent.java      |  71 ++++++
 .../customer/api/v1/events/DocumentPageEvent.java  |  85 +++++++
 .../io/mifos/customer/AbstractCustomerTest.java    |   4 +
 .../main/java/io/mifos/customer/TestCustomer.java  |   4 +-
 .../main/java/io/mifos/customer/TestDocuments.java | 168 +++++++++++++
 .../src/main/java/io/mifos/customer/TestSuite.java |   1 +
 .../customer/listener/DocumentEventListener.java   |  81 +++++++
 .../customer/util/CustomerDocumentGenerator.java   |  21 +-
 .../internal/command/CompleteDocumentCommand.java  |  45 ++++
 .../internal/command/CreateDocumentCommand.java    |  49 ++++
 .../command/CreateDocumentPageCommand.java         |  55 +++++
 .../command/DeleteDocumentPageCommand.java         |  52 +++++
 .../command/handler/DocumentCommandHandler.java    | 118 ++++++++++
 .../service/internal/mapper/DocumentMapper.java    |  70 ++++++
 .../internal/repository/DocumentEntity.java        | 117 ++++++++++
 .../internal/repository/DocumentPageEntity.java    | 128 ++++++++++
 .../repository/DocumentPageRepository.java         |  38 +++
 .../internal/repository/DocumentRepository.java    |  39 ++++
 .../service/internal/service/DocumentService.java  | 103 ++++++++
 .../rest/config/CustomerRestConfiguration.java     |   2 +
 .../service/rest/config/UploadProperties.java      |  51 ++++
 .../rest/controller/DocumentsRestController.java   | 260 +++++++++++++++++++++
 .../db/migrations/mariadb/V7__documents.sql        |  39 ++++
 30 files changed, 1852 insertions(+), 12 deletions(-)

-- 
To stop receiving notification emails like this one, please contact
myrle@apache.org.

[fineract-cn-customer] 19/46: Fix race conditions in task test - reset predefined tasks to false after test - reset events in recorder to prevent successful assert based on previous events

Posted by my...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

myrle pushed a commit to branch develop
in repository https://gitbox.apache.org/repos/asf/fineract-cn-customer.git

commit 2159f03d3c36e00f8af5512d778500f60ae9d019
Author: Mark <ma...@gmail.com>
AuthorDate: Fri Sep 15 15:27:36 2017 +0200

    Fix race conditions in task test
    - reset predefined tasks to false after test
    - reset events in recorder to prevent successful assert based on previous events
---
 .../src/main/java/io/mifos/customer/TestTaskInstance.java     | 11 +++++++++++
 1 file changed, 11 insertions(+)

diff --git a/component-test/src/main/java/io/mifos/customer/TestTaskInstance.java b/component-test/src/main/java/io/mifos/customer/TestTaskInstance.java
index 7ff792c..edce92c 100644
--- a/component-test/src/main/java/io/mifos/customer/TestTaskInstance.java
+++ b/component-test/src/main/java/io/mifos/customer/TestTaskInstance.java
@@ -164,6 +164,8 @@ public class TestTaskInstance extends AbstractCustomerTest {
     this.customerManager.taskForCustomerExecuted(randomCustomer.getIdentifier(), customTask2.getIdentifier());
     this.eventRecorder.wait(CustomerEventConstants.PUT_CUSTOMER, randomCustomer.getIdentifier());
 
+    this.eventRecorder.clear();
+
     final Command activateCustomer = new Command();
     activateCustomer.setAction(Command.Action.ACTIVATE.name());
     this.customerManager.customerCommand(randomCustomer.getIdentifier(), activateCustomer);
@@ -188,5 +190,14 @@ public class TestTaskInstance extends AbstractCustomerTest {
 
     final Customer customer = this.customerManager.findCustomer(randomCustomer.getIdentifier());
     Assert.assertEquals(Customer.State.ACTIVE.name(), customer.getCurrentState());
+
+    // set predefined to false so it does not have a side effect on other tests
+    customTask1.setPredefined(false);
+    this.customerManager.updateTask(customTask1.getIdentifier(), customTask1);
+    this.eventRecorder.wait(CustomerEventConstants.PUT_TASK, customTask1.getIdentifier());
+
+    customTask2.setPredefined(false);
+    this.customerManager.updateTask(customTask2.getIdentifier(), customTask1);
+    this.eventRecorder.wait(CustomerEventConstants.PUT_TASK, customTask2.getIdentifier());
   }
 }

-- 
To stop receiving notification emails like this one, please contact
myrle@apache.org.

[fineract-cn-customer] 02/46: Merge pull request #12 from mgeiss/develop

Posted by my...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

myrle pushed a commit to branch develop
in repository https://gitbox.apache.org/repos/asf/fineract-cn-customer.git

commit fb278a5177a4498f2abe13e64bcf0e864ac30717
Merge: 3aa4df7 3771232
Author: Markus Geiß <ma...@apache.org>
AuthorDate: Thu Jun 29 18:08:21 2017 +0200

    Merge pull request #12 from mgeiss/develop
    
    added process steps to fetch actions based on customer state

 .../customer/api/v1/client/CustomerManager.java    | 12 ++++-
 .../io/mifos/customer/api/v1/domain/Command.java   |  4 +-
 .../mifos/customer/api/v1/domain/ProcessStep.java  | 31 +++++++++---
 .../main/java/io/mifos/customer/TestCustomer.java  | 45 +++++++++++++++++
 .../repository/TaskInstanceRepository.java         |  3 ++
 .../service/internal/service/CustomerService.java  | 59 +++++++++++++++++++++-
 .../rest/controller/CustomerRestController.java    | 59 ++++++++++------------
 7 files changed, 170 insertions(+), 43 deletions(-)

-- 
To stop receiving notification emails like this one, please contact
myrle@apache.org.

[fineract-cn-customer] 01/46: added process steps to fetch actions based on customer state

Posted by my...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

myrle pushed a commit to branch develop
in repository https://gitbox.apache.org/repos/asf/fineract-cn-customer.git

commit 3771232db0ed19ec1b05a24fcce78f4270c4a1fa
Author: mgeiss <mg...@mifos.org>
AuthorDate: Thu Jun 29 18:07:15 2017 +0200

    added process steps to fetch actions based on customer state
---
 .../customer/api/v1/client/CustomerManager.java    | 12 ++++-
 .../io/mifos/customer/api/v1/domain/Command.java   |  4 +-
 .../mifos/customer/api/v1/domain/ProcessStep.java  | 31 +++++++++---
 .../main/java/io/mifos/customer/TestCustomer.java  | 45 +++++++++++++++++
 .../repository/TaskInstanceRepository.java         |  3 ++
 .../service/internal/service/CustomerService.java  | 59 +++++++++++++++++++++-
 .../rest/controller/CustomerRestController.java    | 59 ++++++++++------------
 7 files changed, 170 insertions(+), 43 deletions(-)

diff --git a/api/src/main/java/io/mifos/customer/api/v1/client/CustomerManager.java b/api/src/main/java/io/mifos/customer/api/v1/client/CustomerManager.java
index 4634868..bac6bcb 100644
--- a/api/src/main/java/io/mifos/customer/api/v1/client/CustomerManager.java
+++ b/api/src/main/java/io/mifos/customer/api/v1/client/CustomerManager.java
@@ -25,6 +25,7 @@ import io.mifos.customer.api.v1.domain.ContactDetail;
 import io.mifos.customer.api.v1.domain.IdentificationCard;
 import io.mifos.customer.api.v1.domain.Customer;
 import io.mifos.customer.api.v1.domain.CustomerPage;
+import io.mifos.customer.api.v1.domain.ProcessStep;
 import io.mifos.customer.api.v1.domain.TaskDefinition;
 import org.springframework.cloud.netflix.feign.FeignClient;
 import org.springframework.http.HttpStatus;
@@ -144,7 +145,7 @@ public interface CustomerManager {
   )
   @ThrowsException(status = HttpStatus.NOT_FOUND, exception = TaskNotFoundException.class)
   List<TaskDefinition> findTasksForCustomer(@PathVariable("identifier") final String identifier,
-                                          @RequestParam(value = "includeExecuted", required = false) final Boolean includeExecuted);
+                                            @RequestParam(value = "includeExecuted", required = false) final Boolean includeExecuted);
 
   @RequestMapping(
       value = "/customers/{identifier}/address",
@@ -302,4 +303,13 @@ public interface CustomerManager {
       @ThrowsException(status = HttpStatus.BAD_REQUEST, exception = TaskValidationException.class)
   })
   void updateTask(@PathVariable("identifier") final String identifier, @RequestBody final TaskDefinition taskDefinition);
+
+  @RequestMapping(
+      value = "/customer/{identifier}/actions",
+      method = RequestMethod.GET,
+      produces = MediaType.ALL_VALUE,
+      consumes = MediaType.APPLICATION_JSON_VALUE
+  )
+  @ThrowsException(status = HttpStatus.NOT_FOUND, exception = CustomerNotFoundException.class)
+  List<ProcessStep> fetchProcessSteps(@PathVariable(value = "identifier") final String customerIdentifier);
 }
diff --git a/api/src/main/java/io/mifos/customer/api/v1/domain/Command.java b/api/src/main/java/io/mifos/customer/api/v1/domain/Command.java
index 82e2481..6981ee8 100644
--- a/api/src/main/java/io/mifos/customer/api/v1/domain/Command.java
+++ b/api/src/main/java/io/mifos/customer/api/v1/domain/Command.java
@@ -15,7 +15,7 @@
  */
 package io.mifos.customer.api.v1.domain;
 
-import org.hibernate.validator.constraints.NotBlank;
+import javax.validation.constraints.NotNull;
 
 public final class Command {
 
@@ -27,7 +27,7 @@ public final class Command {
     REOPEN
   }
 
-  @NotBlank
+  @NotNull
   private Action action;
   private String comment;
   private String createdOn;
diff --git a/service/src/main/java/io/mifos/customer/service/internal/repository/TaskInstanceRepository.java b/api/src/main/java/io/mifos/customer/api/v1/domain/ProcessStep.java
similarity index 53%
copy from service/src/main/java/io/mifos/customer/service/internal/repository/TaskInstanceRepository.java
copy to api/src/main/java/io/mifos/customer/api/v1/domain/ProcessStep.java
index d8e45d2..622f41e 100644
--- a/service/src/main/java/io/mifos/customer/service/internal/repository/TaskInstanceRepository.java
+++ b/api/src/main/java/io/mifos/customer/api/v1/domain/ProcessStep.java
@@ -13,15 +13,32 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package io.mifos.customer.service.internal.repository;
-
-import org.springframework.data.jpa.repository.JpaRepository;
-import org.springframework.stereotype.Repository;
+package io.mifos.customer.api.v1.domain;
 
 import java.util.List;
 
-@Repository
-public interface TaskInstanceRepository extends JpaRepository<TaskInstanceEntity, Long> {
+public class ProcessStep {
+
+  private Command command;
+  private List<TaskDefinition> taskDefinitions;
+
+  public ProcessStep() {
+    super();
+  }
+
+  public Command getCommand() {
+    return this.command;
+  }
+
+  public void setCommand(final Command command) {
+    this.command = command;
+  }
+
+  public List<TaskDefinition> getTaskDefinitions() {
+    return this.taskDefinitions;
+  }
 
-  List<TaskInstanceEntity> findByCustomer(final CustomerEntity customer);
+  public void setTaskDefinitions(final List<TaskDefinition> taskDefinitions) {
+    this.taskDefinitions = taskDefinitions;
+  }
 }
diff --git a/component-test/src/main/java/io/mifos/customer/TestCustomer.java b/component-test/src/main/java/io/mifos/customer/TestCustomer.java
index 58ce486..83e0152 100644
--- a/component-test/src/main/java/io/mifos/customer/TestCustomer.java
+++ b/component-test/src/main/java/io/mifos/customer/TestCustomer.java
@@ -568,4 +568,49 @@ public class TestCustomer {
 
     this.customerManager.getPortrait(customer.getIdentifier());
   }
+
+  @Test
+  public void shouldReturnAvailableProcessSteps() throws Exception {
+    final Customer customer = CustomerGenerator.createRandomCustomer();
+
+    this.customerManager.createCustomer(customer);
+
+    this.eventRecorder.wait(CustomerEventConstants.POST_CUSTOMER, customer.getIdentifier());
+
+    final List<ProcessStep> pendingProcessSteps = this.customerManager.fetchProcessSteps(customer.getIdentifier());
+    Assert.assertEquals(2, pendingProcessSteps.size());
+    Assert.assertEquals(Command.Action.ACTIVATE.name(), pendingProcessSteps.get(0).getCommand().getAction());
+    Assert.assertEquals(Command.Action.CLOSE.name(), pendingProcessSteps.get(1).getCommand().getAction());
+
+    this.customerManager.customerCommand(customer.getIdentifier(), CommandGenerator.create(Command.Action.ACTIVATE, "Test"));
+    this.eventRecorder.wait(CustomerEventConstants.ACTIVATE_CUSTOMER, customer.getIdentifier());
+
+    final List<ProcessStep> activeProcessSteps = this.customerManager.fetchProcessSteps(customer.getIdentifier());
+    Assert.assertEquals(2, activeProcessSteps.size());
+    Assert.assertEquals(Command.Action.LOCK.name(), activeProcessSteps.get(0).getCommand().getAction());
+    Assert.assertEquals(Command.Action.CLOSE.name(), activeProcessSteps.get(1).getCommand().getAction());
+
+    this.customerManager.customerCommand(customer.getIdentifier(), CommandGenerator.create(Command.Action.LOCK, "Test"));
+    this.eventRecorder.wait(CustomerEventConstants.LOCK_CUSTOMER, customer.getIdentifier());
+
+    final List<ProcessStep> lockedProcessSteps = this.customerManager.fetchProcessSteps(customer.getIdentifier());
+    Assert.assertEquals(2, lockedProcessSteps.size());
+    Assert.assertEquals(Command.Action.UNLOCK.name(), lockedProcessSteps.get(0).getCommand().getAction());
+    Assert.assertEquals(Command.Action.CLOSE.name(), lockedProcessSteps.get(1).getCommand().getAction());
+
+    this.customerManager.customerCommand(customer.getIdentifier(), CommandGenerator.create(Command.Action.UNLOCK, "Test"));
+    this.eventRecorder.wait(CustomerEventConstants.UNLOCK_CUSTOMER, customer.getIdentifier());
+
+    final List<ProcessStep> unlockedProcessSteps = this.customerManager.fetchProcessSteps(customer.getIdentifier());
+    Assert.assertEquals(2, unlockedProcessSteps.size());
+    Assert.assertEquals(Command.Action.LOCK.name(), unlockedProcessSteps.get(0).getCommand().getAction());
+    Assert.assertEquals(Command.Action.CLOSE.name(), unlockedProcessSteps.get(1).getCommand().getAction());
+
+    this.customerManager.customerCommand(customer.getIdentifier(), CommandGenerator.create(Command.Action.CLOSE, "Test"));
+    this.eventRecorder.wait(CustomerEventConstants.CLOSE_CUSTOMER, customer.getIdentifier());
+
+    final List<ProcessStep> closedProcessSteps = this.customerManager.fetchProcessSteps(customer.getIdentifier());
+    Assert.assertEquals(1, closedProcessSteps.size());
+    Assert.assertEquals(Command.Action.REOPEN.name(), closedProcessSteps.get(0).getCommand().getAction());
+  }
 }
diff --git a/service/src/main/java/io/mifos/customer/service/internal/repository/TaskInstanceRepository.java b/service/src/main/java/io/mifos/customer/service/internal/repository/TaskInstanceRepository.java
index d8e45d2..2301f3a 100644
--- a/service/src/main/java/io/mifos/customer/service/internal/repository/TaskInstanceRepository.java
+++ b/service/src/main/java/io/mifos/customer/service/internal/repository/TaskInstanceRepository.java
@@ -24,4 +24,7 @@ import java.util.List;
 public interface TaskInstanceRepository extends JpaRepository<TaskInstanceEntity, Long> {
 
   List<TaskInstanceEntity> findByCustomer(final CustomerEntity customer);
+
+  List<TaskInstanceEntity> findByCustomerAndTaskDefinition(final CustomerEntity customer,
+                                                           final TaskDefinitionEntity taskDefinitionEntity);
 }
diff --git a/service/src/main/java/io/mifos/customer/service/internal/service/CustomerService.java b/service/src/main/java/io/mifos/customer/service/internal/service/CustomerService.java
index 728af4b..5048a4c 100644
--- a/service/src/main/java/io/mifos/customer/service/internal/service/CustomerService.java
+++ b/service/src/main/java/io/mifos/customer/service/internal/service/CustomerService.java
@@ -19,6 +19,8 @@ import io.mifos.customer.api.v1.domain.Command;
 import io.mifos.customer.api.v1.domain.Customer;
 import io.mifos.customer.api.v1.domain.CustomerPage;
 import io.mifos.customer.api.v1.domain.IdentificationCard;
+import io.mifos.customer.api.v1.domain.ProcessStep;
+import io.mifos.customer.api.v1.domain.TaskDefinition;
 import io.mifos.customer.catalog.api.v1.domain.Value;
 import io.mifos.customer.catalog.service.internal.repository.FieldEntity;
 import io.mifos.customer.catalog.service.internal.repository.FieldValueEntity;
@@ -28,6 +30,7 @@ import io.mifos.customer.service.internal.mapper.CommandMapper;
 import io.mifos.customer.service.internal.mapper.ContactDetailMapper;
 import io.mifos.customer.service.internal.mapper.CustomerMapper;
 import io.mifos.customer.service.internal.mapper.IdentificationCardMapper;
+import io.mifos.customer.service.internal.mapper.TaskDefinitionMapper;
 import io.mifos.customer.service.internal.repository.*;
 import io.mifos.customer.service.internal.mapper.AddressMapper;
 import org.slf4j.Logger;
@@ -53,6 +56,8 @@ public class CustomerService {
   private final ContactDetailRepository contactDetailRepository;
   private final FieldValueRepository fieldValueRepository;
   private final CommandRepository commandRepository;
+  private final TaskDefinitionRepository taskDefinitionRepository;
+  private final TaskInstanceRepository taskInstanceRepository;
 
   @Autowired
   public CustomerService(@Qualifier(ServiceConstants.LOGGER_NAME) final Logger logger,
@@ -61,7 +66,9 @@ public class CustomerService {
                          final PortraitRepository portraitRepository,
                          final ContactDetailRepository contactDetailRepository,
                          final FieldValueRepository fieldValueRepository,
-                         final CommandRepository commandRepository) {
+                         final CommandRepository commandRepository,
+                         final TaskDefinitionRepository taskDefinitionRepository,
+                         final TaskInstanceRepository taskInstanceRepository) {
     super();
     this.logger = logger;
     this.customerRepository = customerRepository;
@@ -70,6 +77,8 @@ public class CustomerService {
     this.contactDetailRepository = contactDetailRepository;
     this.fieldValueRepository = fieldValueRepository;
     this.commandRepository = commandRepository;
+    this.taskDefinitionRepository = taskDefinitionRepository;
+    this.taskInstanceRepository = taskInstanceRepository;
   }
 
   public Boolean customerExists(final String identifier) {
@@ -185,4 +194,52 @@ public class CustomerService {
 
     return identificationCardEntity.map(IdentificationCardMapper::map);
   }
+
+  public List<ProcessStep> getProcessSteps(final String customerIdentifier) {
+    final ArrayList<ProcessStep> processSteps = new ArrayList<>();
+    final CustomerEntity customerEntity = this.customerRepository.findByIdentifier(customerIdentifier);
+
+    final Customer.State state = Customer.State.valueOf(customerEntity.getCurrentState());
+    switch (state) {
+      case PENDING:
+        processSteps.add(this.buildProcessStep(customerEntity, Command.Action.ACTIVATE));
+        processSteps.add(this.buildProcessStep(customerEntity, Command.Action.CLOSE));
+        break;
+      case ACTIVE:
+        processSteps.add(this.buildProcessStep(customerEntity, Command.Action.LOCK));
+        processSteps.add(this.buildProcessStep(customerEntity, Command.Action.CLOSE));
+        break;
+      case LOCKED:
+        processSteps.add(this.buildProcessStep(customerEntity, Command.Action.UNLOCK));
+        processSteps.add(this.buildProcessStep(customerEntity, Command.Action.CLOSE));
+        break;
+      case CLOSED:
+        processSteps.add(this.buildProcessStep(customerEntity, Command.Action.REOPEN));
+        break;
+    }
+
+    return processSteps;
+  }
+
+  private ProcessStep buildProcessStep(final CustomerEntity customerEntity, final Command.Action action) {
+    final ProcessStep processStep = new ProcessStep();
+
+    final Command command = new Command();
+    command.setAction(action.name());
+    processStep.setCommand(command);
+
+    final ArrayList<TaskDefinition> taskDefinitions = new ArrayList<>();
+    this.taskDefinitionRepository.findByAssignedCommandsContaining(action.name())
+        .forEach(taskDefinitionEntity -> {
+          this.taskInstanceRepository.findByCustomerAndTaskDefinition(customerEntity, taskDefinitionEntity)
+              .forEach(taskInstanceEntity -> {
+                if (taskInstanceEntity.getExecutedBy() == null) {
+                  taskDefinitions.add(TaskDefinitionMapper.map(taskDefinitionEntity));
+                }
+              });
+        });
+    processStep.setTaskDefinitions(taskDefinitions);
+
+    return processStep;
+  }
 }
diff --git a/service/src/main/java/io/mifos/customer/service/rest/controller/CustomerRestController.java b/service/src/main/java/io/mifos/customer/service/rest/controller/CustomerRestController.java
index 31a3959..41546b9 100644
--- a/service/src/main/java/io/mifos/customer/service/rest/controller/CustomerRestController.java
+++ b/service/src/main/java/io/mifos/customer/service/rest/controller/CustomerRestController.java
@@ -182,52 +182,34 @@ public class CustomerRestController {
     if (customerOptional.isPresent()) {
       final Customer customer = customerOptional.get();
       final Command.Action action = Command.Action.valueOf(command.getAction());
+      final String currentState = customer.getCurrentState();
       switch (action) {
         case ACTIVATE:
-          if (!customer.getCurrentState().equals(Customer.State.PENDING.name())) {
-            throw ServiceException.badRequest(
-                "Customer {0} can not be activated, current state is {1}.",
-                identifier,
-                customer.getCurrentState());
+          if (Customer.State.PENDING.name().equals(currentState)) {
+            this.commandGateway.process(new ActivateCustomerCommand(identifier, command.getComment()));
           }
-          this.commandGateway.process(new ActivateCustomerCommand(identifier, command.getComment()));
           break;
         case LOCK:
-          if (!customer.getCurrentState().equals(Customer.State.ACTIVE.name())) {
-            throw ServiceException.badRequest(
-                "Customer {0} can not be locked, current state is {1}.",
-                identifier,
-                customer.getCurrentState());
+          if (Customer.State.ACTIVE.name().equals(currentState)) {
+            this.commandGateway.process(new LockCustomerCommand(identifier, command.getComment()));
           }
-          this.commandGateway.process(new LockCustomerCommand(identifier, command.getComment()));
           break;
         case UNLOCK:
-          if (!customer.getCurrentState().equals(Customer.State.LOCKED.name())) {
-            throw ServiceException.badRequest(
-                "Customer {0} can not be unlocked, current state is {1}.",
-                identifier,
-                customer.getCurrentState());
+          if (Customer.State.LOCKED.name().equals(currentState)) {
+            this.commandGateway.process(new UnlockCustomerCommand(identifier, command.getComment()));
           }
-          this.commandGateway.process(new UnlockCustomerCommand(identifier, command.getComment()));
           break;
         case CLOSE:
-          if (!customer.getCurrentState().equals(Customer.State.ACTIVE.name())
-              && !customer.getCurrentState().equals(Customer.State.LOCKED.name())) {
-            throw ServiceException.badRequest(
-                "Customer {0} can not be closed, current state is {1}.",
-                identifier,
-                customer.getCurrentState());
+          if (Customer.State.ACTIVE.name().equals(currentState)
+              || Customer.State.LOCKED.name().equals(currentState)
+              || Customer.State.PENDING.name().equals(currentState)) {
+            this.commandGateway.process(new CloseCustomerCommand(identifier, command.getComment()));
           }
-          this.commandGateway.process(new CloseCustomerCommand(identifier, command.getComment()));
           break;
         case REOPEN:
-          if (!customer.getCurrentState().equals(Customer.State.CLOSED.name())) {
-            throw ServiceException.badRequest(
-                "Customer {0} can not be reopened, current state is {1}.",
-                identifier,
-                customer.getCurrentState());
+          if (Customer.State.CLOSED.name().equals(currentState)) {
+            this.commandGateway.process(new ReopenCustomerCommand(identifier, command.getComment()));
           }
-          this.commandGateway.process(new ReopenCustomerCommand(identifier, command.getComment()));
           break;
         default:
           throw ServiceException.badRequest("Unsupported action {0}.", command.getAction());
@@ -607,6 +589,20 @@ public class CustomerRestController {
     return ResponseEntity.accepted().build();
   }
 
+  @Permittable(value = AcceptedTokenType.TENANT, groupId = PermittableGroupIds.CUSTOMER)
+  @RequestMapping(
+      value = "/customer/{identifier}/actions",
+      method = RequestMethod.GET,
+      produces = MediaType.APPLICATION_JSON_VALUE,
+      consumes = MediaType.ALL_VALUE
+  )
+  public
+  @ResponseBody
+  ResponseEntity<List<ProcessStep>> fetchProcessSteps(@PathVariable(value = "identifier") final String customerIdentifier) {
+    this.throwIfCustomerNotExists(customerIdentifier);
+    return ResponseEntity.ok(this.customerService.getProcessSteps(customerIdentifier));
+  }
+
   private Pageable createPageRequest(final Integer pageIndex, final Integer size, final String sortColumn, final String sortDirection) {
     final Integer pageIndexToUse = pageIndex != null ? pageIndex : 0;
     final Integer sizeToUse = size != null ? size : 20;
@@ -632,5 +628,4 @@ public class CustomerRestController {
       throw ServiceException.notFound("Identification card {0} not found.", number);
     }
   }
-
 }

-- 
To stop receiving notification emails like this one, please contact
myrle@apache.org.

[fineract-cn-customer] 20/46: Merge pull request #7 from markvanveen/develop

Posted by my...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

myrle pushed a commit to branch develop
in repository https://gitbox.apache.org/repos/asf/fineract-cn-customer.git

commit b730e07722be4bb69b7fe961dd313917873293e4
Merge: dd82190 2159f03
Author: Mark van Veen <30...@users.noreply.github.com>
AuthorDate: Fri Sep 15 15:39:44 2017 +0200

    Merge pull request #7 from markvanveen/develop
    
    Fix race conditions in task test

 .../src/main/java/io/mifos/customer/TestTaskInstance.java     | 11 +++++++++++
 1 file changed, 11 insertions(+)

-- 
To stop receiving notification emails like this one, please contact
myrle@apache.org.

[fineract-cn-customer] 09/46: added ability to manage payroll distribution per customer

Posted by my...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

myrle pushed a commit to branch develop
in repository https://gitbox.apache.org/repos/asf/fineract-cn-customer.git

commit aaa22f2a070e80613be5d69f05c67b0ddfffe14d
Author: mgeiss <mg...@mifos.org>
AuthorDate: Tue Sep 5 13:55:43 2017 +0200

    added ability to manage payroll distribution per customer
---
 .../customer/api/v1/CustomerEventConstants.java    |   3 +
 .../customer/api/v1/client/CustomerManager.java    |  42 ++++++-
 ...=> PayrollDistributionValidationException.java} |   4 +-
 .../api/v1/client/ScanAlreadyExistsException.java  |   2 +-
 .../api/v1/client/ScanNotFoundException.java       |   2 +-
 .../api/v1/domain/IdentificationCardScan.java      |   2 +-
 .../customer/api/v1/domain/PayrollAllocation.java  |  58 ++++++++++
 .../api/v1/domain/PayrollDistribution.java         |  86 ++++++++++++++
 .../io/mifos/customer/api/v1/events/ScanEvent.java |   2 +-
 .../main/java/io/mifos/customer/TestCustomer.java  |  13 ++-
 .../io/mifos/customer/TestIdentificationCards.java |   2 +-
 .../io/mifos/customer/TestPayrollDistribution.java | 115 ++++++++++++++++++
 .../io/mifos/customer/catalog/TestCatalog.java     |   7 +-
 .../customer/listener/CustomerEventListener.java   |   9 ++
 .../java/io/mifos/customer/util/ScanGenerator.java |   2 +-
 .../resources/{logback.xml => logback-test.xml}    |   0
 .../internal/command/handler/CatalogAggregate.java |   4 +-
 .../internal/service/FieldValueValidator.java      |   2 +-
 .../rest/controller/CatalogRestController.java     |   2 +-
 .../CreateIdentificationCardScanCommand.java       |   2 +-
 .../DeleteIdentificationCardScanCommand.java       |   2 +-
 .../command/SetPayrollDistributionCommand.java     |  38 ++++++
 .../command/handler/CustomerAggregate.java         | 111 +++++++++++++++++-
 .../mapper/IdentificationCardScanMapper.java       |   2 +-
 .../internal/mapper/PayrollDistributionMapper.java |  55 +++++++++
 .../internal/mapper/TaskInstanceMapper.java        |   2 +-
 .../repository/IdentificationCardScanEntity.java   |  15 ++-
 .../IdentificationCardScanRepository.java          |   2 +-
 .../repository/PayrollAllocationEntity.java        |  89 ++++++++++++++
 .../repository/PayrollAllocationRepository.java    |   9 +-
 .../repository/PayrollDistributionEntity.java      | 128 +++++++++++++++++++++
 .../repository/PayrollDistributionRepository.java  |  11 +-
 .../internal/repository/PortraitEntity.java        |  11 +-
 .../service/internal/service/CustomerService.java  |  44 ++++++-
 .../service/internal/service/TaskService.java      |   2 +-
 .../rest/controller/CustomerRestController.java    |  75 +++++++++++-
 .../mariadb/V5__add_payroll_distributions.sql      |  39 +++++++
 37 files changed, 948 insertions(+), 46 deletions(-)

diff --git a/api/src/main/java/io/mifos/customer/api/v1/CustomerEventConstants.java b/api/src/main/java/io/mifos/customer/api/v1/CustomerEventConstants.java
index ab5a9a3..21793b6 100644
--- a/api/src/main/java/io/mifos/customer/api/v1/CustomerEventConstants.java
+++ b/api/src/main/java/io/mifos/customer/api/v1/CustomerEventConstants.java
@@ -46,6 +46,7 @@ public interface CustomerEventConstants {
 
   String POST_PORTRAIT = "post-portrait";
   String DELETE_PORTRAIT = "delete-portrait";
+  String PUT_PAYROLL_DISTRIBUTION = "put-payroll-distribution";
 
   String SELECTOR_INITIALIZE = SELECTOR_NAME + " = '" + INITIALIZE + "'";
 
@@ -72,4 +73,6 @@ public interface CustomerEventConstants {
 
   String SELECTOR_PUT_PORTRAIT = SELECTOR_NAME + " = '" + POST_PORTRAIT + "'";
   String SELECTOR_DELETE_PORTRAIT = SELECTOR_NAME + " = '" + DELETE_PORTRAIT + "'";
+
+  String SELECTOR_PUT_PAYROLL_DISTRIBUTION = SELECTOR_NAME + " = '" + PUT_PAYROLL_DISTRIBUTION + "'";
 }
diff --git a/api/src/main/java/io/mifos/customer/api/v1/client/CustomerManager.java b/api/src/main/java/io/mifos/customer/api/v1/client/CustomerManager.java
index a906755..67ef6c0 100644
--- a/api/src/main/java/io/mifos/customer/api/v1/client/CustomerManager.java
+++ b/api/src/main/java/io/mifos/customer/api/v1/client/CustomerManager.java
@@ -19,13 +19,27 @@ import io.mifos.core.api.annotation.ThrowsException;
 import io.mifos.core.api.annotation.ThrowsExceptions;
 import io.mifos.core.lang.validation.constraints.ValidIdentifier;
 import io.mifos.customer.api.v1.config.CustomerFeignClientConfig;
-import io.mifos.customer.api.v1.domain.*;
+import io.mifos.customer.api.v1.domain.Address;
+import io.mifos.customer.api.v1.domain.Command;
+import io.mifos.customer.api.v1.domain.ContactDetail;
+import io.mifos.customer.api.v1.domain.Customer;
+import io.mifos.customer.api.v1.domain.CustomerPage;
+import io.mifos.customer.api.v1.domain.IdentificationCard;
+import io.mifos.customer.api.v1.domain.IdentificationCardScan;
+import io.mifos.customer.api.v1.domain.PayrollDistribution;
+import io.mifos.customer.api.v1.domain.ProcessStep;
+import io.mifos.customer.api.v1.domain.TaskDefinition;
 import org.springframework.cloud.netflix.feign.FeignClient;
 import org.springframework.http.HttpStatus;
 import org.springframework.http.MediaType;
-import org.springframework.web.bind.annotation.*;
+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.multipart.MultipartFile;
 
+import javax.validation.Valid;
 import javax.validation.constraints.Size;
 import java.util.List;
 
@@ -379,4 +393,28 @@ public interface CustomerManager {
   )
   @ThrowsException(status = HttpStatus.NOT_FOUND, exception = CustomerNotFoundException.class)
   List<ProcessStep> fetchProcessSteps(@PathVariable(value = "identifier") final String customerIdentifier);
+
+  @RequestMapping(
+      value = "/customer/{identifier}/payroll",
+      method = RequestMethod.PUT,
+      produces = MediaType.APPLICATION_JSON_VALUE,
+      consumes = MediaType.APPLICATION_JSON_VALUE
+  )
+  @ThrowsExceptions({
+      @ThrowsException(status = HttpStatus.NOT_FOUND, exception = CustomerNotFoundException.class),
+      @ThrowsException(status = HttpStatus.BAD_REQUEST, exception = PayrollDistributionValidationException.class)
+  })
+  void setPayrollDistribution(@PathVariable(value = "identifier") final String customerIdentifier,
+                              @RequestBody @Valid final PayrollDistribution payrollDistribution);
+
+  @RequestMapping(
+      value = "/customer/{identifier}/payroll",
+      method = RequestMethod.GET,
+      produces = MediaType.ALL_VALUE,
+      consumes = MediaType.APPLICATION_JSON_VALUE
+  )
+  @ThrowsExceptions({
+      @ThrowsException(status = HttpStatus.NOT_FOUND, exception = CustomerNotFoundException.class)
+  })
+  PayrollDistribution getPayrollDistribution(@PathVariable(value = "identifier") final String customerIdentifier);
 }
diff --git a/api/src/main/java/io/mifos/customer/api/v1/client/ScanAlreadyExistsException.java b/api/src/main/java/io/mifos/customer/api/v1/client/PayrollDistributionValidationException.java
similarity index 83%
copy from api/src/main/java/io/mifos/customer/api/v1/client/ScanAlreadyExistsException.java
copy to api/src/main/java/io/mifos/customer/api/v1/client/PayrollDistributionValidationException.java
index 45ef5a1..52caca1 100644
--- a/api/src/main/java/io/mifos/customer/api/v1/client/ScanAlreadyExistsException.java
+++ b/api/src/main/java/io/mifos/customer/api/v1/client/PayrollDistributionValidationException.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2016 The Mifos Initiative.
+ * Copyright 2017 The Mifos Initiative.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -15,5 +15,5 @@
  */
 package io.mifos.customer.api.v1.client;
 
-public final class ScanAlreadyExistsException extends RuntimeException {
+public class PayrollDistributionValidationException extends RuntimeException {
 }
diff --git a/api/src/main/java/io/mifos/customer/api/v1/client/ScanAlreadyExistsException.java b/api/src/main/java/io/mifos/customer/api/v1/client/ScanAlreadyExistsException.java
index 45ef5a1..83682b1 100644
--- a/api/src/main/java/io/mifos/customer/api/v1/client/ScanAlreadyExistsException.java
+++ b/api/src/main/java/io/mifos/customer/api/v1/client/ScanAlreadyExistsException.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2016 The Mifos Initiative.
+ * Copyright 2017 The Mifos Initiative.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
diff --git a/api/src/main/java/io/mifos/customer/api/v1/client/ScanNotFoundException.java b/api/src/main/java/io/mifos/customer/api/v1/client/ScanNotFoundException.java
index 3f2ada1..178fd59 100644
--- a/api/src/main/java/io/mifos/customer/api/v1/client/ScanNotFoundException.java
+++ b/api/src/main/java/io/mifos/customer/api/v1/client/ScanNotFoundException.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2016 The Mifos Initiative.
+ * Copyright 2017 The Mifos Initiative.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
diff --git a/api/src/main/java/io/mifos/customer/api/v1/domain/IdentificationCardScan.java b/api/src/main/java/io/mifos/customer/api/v1/domain/IdentificationCardScan.java
index 462e4fe..ccbe68c 100644
--- a/api/src/main/java/io/mifos/customer/api/v1/domain/IdentificationCardScan.java
+++ b/api/src/main/java/io/mifos/customer/api/v1/domain/IdentificationCardScan.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2016 The Mifos Initiative.
+ * Copyright 2017 The Mifos Initiative.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
diff --git a/api/src/main/java/io/mifos/customer/api/v1/domain/PayrollAllocation.java b/api/src/main/java/io/mifos/customer/api/v1/domain/PayrollAllocation.java
new file mode 100644
index 0000000..10d49cd
--- /dev/null
+++ b/api/src/main/java/io/mifos/customer/api/v1/domain/PayrollAllocation.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2017 The Mifos Initiative.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.mifos.customer.api.v1.domain;
+
+import io.mifos.core.lang.validation.constraints.ValidIdentifier;
+
+import javax.validation.constraints.DecimalMin;
+import java.math.BigDecimal;
+
+public class PayrollAllocation {
+
+  @ValidIdentifier
+  private String accountNumber;
+  @DecimalMin("0.001")
+  private BigDecimal amount;
+  private Boolean proportional = Boolean.FALSE;
+
+  public PayrollAllocation() {
+    super();
+  }
+
+  public String getAccountNumber() {
+    return this.accountNumber;
+  }
+
+  public void setAccountNumber(final String accountNumber) {
+    this.accountNumber = accountNumber;
+  }
+
+  public BigDecimal getAmount() {
+    return this.amount;
+  }
+
+  public void setAmount(final BigDecimal amount) {
+    this.amount = amount;
+  }
+
+  public Boolean getProportional() {
+    return this.proportional;
+  }
+
+  public void setProportional(final Boolean proportional) {
+    this.proportional = proportional;
+  }
+}
diff --git a/api/src/main/java/io/mifos/customer/api/v1/domain/PayrollDistribution.java b/api/src/main/java/io/mifos/customer/api/v1/domain/PayrollDistribution.java
new file mode 100644
index 0000000..d905f11
--- /dev/null
+++ b/api/src/main/java/io/mifos/customer/api/v1/domain/PayrollDistribution.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright 2017 The Mifos Initiative.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.mifos.customer.api.v1.domain;
+
+import io.mifos.core.lang.validation.constraints.ValidIdentifier;
+
+import javax.validation.Valid;
+import java.util.HashSet;
+import java.util.Set;
+
+public class PayrollDistribution {
+
+  @ValidIdentifier
+  private String mainAccountNumber;
+  @Valid
+  private Set<PayrollAllocation> payrollAllocations = new HashSet<>();
+  private String createdBy;
+  private String createdOn;
+  private String lastModifiedBy;
+  private String lastModifiedOn;
+
+  public PayrollDistribution() {
+    super();
+  }
+
+  public String getMainAccountNumber() {
+    return this.mainAccountNumber;
+  }
+
+  public void setMainAccountNumber(final String mainAccountNumber) {
+    this.mainAccountNumber = mainAccountNumber;
+  }
+
+  public Set<PayrollAllocation> getPayrollAllocations() {
+    return this.payrollAllocations;
+  }
+
+  public void setPayrollAllocations(final Set<PayrollAllocation> payrollAllocations) {
+    this.payrollAllocations = payrollAllocations;
+  }
+
+  public String getCreatedBy() {
+    return this.createdBy;
+  }
+
+  public void setCreatedBy(final String createdBy) {
+    this.createdBy = createdBy;
+  }
+
+  public String getCreatedOn() {
+    return this.createdOn;
+  }
+
+  public void setCreatedOn(final String createdOn) {
+    this.createdOn = createdOn;
+  }
+
+  public String getLastModifiedBy() {
+    return this.lastModifiedBy;
+  }
+
+  public void setLastModifiedBy(final String lastModifiedBy) {
+    this.lastModifiedBy = lastModifiedBy;
+  }
+
+  public String getLastModifiedOn() {
+    return this.lastModifiedOn;
+  }
+
+  public void setLastModifiedOn(final String lastModifiedOn) {
+    this.lastModifiedOn = lastModifiedOn;
+  }
+}
diff --git a/api/src/main/java/io/mifos/customer/api/v1/events/ScanEvent.java b/api/src/main/java/io/mifos/customer/api/v1/events/ScanEvent.java
index f4f8057..9ce809e 100644
--- a/api/src/main/java/io/mifos/customer/api/v1/events/ScanEvent.java
+++ b/api/src/main/java/io/mifos/customer/api/v1/events/ScanEvent.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2016 The Mifos Initiative.
+ * Copyright 2017 The Mifos Initiative.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
diff --git a/component-test/src/main/java/io/mifos/customer/TestCustomer.java b/component-test/src/main/java/io/mifos/customer/TestCustomer.java
index 4901dc7..36bf013 100644
--- a/component-test/src/main/java/io/mifos/customer/TestCustomer.java
+++ b/component-test/src/main/java/io/mifos/customer/TestCustomer.java
@@ -16,8 +16,17 @@
 package io.mifos.customer;
 
 import io.mifos.customer.api.v1.CustomerEventConstants;
-import io.mifos.customer.api.v1.client.*;
-import io.mifos.customer.api.v1.domain.*;
+import io.mifos.customer.api.v1.client.CustomerAlreadyExistsException;
+import io.mifos.customer.api.v1.client.CustomerNotFoundException;
+import io.mifos.customer.api.v1.client.CustomerValidationException;
+import io.mifos.customer.api.v1.client.PortraitNotFoundException;
+import io.mifos.customer.api.v1.client.PortraitValidationException;
+import io.mifos.customer.api.v1.domain.Address;
+import io.mifos.customer.api.v1.domain.Command;
+import io.mifos.customer.api.v1.domain.ContactDetail;
+import io.mifos.customer.api.v1.domain.Customer;
+import io.mifos.customer.api.v1.domain.CustomerPage;
+import io.mifos.customer.api.v1.domain.ProcessStep;
 import io.mifos.customer.util.AddressGenerator;
 import io.mifos.customer.util.CommandGenerator;
 import io.mifos.customer.util.ContactDetailGenerator;
diff --git a/component-test/src/main/java/io/mifos/customer/TestIdentificationCards.java b/component-test/src/main/java/io/mifos/customer/TestIdentificationCards.java
index 0740bba..b2d3c8b 100644
--- a/component-test/src/main/java/io/mifos/customer/TestIdentificationCards.java
+++ b/component-test/src/main/java/io/mifos/customer/TestIdentificationCards.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2016 The Mifos Initiative.
+ * Copyright 2017 The Mifos Initiative.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
diff --git a/component-test/src/main/java/io/mifos/customer/TestPayrollDistribution.java b/component-test/src/main/java/io/mifos/customer/TestPayrollDistribution.java
new file mode 100644
index 0000000..89f6da4
--- /dev/null
+++ b/component-test/src/main/java/io/mifos/customer/TestPayrollDistribution.java
@@ -0,0 +1,115 @@
+/*
+ * Copyright 2017 The Mifos Initiative.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.mifos.customer;
+
+import com.google.common.collect.Sets;
+import io.mifos.customer.api.v1.CustomerEventConstants;
+import io.mifos.customer.api.v1.domain.Customer;
+import io.mifos.customer.api.v1.domain.PayrollAllocation;
+import io.mifos.customer.api.v1.domain.PayrollDistribution;
+import io.mifos.customer.util.CustomerGenerator;
+import org.junit.Assert;
+import org.junit.Test;
+
+import java.math.BigDecimal;
+import java.util.Optional;
+
+public class TestPayrollDistribution extends AbstractCustomerTest {
+
+  @Test
+  public void shouldSetInitialPayrollDistribution() throws Exception {
+    final Customer customer = CustomerGenerator.createRandomCustomer();
+    this.customerManager.createCustomer(customer);
+
+    Assert.assertTrue(this.eventRecorder.wait(CustomerEventConstants.POST_CUSTOMER, customer.getIdentifier()));
+
+    final PayrollAllocation firstPayrollAllocation = new PayrollAllocation();
+    firstPayrollAllocation.setAccountNumber("08154712");
+    firstPayrollAllocation.setAmount(BigDecimal.valueOf(234.56D));
+
+    final PayrollAllocation secondPayrollAllocation = new PayrollAllocation();
+    secondPayrollAllocation.setAccountNumber("08154713");
+    secondPayrollAllocation.setAmount(BigDecimal.valueOf(5.00D));
+    secondPayrollAllocation.setProportional(Boolean.TRUE);
+
+    final PayrollDistribution payrollDistribution =  new PayrollDistribution();
+    payrollDistribution.setMainAccountNumber("08154711");
+    payrollDistribution.setPayrollAllocations(Sets.newHashSet(firstPayrollAllocation, secondPayrollAllocation));
+    this.customerManager.setPayrollDistribution(customer.getIdentifier(), payrollDistribution);
+
+    Assert.assertTrue(this.eventRecorder.wait(CustomerEventConstants.PUT_PAYROLL_DISTRIBUTION, customer.getIdentifier()));
+
+    final PayrollDistribution fetchedPayrollDistribution = this.customerManager.getPayrollDistribution(customer.getIdentifier());
+
+    this.compare(payrollDistribution, fetchedPayrollDistribution);
+  }
+
+  @Test
+  public void shouldUpdatePayrollDistribution() throws Exception {
+    final Customer customer = CustomerGenerator.createRandomCustomer();
+    this.customerManager.createCustomer(customer);
+
+    Assert.assertTrue(this.eventRecorder.wait(CustomerEventConstants.POST_CUSTOMER, customer.getIdentifier()));
+
+    final PayrollAllocation firstPayrollAllocation = new PayrollAllocation();
+    firstPayrollAllocation.setAccountNumber("08154712");
+    firstPayrollAllocation.setAmount(BigDecimal.valueOf(234.56D));
+
+    final PayrollAllocation secondPayrollAllocation = new PayrollAllocation();
+    secondPayrollAllocation.setAccountNumber("08154713");
+    secondPayrollAllocation.setAmount(BigDecimal.valueOf(5.00D));
+    secondPayrollAllocation.setProportional(Boolean.TRUE);
+
+    final PayrollDistribution payrollDistribution =  new PayrollDistribution();
+    payrollDistribution.setMainAccountNumber("08154711");
+    payrollDistribution.setPayrollAllocations(Sets.newHashSet(firstPayrollAllocation, secondPayrollAllocation));
+    this.customerManager.setPayrollDistribution(customer.getIdentifier(), payrollDistribution);
+
+    Assert.assertTrue(this.eventRecorder.wait(CustomerEventConstants.PUT_PAYROLL_DISTRIBUTION, customer.getIdentifier()));
+    this.eventRecorder.clear();
+
+    final PayrollDistribution fetchedPayrollDistribution = this.customerManager.getPayrollDistribution(customer.getIdentifier());
+
+    final PayrollAllocation replacedPayrollAllocation = new PayrollAllocation();
+    replacedPayrollAllocation.setAccountNumber("08154714");
+    replacedPayrollAllocation.setAmount(BigDecimal.valueOf(10.00D));
+    replacedPayrollAllocation.setProportional(Boolean.TRUE);
+
+    final PayrollDistribution replacedPayrollDistribution = new PayrollDistribution();
+    replacedPayrollDistribution.setMainAccountNumber(fetchedPayrollDistribution.getMainAccountNumber());
+    replacedPayrollDistribution.setPayrollAllocations(Sets.newHashSet(replacedPayrollAllocation));
+    this.customerManager.setPayrollDistribution(customer.getIdentifier(), replacedPayrollDistribution);
+
+    Assert.assertTrue(this.eventRecorder.wait(CustomerEventConstants.PUT_PAYROLL_DISTRIBUTION, customer.getIdentifier()));
+
+    final PayrollDistribution updatedPayrollDistribution = this.customerManager.getPayrollDistribution(customer.getIdentifier());
+
+    this.compare(replacedPayrollDistribution, updatedPayrollDistribution);
+
+    final Optional<PayrollAllocation> optionalPayrollAllocation =
+        updatedPayrollDistribution.getPayrollAllocations().stream().findFirst();
+
+    final PayrollAllocation payrollAllocation = optionalPayrollAllocation.orElseThrow(IllegalStateException::new);
+    Assert.assertEquals(replacedPayrollAllocation.getAccountNumber(), payrollAllocation.getAccountNumber());
+    Assert.assertTrue(replacedPayrollAllocation.getAmount().compareTo(payrollAllocation.getAmount()) == 0);
+    Assert.assertEquals(replacedPayrollAllocation.getProportional(), payrollAllocation.getProportional());
+  }
+
+  private void compare(final PayrollDistribution payrollDistribution, final PayrollDistribution fetchedPayrollDistribution) {
+    Assert.assertEquals(payrollDistribution.getMainAccountNumber(), fetchedPayrollDistribution.getMainAccountNumber());
+    Assert.assertTrue(payrollDistribution.getPayrollAllocations().size() == fetchedPayrollDistribution.getPayrollAllocations().size());
+  }
+}
diff --git a/component-test/src/main/java/io/mifos/customer/catalog/TestCatalog.java b/component-test/src/main/java/io/mifos/customer/catalog/TestCatalog.java
index e538822..2fe10c6 100644
--- a/component-test/src/main/java/io/mifos/customer/catalog/TestCatalog.java
+++ b/component-test/src/main/java/io/mifos/customer/catalog/TestCatalog.java
@@ -34,7 +34,12 @@ import io.mifos.customer.catalog.api.v1.domain.Value;
 import io.mifos.customer.catalog.util.CatalogGenerator;
 import io.mifos.customer.service.rest.config.CustomerRestConfiguration;
 import io.mifos.customer.util.CustomerGenerator;
-import org.junit.*;
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.ClassRule;
+import org.junit.Rule;
+import org.junit.Test;
 import org.junit.rules.RuleChain;
 import org.junit.rules.TestRule;
 import org.junit.runner.RunWith;
diff --git a/component-test/src/main/java/io/mifos/customer/listener/CustomerEventListener.java b/component-test/src/main/java/io/mifos/customer/listener/CustomerEventListener.java
index 5c1d2e1..47ac0b5 100644
--- a/component-test/src/main/java/io/mifos/customer/listener/CustomerEventListener.java
+++ b/component-test/src/main/java/io/mifos/customer/listener/CustomerEventListener.java
@@ -178,4 +178,13 @@ public class CustomerEventListener {
                                final String payload) {
     this.eventRecorder.event(tenant, CustomerEventConstants.DELETE_PORTRAIT, payload, String.class);
   }
+
+  @JmsListener(
+      destination = CustomerEventConstants.DESTINATION,
+      selector = CustomerEventConstants.SELECTOR_PUT_PAYROLL_DISTRIBUTION
+  )
+  public void onSetPayrollDistribution(@Header(TenantHeaderFilter.TENANT_HEADER) final String tenant,
+                                       final String payload) {
+    this.eventRecorder.event(tenant, CustomerEventConstants.PUT_PAYROLL_DISTRIBUTION, payload, String.class);
+  }
 }
diff --git a/component-test/src/main/java/io/mifos/customer/util/ScanGenerator.java b/component-test/src/main/java/io/mifos/customer/util/ScanGenerator.java
index b139623..b029496 100644
--- a/component-test/src/main/java/io/mifos/customer/util/ScanGenerator.java
+++ b/component-test/src/main/java/io/mifos/customer/util/ScanGenerator.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2016 The Mifos Initiative.
+ * Copyright 2017 The Mifos Initiative.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
diff --git a/component-test/src/main/resources/logback.xml b/component-test/src/main/resources/logback-test.xml
similarity index 100%
rename from component-test/src/main/resources/logback.xml
rename to component-test/src/main/resources/logback-test.xml
diff --git a/service/src/main/java/io/mifos/customer/catalog/service/internal/command/handler/CatalogAggregate.java b/service/src/main/java/io/mifos/customer/catalog/service/internal/command/handler/CatalogAggregate.java
index 31de88d..c464e3f 100644
--- a/service/src/main/java/io/mifos/customer/catalog/service/internal/command/handler/CatalogAggregate.java
+++ b/service/src/main/java/io/mifos/customer/catalog/service/internal/command/handler/CatalogAggregate.java
@@ -20,11 +20,11 @@ import io.mifos.core.command.annotation.CommandHandler;
 import io.mifos.core.command.annotation.EventEmitter;
 import io.mifos.customer.catalog.api.v1.CatalogEventConstants;
 import io.mifos.customer.catalog.api.v1.domain.Catalog;
-import io.mifos.customer.catalog.service.internal.repository.CatalogEntity;
-import io.mifos.customer.catalog.service.internal.repository.CatalogRepository;
 import io.mifos.customer.catalog.service.internal.command.CreateCatalogCommand;
 import io.mifos.customer.catalog.service.internal.mapper.CatalogMapper;
 import io.mifos.customer.catalog.service.internal.mapper.FieldMapper;
+import io.mifos.customer.catalog.service.internal.repository.CatalogEntity;
+import io.mifos.customer.catalog.service.internal.repository.CatalogRepository;
 import io.mifos.customer.service.ServiceConstants;
 import org.slf4j.Logger;
 import org.springframework.beans.factory.annotation.Autowired;
diff --git a/service/src/main/java/io/mifos/customer/catalog/service/internal/service/FieldValueValidator.java b/service/src/main/java/io/mifos/customer/catalog/service/internal/service/FieldValueValidator.java
index 35500f7..75c13d1 100644
--- a/service/src/main/java/io/mifos/customer/catalog/service/internal/service/FieldValueValidator.java
+++ b/service/src/main/java/io/mifos/customer/catalog/service/internal/service/FieldValueValidator.java
@@ -21,9 +21,9 @@ import io.mifos.customer.catalog.api.v1.domain.Field;
 import io.mifos.customer.catalog.api.v1.domain.Value;
 import io.mifos.customer.catalog.service.internal.repository.CatalogEntity;
 import io.mifos.customer.catalog.service.internal.repository.CatalogRepository;
+import io.mifos.customer.catalog.service.internal.repository.FieldEntity;
 import io.mifos.customer.catalog.service.internal.repository.FieldRepository;
 import io.mifos.customer.catalog.service.internal.repository.OptionEntity;
-import io.mifos.customer.catalog.service.internal.repository.FieldEntity;
 import io.mifos.customer.service.ServiceConstants;
 import org.slf4j.Logger;
 import org.springframework.beans.factory.annotation.Autowired;
diff --git a/service/src/main/java/io/mifos/customer/catalog/service/rest/controller/CatalogRestController.java b/service/src/main/java/io/mifos/customer/catalog/service/rest/controller/CatalogRestController.java
index 84a4a33..96fe00e 100644
--- a/service/src/main/java/io/mifos/customer/catalog/service/rest/controller/CatalogRestController.java
+++ b/service/src/main/java/io/mifos/customer/catalog/service/rest/controller/CatalogRestController.java
@@ -21,8 +21,8 @@ import io.mifos.core.command.gateway.CommandGateway;
 import io.mifos.core.lang.ServiceException;
 import io.mifos.customer.PermittableGroupIds;
 import io.mifos.customer.catalog.api.v1.domain.Catalog;
-import io.mifos.customer.catalog.service.internal.service.CatalogService;
 import io.mifos.customer.catalog.service.internal.command.CreateCatalogCommand;
+import io.mifos.customer.catalog.service.internal.service.CatalogService;
 import io.mifos.customer.service.ServiceConstants;
 import org.slf4j.Logger;
 import org.springframework.beans.factory.annotation.Autowired;
diff --git a/service/src/main/java/io/mifos/customer/service/internal/command/CreateIdentificationCardScanCommand.java b/service/src/main/java/io/mifos/customer/service/internal/command/CreateIdentificationCardScanCommand.java
index 166ba69..be9831b 100644
--- a/service/src/main/java/io/mifos/customer/service/internal/command/CreateIdentificationCardScanCommand.java
+++ b/service/src/main/java/io/mifos/customer/service/internal/command/CreateIdentificationCardScanCommand.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2016 The Mifos Initiative.
+ * Copyright 2017 The Mifos Initiative.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
diff --git a/service/src/main/java/io/mifos/customer/service/internal/command/DeleteIdentificationCardScanCommand.java b/service/src/main/java/io/mifos/customer/service/internal/command/DeleteIdentificationCardScanCommand.java
index 2240c0a..3961445 100644
--- a/service/src/main/java/io/mifos/customer/service/internal/command/DeleteIdentificationCardScanCommand.java
+++ b/service/src/main/java/io/mifos/customer/service/internal/command/DeleteIdentificationCardScanCommand.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2016 The Mifos Initiative.
+ * Copyright 2017 The Mifos Initiative.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
diff --git a/service/src/main/java/io/mifos/customer/service/internal/command/SetPayrollDistributionCommand.java b/service/src/main/java/io/mifos/customer/service/internal/command/SetPayrollDistributionCommand.java
new file mode 100644
index 0000000..9d456dd
--- /dev/null
+++ b/service/src/main/java/io/mifos/customer/service/internal/command/SetPayrollDistributionCommand.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2017 The Mifos Initiative.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.mifos.customer.service.internal.command;
+
+import io.mifos.customer.api.v1.domain.PayrollDistribution;
+
+public class SetPayrollDistributionCommand {
+  private final String customerIdentifier;
+  private final PayrollDistribution payrollDistribution;
+
+  public SetPayrollDistributionCommand(final String customerIdentifier,
+                                       final PayrollDistribution payrollDistribution) {
+    super();
+    this.customerIdentifier = customerIdentifier;
+    this.payrollDistribution = payrollDistribution;
+  }
+
+  public String customerIdentifier() {
+    return this.customerIdentifier;
+  }
+
+  public PayrollDistribution payrollDistribution() {
+    return this.payrollDistribution;
+  }
+}
diff --git a/service/src/main/java/io/mifos/customer/service/internal/command/handler/CustomerAggregate.java b/service/src/main/java/io/mifos/customer/service/internal/command/handler/CustomerAggregate.java
index 7ec7770..917b364 100644
--- a/service/src/main/java/io/mifos/customer/service/internal/command/handler/CustomerAggregate.java
+++ b/service/src/main/java/io/mifos/customer/service/internal/command/handler/CustomerAggregate.java
@@ -24,6 +24,7 @@ import io.mifos.core.lang.ServiceException;
 import io.mifos.customer.api.v1.CustomerEventConstants;
 import io.mifos.customer.api.v1.domain.Command;
 import io.mifos.customer.api.v1.domain.Customer;
+import io.mifos.customer.api.v1.domain.PayrollDistribution;
 import io.mifos.customer.api.v1.events.ScanEvent;
 import io.mifos.customer.catalog.service.internal.repository.CatalogEntity;
 import io.mifos.customer.catalog.service.internal.repository.CatalogRepository;
@@ -31,10 +32,52 @@ import io.mifos.customer.catalog.service.internal.repository.FieldEntity;
 import io.mifos.customer.catalog.service.internal.repository.FieldRepository;
 import io.mifos.customer.catalog.service.internal.repository.FieldValueEntity;
 import io.mifos.customer.catalog.service.internal.repository.FieldValueRepository;
-import io.mifos.customer.service.internal.command.*;
-import io.mifos.customer.service.internal.mapper.*;
-import io.mifos.customer.service.internal.repository.*;
+import io.mifos.customer.service.ServiceConstants;
+import io.mifos.customer.service.internal.command.ActivateCustomerCommand;
+import io.mifos.customer.service.internal.command.CloseCustomerCommand;
+import io.mifos.customer.service.internal.command.CreateCustomerCommand;
+import io.mifos.customer.service.internal.command.CreateIdentificationCardCommand;
+import io.mifos.customer.service.internal.command.CreateIdentificationCardScanCommand;
+import io.mifos.customer.service.internal.command.CreatePortraitCommand;
+import io.mifos.customer.service.internal.command.DeleteIdentificationCardCommand;
+import io.mifos.customer.service.internal.command.DeleteIdentificationCardScanCommand;
+import io.mifos.customer.service.internal.command.DeletePortraitCommand;
+import io.mifos.customer.service.internal.command.LockCustomerCommand;
+import io.mifos.customer.service.internal.command.ReopenCustomerCommand;
+import io.mifos.customer.service.internal.command.SetPayrollDistributionCommand;
+import io.mifos.customer.service.internal.command.UnlockCustomerCommand;
+import io.mifos.customer.service.internal.command.UpdateAddressCommand;
+import io.mifos.customer.service.internal.command.UpdateContactDetailsCommand;
+import io.mifos.customer.service.internal.command.UpdateCustomerCommand;
+import io.mifos.customer.service.internal.command.UpdateIdentificationCardCommand;
+import io.mifos.customer.service.internal.mapper.AddressMapper;
+import io.mifos.customer.service.internal.mapper.CommandMapper;
+import io.mifos.customer.service.internal.mapper.ContactDetailMapper;
+import io.mifos.customer.service.internal.mapper.CustomerMapper;
+import io.mifos.customer.service.internal.mapper.FieldValueMapper;
+import io.mifos.customer.service.internal.mapper.IdentificationCardMapper;
+import io.mifos.customer.service.internal.mapper.IdentificationCardScanMapper;
+import io.mifos.customer.service.internal.mapper.PortraitMapper;
+import io.mifos.customer.service.internal.repository.AddressEntity;
+import io.mifos.customer.service.internal.repository.AddressRepository;
+import io.mifos.customer.service.internal.repository.CommandRepository;
+import io.mifos.customer.service.internal.repository.ContactDetailEntity;
+import io.mifos.customer.service.internal.repository.ContactDetailRepository;
+import io.mifos.customer.service.internal.repository.CustomerEntity;
+import io.mifos.customer.service.internal.repository.CustomerRepository;
+import io.mifos.customer.service.internal.repository.IdentificationCardEntity;
+import io.mifos.customer.service.internal.repository.IdentificationCardRepository;
+import io.mifos.customer.service.internal.repository.IdentificationCardScanEntity;
+import io.mifos.customer.service.internal.repository.IdentificationCardScanRepository;
+import io.mifos.customer.service.internal.repository.PayrollAllocationEntity;
+import io.mifos.customer.service.internal.repository.PayrollAllocationRepository;
+import io.mifos.customer.service.internal.repository.PayrollDistributionEntity;
+import io.mifos.customer.service.internal.repository.PayrollDistributionRepository;
+import io.mifos.customer.service.internal.repository.PortraitEntity;
+import io.mifos.customer.service.internal.repository.PortraitRepository;
+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 org.springframework.web.multipart.MultipartFile;
 
@@ -51,6 +94,7 @@ import java.util.stream.Collectors;
 @Aggregate
 public class CustomerAggregate {
 
+  private final Logger logger;
   private final AddressRepository addressRepository;
   private final CustomerRepository customerRepository;
   private final IdentificationCardRepository identificationCardRepository;
@@ -62,9 +106,12 @@ public class CustomerAggregate {
   private final FieldRepository fieldRepository;
   private final CommandRepository commandRepository;
   private final TaskAggregate taskAggregate;
+  private final PayrollDistributionRepository payrollDistributionRepository;
+  private final PayrollAllocationRepository payrollAllocationRepository;
 
   @Autowired
-  public CustomerAggregate(final AddressRepository addressRepository,
+  public CustomerAggregate(@Qualifier(ServiceConstants.LOGGER_NAME) final Logger logger,
+                           final AddressRepository addressRepository,
                            final CustomerRepository customerRepository,
                            final IdentificationCardRepository identificationCardRepository,
                            final IdentificationCardScanRepository identificationCardScanRepository,
@@ -74,8 +121,11 @@ public class CustomerAggregate {
                            final CatalogRepository catalogRepository,
                            final FieldRepository fieldRepository,
                            final CommandRepository commandRepository,
-                           final TaskAggregate taskAggregate) {
+                           final TaskAggregate taskAggregate,
+                           final PayrollDistributionRepository payrollDistributionRepository,
+                           final PayrollAllocationRepository payrollAllocationRepository) {
     super();
+    this.logger = logger;
     this.addressRepository = addressRepository;
     this.customerRepository = customerRepository;
     this.identificationCardRepository = identificationCardRepository;
@@ -87,6 +137,8 @@ public class CustomerAggregate {
     this.fieldRepository = fieldRepository;
     this.commandRepository = commandRepository;
     this.taskAggregate = taskAggregate;
+    this.payrollDistributionRepository = payrollDistributionRepository;
+    this.payrollAllocationRepository = payrollAllocationRepository;
   }
 
   @Transactional
@@ -496,6 +548,55 @@ public class CustomerAggregate {
     return deletePortraitCommand.identifier();
   }
 
+  @Transactional
+  @CommandHandler
+  @EventEmitter(selectorName = CustomerEventConstants.SELECTOR_NAME, selectorValue = CustomerEventConstants.PUT_PAYROLL_DISTRIBUTION)
+  public String setPayrollDistribution(final SetPayrollDistributionCommand setPayrollDistributionCommand) {
+    final CustomerEntity customerEntity =
+        this.customerRepository.findByIdentifier(setPayrollDistributionCommand.customerIdentifier());
+
+    final LocalDateTime now = LocalDateTime.now(Clock.systemUTC());
+
+    final PayrollDistributionEntity payrollDistributionEntity =
+        this.payrollDistributionRepository.findByCustomer(customerEntity).orElseGet(() -> {
+          final PayrollDistributionEntity newPayrollDistribution = new PayrollDistributionEntity();
+          newPayrollDistribution.setCustomer(customerEntity);
+          newPayrollDistribution.setCreatedBy(UserContextHolder.checkedGetUser());
+          newPayrollDistribution.setCreatedOn(now);
+          return newPayrollDistribution;
+        });
+
+    if (payrollDistributionEntity.getId() != null) {
+      this.payrollAllocationRepository.deleteByPayrollDistribution(payrollDistributionEntity);
+      this.payrollAllocationRepository.flush();
+
+      payrollDistributionEntity.setLastModifiedBy(UserContextHolder.checkedGetUser());
+      payrollDistributionEntity.setLastModifiedOn(now);
+      payrollDistributionEntity.setPayrollAllocationEntities(null);
+    }
+
+    final PayrollDistribution payrollDistribution = setPayrollDistributionCommand.payrollDistribution();
+    payrollDistributionEntity.setMainAccountNumber(payrollDistribution.getMainAccountNumber());
+
+    final PayrollDistributionEntity savedPayrollDistributionEntity =
+        this.payrollDistributionRepository.save(payrollDistributionEntity);
+
+    this.payrollAllocationRepository.save(payrollDistribution.getPayrollAllocations()
+        .stream()
+        .map(payrollAllocation -> {
+          final PayrollAllocationEntity payrollAllocationEntity = new PayrollAllocationEntity();
+          payrollAllocationEntity.setPayrollDistribution(savedPayrollDistributionEntity);
+          payrollAllocationEntity.setAccountNumber(payrollAllocation.getAccountNumber());
+          payrollAllocationEntity.setAmount(payrollAllocation.getAmount());
+          payrollAllocationEntity.setProportional(payrollAllocation.getProportional());
+          return payrollAllocationEntity;
+        })
+        .collect(Collectors.toList())
+    );
+
+    return setPayrollDistributionCommand.customerIdentifier();
+  }
+
   private void setCustomValues(final Customer customer, final CustomerEntity savedCustomerEntity) {
     this.fieldValueRepository.save(
         customer.getCustomValues()
diff --git a/service/src/main/java/io/mifos/customer/service/internal/mapper/IdentificationCardScanMapper.java b/service/src/main/java/io/mifos/customer/service/internal/mapper/IdentificationCardScanMapper.java
index 346adfb..e451d65 100644
--- a/service/src/main/java/io/mifos/customer/service/internal/mapper/IdentificationCardScanMapper.java
+++ b/service/src/main/java/io/mifos/customer/service/internal/mapper/IdentificationCardScanMapper.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2016 The Mifos Initiative.
+ * Copyright 2017 The Mifos Initiative.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
diff --git a/service/src/main/java/io/mifos/customer/service/internal/mapper/PayrollDistributionMapper.java b/service/src/main/java/io/mifos/customer/service/internal/mapper/PayrollDistributionMapper.java
new file mode 100644
index 0000000..c7cc514
--- /dev/null
+++ b/service/src/main/java/io/mifos/customer/service/internal/mapper/PayrollDistributionMapper.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2017 The Mifos Initiative.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.mifos.customer.service.internal.mapper;
+
+import io.mifos.core.lang.DateConverter;
+import io.mifos.customer.api.v1.domain.PayrollAllocation;
+import io.mifos.customer.api.v1.domain.PayrollDistribution;
+import io.mifos.customer.service.internal.repository.PayrollDistributionEntity;
+
+import java.util.stream.Collectors;
+
+public class PayrollDistributionMapper {
+
+  private PayrollDistributionMapper() {
+    super();
+  }
+
+  public static PayrollDistribution map(final PayrollDistributionEntity payrollDistributionEntity) {
+    final PayrollDistribution payrollDistribution = new PayrollDistribution();
+    payrollDistribution.setMainAccountNumber(payrollDistributionEntity.getMainAccountNumber());
+    payrollDistribution.setCreatedBy(payrollDistributionEntity.getCreatedBy());
+    payrollDistribution.setCreatedOn(DateConverter.toIsoString(payrollDistributionEntity.getCreatedOn()));
+    if (payrollDistributionEntity.getLastModifiedBy() != null) {
+      payrollDistribution.setLastModifiedBy(payrollDistributionEntity.getLastModifiedBy());
+      payrollDistribution.setLastModifiedOn(DateConverter.toIsoString(payrollDistributionEntity.getLastModifiedOn()));
+    }
+
+    if (payrollDistributionEntity.getPayrollAllocationEntities() != null) {
+      payrollDistribution.setPayrollAllocations(
+          payrollDistributionEntity.getPayrollAllocationEntities().stream().map(payrollAllocationEntity -> {
+            final PayrollAllocation payrollAllocation = new PayrollAllocation();
+            payrollAllocation.setAccountNumber(payrollAllocationEntity.getAccountNumber());
+            payrollAllocation.setAmount(payrollAllocationEntity.getAmount());
+            payrollAllocation.setProportional(payrollAllocationEntity.getProportional());
+            return payrollAllocation;
+          }).collect(Collectors.toSet())
+      );
+    }
+
+    return payrollDistribution;
+  }
+}
diff --git a/service/src/main/java/io/mifos/customer/service/internal/mapper/TaskInstanceMapper.java b/service/src/main/java/io/mifos/customer/service/internal/mapper/TaskInstanceMapper.java
index 2ffbb1b..2b05865 100644
--- a/service/src/main/java/io/mifos/customer/service/internal/mapper/TaskInstanceMapper.java
+++ b/service/src/main/java/io/mifos/customer/service/internal/mapper/TaskInstanceMapper.java
@@ -18,8 +18,8 @@ package io.mifos.customer.service.internal.mapper;
 import io.mifos.core.lang.DateConverter;
 import io.mifos.customer.api.v1.domain.TaskInstance;
 import io.mifos.customer.service.internal.repository.CustomerEntity;
-import io.mifos.customer.service.internal.repository.TaskInstanceEntity;
 import io.mifos.customer.service.internal.repository.TaskDefinitionEntity;
+import io.mifos.customer.service.internal.repository.TaskInstanceEntity;
 
 public class TaskInstanceMapper {
 
diff --git a/service/src/main/java/io/mifos/customer/service/internal/repository/IdentificationCardScanEntity.java b/service/src/main/java/io/mifos/customer/service/internal/repository/IdentificationCardScanEntity.java
index acd9808..84c2e34 100644
--- a/service/src/main/java/io/mifos/customer/service/internal/repository/IdentificationCardScanEntity.java
+++ b/service/src/main/java/io/mifos/customer/service/internal/repository/IdentificationCardScanEntity.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2016 The Mifos Initiative.
+ * Copyright 2017 The Mifos Initiative.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -16,9 +16,18 @@
 package io.mifos.customer.service.internal.repository;
 
 import io.mifos.core.mariadb.util.LocalDateTimeConverter;
-import io.mifos.customer.api.v1.domain.IdentificationCard;
 
-import javax.persistence.*;
+import javax.persistence.Column;
+import javax.persistence.Convert;
+import javax.persistence.Entity;
+import javax.persistence.FetchType;
+import javax.persistence.GeneratedValue;
+import javax.persistence.GenerationType;
+import javax.persistence.Id;
+import javax.persistence.JoinColumn;
+import javax.persistence.Lob;
+import javax.persistence.ManyToOne;
+import javax.persistence.Table;
 import java.time.LocalDateTime;
 
 @Entity
diff --git a/service/src/main/java/io/mifos/customer/service/internal/repository/IdentificationCardScanRepository.java b/service/src/main/java/io/mifos/customer/service/internal/repository/IdentificationCardScanRepository.java
index dded654..ce149cd 100644
--- a/service/src/main/java/io/mifos/customer/service/internal/repository/IdentificationCardScanRepository.java
+++ b/service/src/main/java/io/mifos/customer/service/internal/repository/IdentificationCardScanRepository.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2016 The Mifos Initiative.
+ * Copyright 2017 The Mifos Initiative.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
diff --git a/service/src/main/java/io/mifos/customer/service/internal/repository/PayrollAllocationEntity.java b/service/src/main/java/io/mifos/customer/service/internal/repository/PayrollAllocationEntity.java
new file mode 100644
index 0000000..a08907c
--- /dev/null
+++ b/service/src/main/java/io/mifos/customer/service/internal/repository/PayrollAllocationEntity.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright 2017 The Mifos Initiative.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.mifos.customer.service.internal.repository;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.GeneratedValue;
+import javax.persistence.GenerationType;
+import javax.persistence.Id;
+import javax.persistence.JoinColumn;
+import javax.persistence.ManyToOne;
+import javax.persistence.Table;
+import java.math.BigDecimal;
+
+@Entity
+@Table(name = "maat_payroll_allocations")
+public class PayrollAllocationEntity {
+
+  @Id
+  @GeneratedValue(strategy = GenerationType.IDENTITY)
+  @Column(name = "id", nullable = false)
+  private Long id;
+  @ManyToOne
+  @JoinColumn(name = "payroll_distribution_id", nullable = false)
+  private PayrollDistributionEntity payrollDistribution;
+  @Column(name = "account_number", nullable = false, length = 34)
+  private String accountNumber;
+  @Column(name = "amount", nullable = false, precision = 15, scale = 5)
+  private BigDecimal amount;
+  @Column(name = "proportional", nullable = false)
+  private Boolean proportional = Boolean.FALSE;
+
+  public PayrollAllocationEntity() {
+    super();
+  }
+
+  public Long getId() {
+    return this.id;
+  }
+
+  public void setId(final Long id) {
+    this.id = id;
+  }
+
+  public PayrollDistributionEntity getPayrollDistribution() {
+    return this.payrollDistribution;
+  }
+
+  public void setPayrollDistribution(final PayrollDistributionEntity payrollDistribution) {
+    this.payrollDistribution = payrollDistribution;
+  }
+
+  public String getAccountNumber() {
+    return this.accountNumber;
+  }
+
+  public void setAccountNumber(final String accountNumber) {
+    this.accountNumber = accountNumber;
+  }
+
+  public BigDecimal getAmount() {
+    return this.amount;
+  }
+
+  public void setAmount(final BigDecimal amount) {
+    this.amount = amount;
+  }
+
+  public Boolean getProportional() {
+    return this.proportional;
+  }
+
+  public void setProportional(final Boolean proportional) {
+    this.proportional = proportional;
+  }
+}
diff --git a/api/src/main/java/io/mifos/customer/api/v1/client/ScanAlreadyExistsException.java b/service/src/main/java/io/mifos/customer/service/internal/repository/PayrollAllocationRepository.java
similarity index 61%
copy from api/src/main/java/io/mifos/customer/api/v1/client/ScanAlreadyExistsException.java
copy to service/src/main/java/io/mifos/customer/service/internal/repository/PayrollAllocationRepository.java
index 45ef5a1..9d418f3 100644
--- a/api/src/main/java/io/mifos/customer/api/v1/client/ScanAlreadyExistsException.java
+++ b/service/src/main/java/io/mifos/customer/service/internal/repository/PayrollAllocationRepository.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2016 The Mifos Initiative.
+ * Copyright 2017 The Mifos Initiative.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -13,7 +13,10 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package io.mifos.customer.api.v1.client;
+package io.mifos.customer.service.internal.repository;
 
-public final class ScanAlreadyExistsException extends RuntimeException {
+import org.springframework.data.jpa.repository.JpaRepository;
+
+public interface PayrollAllocationRepository extends JpaRepository<PayrollAllocationEntity, Long> {
+  void deleteByPayrollDistribution(final PayrollDistributionEntity payrollDistributionEntity);
 }
diff --git a/service/src/main/java/io/mifos/customer/service/internal/repository/PayrollDistributionEntity.java b/service/src/main/java/io/mifos/customer/service/internal/repository/PayrollDistributionEntity.java
new file mode 100644
index 0000000..5cb62b9
--- /dev/null
+++ b/service/src/main/java/io/mifos/customer/service/internal/repository/PayrollDistributionEntity.java
@@ -0,0 +1,128 @@
+/*
+ * Copyright 2017 The Mifos Initiative.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.mifos.customer.service.internal.repository;
+
+import io.mifos.core.mariadb.util.LocalDateTimeConverter;
+
+import javax.persistence.CascadeType;
+import javax.persistence.Column;
+import javax.persistence.Convert;
+import javax.persistence.Entity;
+import javax.persistence.FetchType;
+import javax.persistence.GeneratedValue;
+import javax.persistence.GenerationType;
+import javax.persistence.Id;
+import javax.persistence.JoinColumn;
+import javax.persistence.OneToMany;
+import javax.persistence.OneToOne;
+import javax.persistence.Table;
+import java.time.LocalDateTime;
+import java.util.List;
+
+@Entity
+@Table(name = "maat_payroll_distributions")
+public class PayrollDistributionEntity {
+
+  @Id
+  @GeneratedValue(strategy = GenerationType.IDENTITY)
+  @Column(name = "id", nullable = false)
+  private Long id;
+  @OneToOne(cascade = CascadeType.ALL)
+  @JoinColumn(name = "customer_id", nullable = false)
+  private CustomerEntity customer;
+  @Column(name = "main_account_number", nullable = false, length = 34)
+  private String mainAccountNumber;
+  @OneToMany(fetch = FetchType.EAGER, mappedBy = "payrollDistribution")
+  private List<PayrollAllocationEntity> payrollAllocationEntities;
+  @Column(name = "created_by")
+  private String createdBy;
+  @Column(name = "created_on")
+  @Convert(converter = LocalDateTimeConverter.class)
+  private LocalDateTime createdOn;
+  @Column(name = "last_modified_by")
+  private String lastModifiedBy;
+  @Column(name = "last_modified_on")
+  @Convert(converter = LocalDateTimeConverter.class)
+  private LocalDateTime lastModifiedOn;
+
+  public PayrollDistributionEntity() {
+    super();
+  }
+
+  public Long getId() {
+    return this.id;
+  }
+
+  public void setId(final Long id) {
+    this.id = id;
+  }
+
+  public CustomerEntity getCustomer() {
+    return this.customer;
+  }
+
+  public void setCustomer(final CustomerEntity customer) {
+    this.customer = customer;
+  }
+
+  public String getMainAccountNumber() {
+    return this.mainAccountNumber;
+  }
+
+  public void setMainAccountNumber(final String mainAccountNumber) {
+    this.mainAccountNumber = mainAccountNumber;
+  }
+
+  public List<PayrollAllocationEntity> getPayrollAllocationEntities() {
+    return this.payrollAllocationEntities;
+  }
+
+  public void setPayrollAllocationEntities(final List<PayrollAllocationEntity> payrollAllocationEntities) {
+    this.payrollAllocationEntities = payrollAllocationEntities;
+  }
+
+  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/api/src/main/java/io/mifos/customer/api/v1/client/ScanAlreadyExistsException.java b/service/src/main/java/io/mifos/customer/service/internal/repository/PayrollDistributionRepository.java
similarity index 59%
copy from api/src/main/java/io/mifos/customer/api/v1/client/ScanAlreadyExistsException.java
copy to service/src/main/java/io/mifos/customer/service/internal/repository/PayrollDistributionRepository.java
index 45ef5a1..f332432 100644
--- a/api/src/main/java/io/mifos/customer/api/v1/client/ScanAlreadyExistsException.java
+++ b/service/src/main/java/io/mifos/customer/service/internal/repository/PayrollDistributionRepository.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2016 The Mifos Initiative.
+ * Copyright 2017 The Mifos Initiative.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -13,7 +13,12 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package io.mifos.customer.api.v1.client;
+package io.mifos.customer.service.internal.repository;
 
-public final class ScanAlreadyExistsException extends RuntimeException {
+import org.springframework.data.jpa.repository.JpaRepository;
+
+import java.util.Optional;
+
+public interface PayrollDistributionRepository extends JpaRepository<PayrollDistributionEntity, Long> {
+  Optional<PayrollDistributionEntity> findByCustomer(final CustomerEntity customerEntity);
 }
diff --git a/service/src/main/java/io/mifos/customer/service/internal/repository/PortraitEntity.java b/service/src/main/java/io/mifos/customer/service/internal/repository/PortraitEntity.java
index 3d734b7..21231a7 100644
--- a/service/src/main/java/io/mifos/customer/service/internal/repository/PortraitEntity.java
+++ b/service/src/main/java/io/mifos/customer/service/internal/repository/PortraitEntity.java
@@ -15,7 +15,16 @@
  */
 package io.mifos.customer.service.internal.repository;
 
-import javax.persistence.*;
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.FetchType;
+import javax.persistence.GeneratedValue;
+import javax.persistence.GenerationType;
+import javax.persistence.Id;
+import javax.persistence.JoinColumn;
+import javax.persistence.Lob;
+import javax.persistence.OneToOne;
+import javax.persistence.Table;
 
 @Entity
 @Table(name = "maat_portraits")
diff --git a/service/src/main/java/io/mifos/customer/service/internal/service/CustomerService.java b/service/src/main/java/io/mifos/customer/service/internal/service/CustomerService.java
index f7fd56f..3901f0d 100644
--- a/service/src/main/java/io/mifos/customer/service/internal/service/CustomerService.java
+++ b/service/src/main/java/io/mifos/customer/service/internal/service/CustomerService.java
@@ -15,14 +15,42 @@
  */
 package io.mifos.customer.service.internal.service;
 
-import io.mifos.customer.api.v1.domain.*;
+import io.mifos.customer.api.v1.domain.Command;
+import io.mifos.customer.api.v1.domain.Customer;
+import io.mifos.customer.api.v1.domain.CustomerPage;
+import io.mifos.customer.api.v1.domain.IdentificationCard;
+import io.mifos.customer.api.v1.domain.IdentificationCardScan;
+import io.mifos.customer.api.v1.domain.PayrollDistribution;
+import io.mifos.customer.api.v1.domain.ProcessStep;
+import io.mifos.customer.api.v1.domain.TaskDefinition;
 import io.mifos.customer.catalog.api.v1.domain.Value;
 import io.mifos.customer.catalog.service.internal.repository.FieldEntity;
 import io.mifos.customer.catalog.service.internal.repository.FieldValueEntity;
 import io.mifos.customer.catalog.service.internal.repository.FieldValueRepository;
 import io.mifos.customer.service.ServiceConstants;
-import io.mifos.customer.service.internal.mapper.*;
-import io.mifos.customer.service.internal.repository.*;
+import io.mifos.customer.service.internal.mapper.AddressMapper;
+import io.mifos.customer.service.internal.mapper.CommandMapper;
+import io.mifos.customer.service.internal.mapper.ContactDetailMapper;
+import io.mifos.customer.service.internal.mapper.CustomerMapper;
+import io.mifos.customer.service.internal.mapper.IdentificationCardMapper;
+import io.mifos.customer.service.internal.mapper.IdentificationCardScanMapper;
+import io.mifos.customer.service.internal.mapper.PayrollDistributionMapper;
+import io.mifos.customer.service.internal.mapper.TaskDefinitionMapper;
+import io.mifos.customer.service.internal.repository.CommandEntity;
+import io.mifos.customer.service.internal.repository.CommandRepository;
+import io.mifos.customer.service.internal.repository.ContactDetailEntity;
+import io.mifos.customer.service.internal.repository.ContactDetailRepository;
+import io.mifos.customer.service.internal.repository.CustomerEntity;
+import io.mifos.customer.service.internal.repository.CustomerRepository;
+import io.mifos.customer.service.internal.repository.IdentificationCardEntity;
+import io.mifos.customer.service.internal.repository.IdentificationCardRepository;
+import io.mifos.customer.service.internal.repository.IdentificationCardScanEntity;
+import io.mifos.customer.service.internal.repository.IdentificationCardScanRepository;
+import io.mifos.customer.service.internal.repository.PayrollDistributionRepository;
+import io.mifos.customer.service.internal.repository.PortraitEntity;
+import io.mifos.customer.service.internal.repository.PortraitRepository;
+import io.mifos.customer.service.internal.repository.TaskDefinitionRepository;
+import io.mifos.customer.service.internal.repository.TaskInstanceRepository;
 import org.slf4j.Logger;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.beans.factory.annotation.Qualifier;
@@ -49,6 +77,7 @@ public class CustomerService {
   private final CommandRepository commandRepository;
   private final TaskDefinitionRepository taskDefinitionRepository;
   private final TaskInstanceRepository taskInstanceRepository;
+  private final PayrollDistributionRepository payrollDistributionRepository;
 
   @Autowired
   public CustomerService(@Qualifier(ServiceConstants.LOGGER_NAME) final Logger logger,
@@ -60,7 +89,8 @@ public class CustomerService {
                          final FieldValueRepository fieldValueRepository,
                          final CommandRepository commandRepository,
                          final TaskDefinitionRepository taskDefinitionRepository,
-                         final TaskInstanceRepository taskInstanceRepository) {
+                         final TaskInstanceRepository taskInstanceRepository,
+                         final PayrollDistributionRepository payrollDistributionRepository) {
     super();
     this.logger = logger;
     this.customerRepository = customerRepository;
@@ -72,6 +102,7 @@ public class CustomerService {
     this.commandRepository = commandRepository;
     this.taskDefinitionRepository = taskDefinitionRepository;
     this.taskInstanceRepository = taskInstanceRepository;
+    this.payrollDistributionRepository = payrollDistributionRepository;
   }
 
   public Boolean customerExists(final String identifier) {
@@ -263,4 +294,9 @@ public class CustomerService {
 
     return processStep;
   }
+
+  public Optional<PayrollDistribution> getPayrollDistribution(final String customerIdentifier) {
+    final CustomerEntity customerEntity = this.customerRepository.findByIdentifier(customerIdentifier);
+    return this.payrollDistributionRepository.findByCustomer(customerEntity).map(PayrollDistributionMapper::map);
+  }
 }
diff --git a/service/src/main/java/io/mifos/customer/service/internal/service/TaskService.java b/service/src/main/java/io/mifos/customer/service/internal/service/TaskService.java
index a6100f5..66132fc 100644
--- a/service/src/main/java/io/mifos/customer/service/internal/service/TaskService.java
+++ b/service/src/main/java/io/mifos/customer/service/internal/service/TaskService.java
@@ -19,9 +19,9 @@ import io.mifos.customer.api.v1.domain.TaskDefinition;
 import io.mifos.customer.service.internal.mapper.TaskDefinitionMapper;
 import io.mifos.customer.service.internal.repository.CustomerEntity;
 import io.mifos.customer.service.internal.repository.CustomerRepository;
+import io.mifos.customer.service.internal.repository.TaskDefinitionEntity;
 import io.mifos.customer.service.internal.repository.TaskDefinitionRepository;
 import io.mifos.customer.service.internal.repository.TaskInstanceRepository;
-import io.mifos.customer.service.internal.repository.TaskDefinitionEntity;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 
diff --git a/service/src/main/java/io/mifos/customer/service/rest/controller/CustomerRestController.java b/service/src/main/java/io/mifos/customer/service/rest/controller/CustomerRestController.java
index f68549e..401f1d9 100644
--- a/service/src/main/java/io/mifos/customer/service/rest/controller/CustomerRestController.java
+++ b/service/src/main/java/io/mifos/customer/service/rest/controller/CustomerRestController.java
@@ -22,14 +22,43 @@ import io.mifos.core.command.gateway.CommandGateway;
 import io.mifos.core.lang.ServiceException;
 import io.mifos.core.lang.validation.constraints.ValidIdentifier;
 import io.mifos.customer.PermittableGroupIds;
-import io.mifos.customer.api.v1.domain.*;
+import io.mifos.customer.api.v1.domain.Address;
+import io.mifos.customer.api.v1.domain.Command;
+import io.mifos.customer.api.v1.domain.ContactDetail;
+import io.mifos.customer.api.v1.domain.Customer;
+import io.mifos.customer.api.v1.domain.CustomerPage;
+import io.mifos.customer.api.v1.domain.IdentificationCard;
+import io.mifos.customer.api.v1.domain.IdentificationCardScan;
+import io.mifos.customer.api.v1.domain.PayrollDistribution;
+import io.mifos.customer.api.v1.domain.ProcessStep;
+import io.mifos.customer.api.v1.domain.TaskDefinition;
 import io.mifos.customer.catalog.service.internal.service.FieldValueValidator;
 import io.mifos.customer.service.ServiceConstants;
-import io.mifos.customer.service.internal.command.*;
+import io.mifos.customer.service.internal.command.ActivateCustomerCommand;
+import io.mifos.customer.service.internal.command.AddTaskDefinitionToCustomerCommand;
+import io.mifos.customer.service.internal.command.CloseCustomerCommand;
+import io.mifos.customer.service.internal.command.CreateCustomerCommand;
+import io.mifos.customer.service.internal.command.CreateIdentificationCardCommand;
+import io.mifos.customer.service.internal.command.CreateIdentificationCardScanCommand;
+import io.mifos.customer.service.internal.command.CreatePortraitCommand;
+import io.mifos.customer.service.internal.command.CreateTaskDefinitionCommand;
+import io.mifos.customer.service.internal.command.DeleteIdentificationCardCommand;
+import io.mifos.customer.service.internal.command.DeleteIdentificationCardScanCommand;
+import io.mifos.customer.service.internal.command.DeletePortraitCommand;
+import io.mifos.customer.service.internal.command.ExecuteTaskForCustomerCommand;
+import io.mifos.customer.service.internal.command.InitializeServiceCommand;
+import io.mifos.customer.service.internal.command.LockCustomerCommand;
+import io.mifos.customer.service.internal.command.ReopenCustomerCommand;
+import io.mifos.customer.service.internal.command.SetPayrollDistributionCommand;
+import io.mifos.customer.service.internal.command.UnlockCustomerCommand;
+import io.mifos.customer.service.internal.command.UpdateAddressCommand;
+import io.mifos.customer.service.internal.command.UpdateContactDetailsCommand;
+import io.mifos.customer.service.internal.command.UpdateCustomerCommand;
+import io.mifos.customer.service.internal.command.UpdateIdentificationCardCommand;
+import io.mifos.customer.service.internal.command.UpdateTaskDefinitionCommand;
 import io.mifos.customer.service.internal.repository.PortraitEntity;
 import io.mifos.customer.service.internal.service.CustomerService;
 import io.mifos.customer.service.internal.service.TaskService;
-import org.apache.commons.lang.RandomStringUtils;
 import org.slf4j.Logger;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.beans.factory.annotation.Qualifier;
@@ -39,7 +68,13 @@ import org.springframework.data.domain.Pageable;
 import org.springframework.data.domain.Sort;
 import org.springframework.http.MediaType;
 import org.springframework.http.ResponseEntity;
-import org.springframework.web.bind.annotation.*;
+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 org.springframework.web.multipart.MultipartFile;
 
 import javax.validation.Valid;
@@ -711,6 +746,38 @@ public class CustomerRestController {
     return ResponseEntity.ok(this.customerService.getProcessSteps(customerIdentifier));
   }
 
+  @Permittable(value = AcceptedTokenType.TENANT, groupId = PermittableGroupIds.CUSTOMER)
+  @RequestMapping(
+      value = "customer/{identifier}/payroll",
+      method = RequestMethod.PUT,
+      produces = MediaType.APPLICATION_JSON_VALUE,
+      consumes = MediaType.APPLICATION_JSON_VALUE
+  )
+  public
+  @ResponseBody
+  ResponseEntity<Void> setPayrollDistribution(@PathVariable(value = "identifier") final String customerIdentifier,
+                                              @RequestBody @Valid final PayrollDistribution payrollDistribution) {
+    this.throwIfCustomerNotExists(customerIdentifier);
+    this.commandGateway.process(new SetPayrollDistributionCommand(customerIdentifier, payrollDistribution));
+    return ResponseEntity.accepted().build();
+  }
+
+  @Permittable(value = AcceptedTokenType.TENANT, groupId = PermittableGroupIds.CUSTOMER)
+  @RequestMapping(
+      value = "customer/{identifier}/payroll",
+      method = RequestMethod.GET,
+      produces = MediaType.APPLICATION_JSON_VALUE,
+      consumes = MediaType.ALL_VALUE
+  )
+  public
+  @ResponseBody
+  ResponseEntity<PayrollDistribution> getPayrollDistribution(@PathVariable(value = "identifier") final String customerIdentifier) {
+    this.throwIfCustomerNotExists(customerIdentifier);
+    return ResponseEntity.ok(
+        this.customerService.getPayrollDistribution(customerIdentifier).orElse(null)
+    );
+  }
+
   private Pageable createPageRequest(final Integer pageIndex, final Integer size, final String sortColumn, final String sortDirection) {
     final Integer pageIndexToUse = pageIndex != null ? pageIndex : 0;
     final Integer sizeToUse = size != null ? size : 20;
diff --git a/service/src/main/resources/db/migrations/mariadb/V5__add_payroll_distributions.sql b/service/src/main/resources/db/migrations/mariadb/V5__add_payroll_distributions.sql
new file mode 100644
index 0000000..1a50ac1
--- /dev/null
+++ b/service/src/main/resources/db/migrations/mariadb/V5__add_payroll_distributions.sql
@@ -0,0 +1,39 @@
+--
+-- Copyright 2017 The Mifos Initiative.
+--
+-- Licensed under the Apache License, Version 2.0 (the "License");
+-- you may not use this file except in compliance with the License.
+-- You may obtain a copy of the License at
+--
+--    http://www.apache.org/licenses/LICENSE-2.0
+--
+-- Unless required by applicable law or agreed to in writing, software
+-- distributed under the License is distributed on an "AS IS" BASIS,
+-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+-- See the License for the specific language governing permissions and
+-- limitations under the License.
+--
+
+CREATE TABLE maat_payroll_distributions (
+  id BIGINT NOT NULL AUTO_INCREMENT,
+  customer_id BIGINT NOT NULL,
+  main_account_number VARCHAR(34) NOT NULL,
+  created_by VARCHAR(32) NOT NULL,
+  created_on TIMESTAMP(3) NOT NULL,
+  last_modified_by VARCHAR(32) NULL,
+  last_modified_on TIMESTAMP(3) NULL,
+  CONSTRAINT maat_payroll_distributions_pk PRIMARY KEY (id),
+  CONSTRAINT maat_payroll_dist_main_acct_uq UNIQUE (customer_id, main_account_number),
+  CONSTRAINT maat_payroll_dist_customers_fk FOREIGN KEY (customer_id) REFERENCES maat_customers (id)
+);
+
+CREATE TABLE maat_payroll_allocations (
+  id BIGINT NOT NULL AUTO_INCREMENT,
+  payroll_distribution_id BIGINT NOT NULL,
+  account_number VARCHAR(34) NOT NULL,
+  amount NUMERIC(15,5) NOT NULL,
+  proportional BOOLEAN NOT NULL,
+  CONSTRAINT maat_payroll_allocations_pk PRIMARY KEY (id),
+  CONSTRAINT maat_payroll_alloc_acct_uq UNIQUE (payroll_distribution_id, account_number),
+  CONSTRAINT maat_payroll_alloc_dist_fk FOREIGN KEY (payroll_distribution_id) REFERENCES maat_payroll_distributions (id)
+);
\ No newline at end of file

-- 
To stop receiving notification emails like this one, please contact
myrle@apache.org.

[fineract-cn-customer] 17/46: renamed customer endpoit to customers fixed taks execution

Posted by my...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

myrle pushed a commit to branch develop
in repository https://gitbox.apache.org/repos/asf/fineract-cn-customer.git

commit 5d94ca2d07324881eea0f6fc44f7c1339c6e15d7
Author: mgeiss <mg...@mifos.org>
AuthorDate: Fri Sep 8 09:34:34 2017 +0200

    renamed customer endpoit to customers
    fixed taks execution
---
 .../customer/api/v1/client/CustomerManager.java    |  6 +-
 .../java/io/mifos/customer/TestTaskInstance.java   | 73 +++++++++++++++++++++-
 .../internal/command/handler/TaskAggregate.java    |  5 +-
 .../rest/controller/CustomerRestController.java    |  6 +-
 4 files changed, 80 insertions(+), 10 deletions(-)

diff --git a/api/src/main/java/io/mifos/customer/api/v1/client/CustomerManager.java b/api/src/main/java/io/mifos/customer/api/v1/client/CustomerManager.java
index 67ef6c0..024dd2c 100644
--- a/api/src/main/java/io/mifos/customer/api/v1/client/CustomerManager.java
+++ b/api/src/main/java/io/mifos/customer/api/v1/client/CustomerManager.java
@@ -386,7 +386,7 @@ public interface CustomerManager {
   void updateTask(@PathVariable("identifier") final String identifier, @RequestBody final TaskDefinition taskDefinition);
 
   @RequestMapping(
-      value = "/customer/{identifier}/actions",
+      value = "/customers/{identifier}/actions",
       method = RequestMethod.GET,
       produces = MediaType.ALL_VALUE,
       consumes = MediaType.APPLICATION_JSON_VALUE
@@ -395,7 +395,7 @@ public interface CustomerManager {
   List<ProcessStep> fetchProcessSteps(@PathVariable(value = "identifier") final String customerIdentifier);
 
   @RequestMapping(
-      value = "/customer/{identifier}/payroll",
+      value = "/customers/{identifier}/payroll",
       method = RequestMethod.PUT,
       produces = MediaType.APPLICATION_JSON_VALUE,
       consumes = MediaType.APPLICATION_JSON_VALUE
@@ -408,7 +408,7 @@ public interface CustomerManager {
                               @RequestBody @Valid final PayrollDistribution payrollDistribution);
 
   @RequestMapping(
-      value = "/customer/{identifier}/payroll",
+      value = "/customers/{identifier}/payroll",
       method = RequestMethod.GET,
       produces = MediaType.ALL_VALUE,
       consumes = MediaType.APPLICATION_JSON_VALUE
diff --git a/component-test/src/main/java/io/mifos/customer/TestTaskInstance.java b/component-test/src/main/java/io/mifos/customer/TestTaskInstance.java
index a35fa97..7ff792c 100644
--- a/component-test/src/main/java/io/mifos/customer/TestTaskInstance.java
+++ b/component-test/src/main/java/io/mifos/customer/TestTaskInstance.java
@@ -19,6 +19,7 @@ import io.mifos.customer.api.v1.CustomerEventConstants;
 import io.mifos.customer.api.v1.client.TaskExecutionException;
 import io.mifos.customer.api.v1.domain.Command;
 import io.mifos.customer.api.v1.domain.Customer;
+import io.mifos.customer.api.v1.domain.IdentificationCard;
 import io.mifos.customer.api.v1.domain.TaskDefinition;
 import io.mifos.customer.util.CustomerGenerator;
 import io.mifos.customer.util.IdentificationCardGenerator;
@@ -72,8 +73,9 @@ public class TestTaskInstance extends AbstractCustomerTest {
     }
 
     // set the ID card for the customer
-    this.customerManager.createIdentificationCard(randomCustomer.getIdentifier(), IdentificationCardGenerator.createRandomIdentificationCard());
-    this.eventRecorder.wait(CustomerEventConstants.POST_IDENTIFICATION_CARD, randomCustomer.getIdentifier());
+    final IdentificationCard randomIdentificationCard = IdentificationCardGenerator.createRandomIdentificationCard();
+    this.customerManager.createIdentificationCard(randomCustomer.getIdentifier(), randomIdentificationCard);
+    this.eventRecorder.wait(CustomerEventConstants.POST_IDENTIFICATION_CARD, randomIdentificationCard.getNumber());
 
     // close the task
     this.customerManager.taskForCustomerExecuted(randomCustomer.getIdentifier(), taskDefinition.getIdentifier());
@@ -120,4 +122,71 @@ public class TestTaskInstance extends AbstractCustomerTest {
     Assert.assertEquals(1, tasksForCustomer.size());
   }
 
+  @Test
+  public void shouldUnlockCustomerMultipleTasks() throws Exception{
+    final TaskDefinition customTask1 = new TaskDefinition();
+    customTask1.setIdentifier("custom-task-1");
+    customTask1.setType(TaskDefinition.Type.CUSTOM.name());
+    customTask1.setName("Do the barrel roll");
+    customTask1.setCommands(
+        TaskDefinition.Command.ACTIVATE.name(),
+        TaskDefinition.Command.UNLOCK.name()
+    );
+    customTask1.setPredefined(Boolean.TRUE);
+    customTask1.setMandatory(Boolean.TRUE);
+
+    this.customerManager.createTask(customTask1);
+    this.eventRecorder.wait(CustomerEventConstants.POST_TASK, customTask1.getIdentifier());
+
+    final TaskDefinition customTask2 = new TaskDefinition();
+    customTask2.setIdentifier("custom-task-2");
+    customTask2.setType(TaskDefinition.Type.CUSTOM.name());
+    customTask2.setName("Do the barrel roll");
+    customTask2.setCommands(
+        TaskDefinition.Command.ACTIVATE.name(),
+        TaskDefinition.Command.UNLOCK.name()
+    );
+    customTask2.setPredefined(Boolean.TRUE);
+    customTask2.setMandatory(Boolean.TRUE);
+
+    this.customerManager.createTask(customTask2);
+    this.eventRecorder.wait(CustomerEventConstants.POST_TASK, customTask2.getIdentifier());
+
+    // create a random customer
+    final Customer randomCustomer = CustomerGenerator.createRandomCustomer();
+    this.customerManager.createCustomer(randomCustomer);
+    this.eventRecorder.wait(CustomerEventConstants.POST_CUSTOMER, randomCustomer.getIdentifier());
+
+    // close the task
+    this.customerManager.taskForCustomerExecuted(randomCustomer.getIdentifier(), customTask1.getIdentifier());
+    this.eventRecorder.wait(CustomerEventConstants.PUT_CUSTOMER, randomCustomer.getIdentifier());
+
+    this.customerManager.taskForCustomerExecuted(randomCustomer.getIdentifier(), customTask2.getIdentifier());
+    this.eventRecorder.wait(CustomerEventConstants.PUT_CUSTOMER, randomCustomer.getIdentifier());
+
+    final Command activateCustomer = new Command();
+    activateCustomer.setAction(Command.Action.ACTIVATE.name());
+    this.customerManager.customerCommand(randomCustomer.getIdentifier(), activateCustomer);
+    Assert.assertTrue(this.eventRecorder.wait(CustomerEventConstants.ACTIVATE_CUSTOMER, randomCustomer.getIdentifier()));
+
+    final Command lockCustomer = new Command();
+    lockCustomer.setAction(Command.Action.LOCK.name());
+    this.customerManager.customerCommand(randomCustomer.getIdentifier(), lockCustomer);
+    Assert.assertTrue(this.eventRecorder.wait(CustomerEventConstants.LOCK_CUSTOMER, randomCustomer.getIdentifier()));
+
+    // close the task
+    this.customerManager.taskForCustomerExecuted(randomCustomer.getIdentifier(), customTask1.getIdentifier());
+    this.eventRecorder.wait(CustomerEventConstants.PUT_CUSTOMER, randomCustomer.getIdentifier());
+
+    this.customerManager.taskForCustomerExecuted(randomCustomer.getIdentifier(), customTask2.getIdentifier());
+    this.eventRecorder.wait(CustomerEventConstants.PUT_CUSTOMER, randomCustomer.getIdentifier());
+
+    final Command unlockCustomer = new Command();
+    unlockCustomer.setAction(Command.Action.UNLOCK.name());
+    this.customerManager.customerCommand(randomCustomer.getIdentifier(), unlockCustomer);
+    Assert.assertTrue(this.eventRecorder.wait(CustomerEventConstants.UNLOCK_CUSTOMER, randomCustomer.getIdentifier()));
+
+    final Customer customer = this.customerManager.findCustomer(randomCustomer.getIdentifier());
+    Assert.assertEquals(Customer.State.ACTIVE.name(), customer.getCurrentState());
+  }
 }
diff --git a/service/src/main/java/io/mifos/customer/service/internal/command/handler/TaskAggregate.java b/service/src/main/java/io/mifos/customer/service/internal/command/handler/TaskAggregate.java
index 65a03d5..5e1bac6 100644
--- a/service/src/main/java/io/mifos/customer/service/internal/command/handler/TaskAggregate.java
+++ b/service/src/main/java/io/mifos/customer/service/internal/command/handler/TaskAggregate.java
@@ -114,8 +114,9 @@ public class TaskAggregate {
       final Optional<TaskInstanceEntity> taskInstanceEntityOptional = taskInstanceEntities
           .stream()
           .filter(
-              taskInstanceEntity -> taskInstanceEntity.getTaskDefinition().getIdentifier()
-                  .equals(executeTaskForCustomerCommand.taskIdentifier()))
+              taskInstanceEntity -> taskInstanceEntity.getTaskDefinition().getIdentifier().equals(executeTaskForCustomerCommand.taskIdentifier())
+                  && taskInstanceEntity.getExecutedBy() == null
+          )
           .findAny();
 
       if (taskInstanceEntityOptional.isPresent()) {
diff --git a/service/src/main/java/io/mifos/customer/service/rest/controller/CustomerRestController.java b/service/src/main/java/io/mifos/customer/service/rest/controller/CustomerRestController.java
index 401f1d9..d7ad048 100644
--- a/service/src/main/java/io/mifos/customer/service/rest/controller/CustomerRestController.java
+++ b/service/src/main/java/io/mifos/customer/service/rest/controller/CustomerRestController.java
@@ -734,7 +734,7 @@ public class CustomerRestController {
 
   @Permittable(value = AcceptedTokenType.TENANT, groupId = PermittableGroupIds.CUSTOMER)
   @RequestMapping(
-      value = "/customer/{identifier}/actions",
+      value = "/customers/{identifier}/actions",
       method = RequestMethod.GET,
       produces = MediaType.APPLICATION_JSON_VALUE,
       consumes = MediaType.ALL_VALUE
@@ -748,7 +748,7 @@ public class CustomerRestController {
 
   @Permittable(value = AcceptedTokenType.TENANT, groupId = PermittableGroupIds.CUSTOMER)
   @RequestMapping(
-      value = "customer/{identifier}/payroll",
+      value = "/customers/{identifier}/payroll",
       method = RequestMethod.PUT,
       produces = MediaType.APPLICATION_JSON_VALUE,
       consumes = MediaType.APPLICATION_JSON_VALUE
@@ -764,7 +764,7 @@ public class CustomerRestController {
 
   @Permittable(value = AcceptedTokenType.TENANT, groupId = PermittableGroupIds.CUSTOMER)
   @RequestMapping(
-      value = "customer/{identifier}/payroll",
+      value = "/customers/{identifier}/payroll",
       method = RequestMethod.GET,
       produces = MediaType.APPLICATION_JSON_VALUE,
       consumes = MediaType.ALL_VALUE

-- 
To stop receiving notification emails like this one, please contact
myrle@apache.org.

[fineract-cn-customer] 24/46: Merge pull request #9 from markusgeiss/develop

Posted by my...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

myrle pushed a commit to branch develop
in repository https://gitbox.apache.org/repos/asf/fineract-cn-customer.git

commit 9f5e721b3d4f93b4ef5047395142ec1cd2cb8bdd
Merge: c717b50 38ab00d
Author: Markus Geiss <ma...@kuelap.io>
AuthorDate: Tue Sep 19 10:44:04 2017 +0200

    Merge pull request #9 from markusgeiss/develop
    
    added date range validation to expiration date

 .../main/java/io/mifos/customer/api/v1/domain/ExpirationDate.java | 8 ++++++++
 1 file changed, 8 insertions(+)

-- 
To stop receiving notification emails like this one, please contact
myrle@apache.org.

[fineract-cn-customer] 32/46: Merge pull request #13 from markvanveen/develop

Posted by my...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

myrle pushed a commit to branch develop
in repository https://gitbox.apache.org/repos/asf/fineract-cn-customer.git

commit 5abd7727e1630de78a7ba8158813f6d1e6050863
Merge: b560180 a280d78
Author: Mark van Veen <30...@users.noreply.github.com>
AuthorDate: Fri Sep 29 17:58:10 2017 +0200

    Merge pull request #13 from markvanveen/develop
    
    Fix issue not updating fields

 .../api/v1/client/CatalogAlreadyInUseException.java   | 19 +++++++++++++++++++
 .../catalog/api/v1/client/CatalogManager.java         |  9 ++++++---
 .../api/v1/client/FieldAlreadyInUseException.java     | 19 +++++++++++++++++++
 .../java/io/mifos/customer/catalog/TestCatalog.java   |  6 ++++--
 .../rest/controller/CatalogRestController.java        |  6 +++---
 .../internal/command/handler/CustomerAggregate.java   |  1 +
 6 files changed, 52 insertions(+), 8 deletions(-)

-- 
To stop receiving notification emails like this one, please contact
myrle@apache.org.

[fineract-cn-customer] 08/46: Merge pull request #1 from myrlen/develop

Posted by my...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

myrle pushed a commit to branch develop
in repository https://gitbox.apache.org/repos/asf/fineract-cn-customer.git

commit fd4bf02286ef8f4767dab47d15205d02ea1488e2
Merge: 65d25fe 6c963dd
Author: myrlen <my...@kuelap.io>
AuthorDate: Mon Aug 14 12:57:55 2017 +0200

    Merge pull request #1 from myrlen/develop
    
    Added helper function to feign client isCustomerInGoodStanding

 .../customer/api/v1/client/CustomerManager.java    | 12 +++++++++
 .../main/java/io/mifos/customer/TestCustomer.java  | 30 ++++++++++++++++++++++
 2 files changed, 42 insertions(+)

-- 
To stop receiving notification emails like this one, please contact
myrle@apache.org.

[fineract-cn-customer] 39/46: Added delete document endpoint.

Posted by my...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

myrle pushed a commit to branch develop
in repository https://gitbox.apache.org/repos/asf/fineract-cn-customer.git

commit 41749c5927fd9066710ad2757184b46d54b34cdc
Author: Myrle Krantz <my...@apache.org>
AuthorDate: Fri Oct 20 12:01:07 2017 +0200

    Added delete document endpoint.
---
 .../customer/api/v1/CustomerEventConstants.java    |  2 +
 .../api/v1/client/CustomerDocumentsManager.java    | 14 +++++++
 .../main/java/io/mifos/customer/TestDocuments.java | 38 +++++++++++++++++-
 .../customer/listener/DocumentEventListener.java   | 10 +++++
 .../internal/command/DeleteDocumentCommand.java    | 45 ++++++++++++++++++++++
 .../command/handler/DocumentCommandHandler.java    | 16 ++++++++
 .../rest/controller/DocumentsRestController.java   | 22 +++++++++++
 7 files changed, 146 insertions(+), 1 deletion(-)

diff --git a/api/src/main/java/io/mifos/customer/api/v1/CustomerEventConstants.java b/api/src/main/java/io/mifos/customer/api/v1/CustomerEventConstants.java
index 7a47b73..9608f83 100644
--- a/api/src/main/java/io/mifos/customer/api/v1/CustomerEventConstants.java
+++ b/api/src/main/java/io/mifos/customer/api/v1/CustomerEventConstants.java
@@ -49,6 +49,7 @@ public interface CustomerEventConstants {
 
   String POST_DOCUMENT = "post-document";
   String PUT_DOCUMENT = "put-document";
+  String DELETE_DOCUMENT = "delete-document";
   String POST_DOCUMENT_PAGE = "post-document-page";
   String DELETE_DOCUMENT_PAGE = "delete-document-page";
   String POST_DOCUMENT_COMPLETE = "post-document-complete";
@@ -81,6 +82,7 @@ public interface CustomerEventConstants {
 
   String SELECTOR_POST_DOCUMENT = SELECTOR_NAME + " = '" + POST_DOCUMENT + "'";
   String SELECTOR_PUT_DOCUMENT = SELECTOR_NAME + " = '" + PUT_DOCUMENT + "'";
+  String SELECTOR_DELETE_DOCUMENT = SELECTOR_NAME + " = '" + DELETE_DOCUMENT + "'";
   String SELECTOR_POST_DOCUMENT_PAGE = SELECTOR_NAME + " = '" + POST_DOCUMENT_PAGE + "'";
   String SELECTOR_DELETE_DOCUMENT_PAGE = SELECTOR_NAME + " = '" + DELETE_DOCUMENT_PAGE + "'";
   String SELECTOR_POST_DOCUMENT_COMPLETE = SELECTOR_NAME + " = '" + POST_DOCUMENT_COMPLETE + "'";
diff --git a/api/src/main/java/io/mifos/customer/api/v1/client/CustomerDocumentsManager.java b/api/src/main/java/io/mifos/customer/api/v1/client/CustomerDocumentsManager.java
index 56ba88a..16575d8 100644
--- a/api/src/main/java/io/mifos/customer/api/v1/client/CustomerDocumentsManager.java
+++ b/api/src/main/java/io/mifos/customer/api/v1/client/CustomerDocumentsManager.java
@@ -87,6 +87,20 @@ public interface CustomerDocumentsManager {
       @RequestBody final CustomerDocument customerDocument);
 
 
+  @RequestMapping(
+      value = "/customers/{customeridentifier}/documents/{documentidentifier}",
+      method = RequestMethod.DELETE,
+      produces = MediaType.ALL_VALUE,
+      consumes = MediaType.APPLICATION_JSON_VALUE
+  )
+  @ThrowsExceptions({
+      @ThrowsException(status = HttpStatus.CONFLICT, exception = CompletedDocumentCannotBeChangedException.class)
+  })
+  void deleteDocument(
+      @PathVariable("customeridentifier") final String customerIdentifier,
+      @PathVariable("documentidentifier") final String documentIdentifier);
+
+
   /**
    * Once a document is "completed" its name and images cannot be changed again.  Only completed
    * documents should be referenced by other services.
diff --git a/component-test/src/main/java/io/mifos/customer/TestDocuments.java b/component-test/src/main/java/io/mifos/customer/TestDocuments.java
index 8b8e6d1..3b14735 100644
--- a/component-test/src/main/java/io/mifos/customer/TestDocuments.java
+++ b/component-test/src/main/java/io/mifos/customer/TestDocuments.java
@@ -44,6 +44,37 @@ import java.util.stream.IntStream;
 public class TestDocuments extends AbstractCustomerTest {
 
   @Test
+  public void shouldUploadThenDeleteInCompleteDocument() throws InterruptedException, IOException {
+    logger.info("Prepare test");
+    final Customer customer = CustomerGenerator.createRandomCustomer();
+    customerManager.createCustomer(customer);
+    Assert.assertTrue(eventRecorder.wait(CustomerEventConstants.POST_CUSTOMER, customer.getIdentifier()));
+
+    final CustomerDocument customerDocument = CustomerDocumentGenerator.createRandomCustomerDocument();
+    customerDocumentsManager.createDocument(customer.getIdentifier(), customerDocument.getIdentifier(), customerDocument);
+    Assert.assertTrue(eventRecorder.wait(CustomerEventConstants.POST_DOCUMENT,
+        new DocumentEvent(customer.getIdentifier(), customerDocument.getIdentifier())));
+
+    for (int i = 0; i < 5; i++) {
+      createDocumentPage(customer.getIdentifier(), customerDocument.getIdentifier(), i);
+    }
+
+    logger.info("Delete document");
+    customerDocumentsManager.deleteDocument(customer.getIdentifier(), customerDocument.getIdentifier());
+    Assert.assertTrue(eventRecorder.wait(CustomerEventConstants.DELETE_DOCUMENT,
+        new DocumentEvent(customer.getIdentifier(), customerDocument.getIdentifier())));
+
+    try {
+      customerDocumentsManager.getDocument(customer.getIdentifier(), customerDocument.getIdentifier());
+      Assert.fail("Deleted document should not be findable");
+    }
+    catch (final NotFoundException ignored) {}
+
+    final List<CustomerDocument> customersDocuments = customerDocumentsManager.getDocuments(customer.getIdentifier());
+    Assert.assertFalse(customersDocuments.contains(customerDocument));
+  }
+
+  @Test
   public void shouldUploadEditThenCompleteDocument() throws InterruptedException, IOException {
     logger.info("Prepare test");
     final Customer customer = CustomerGenerator.createRandomCustomer();
@@ -128,7 +159,7 @@ public class TestDocuments extends AbstractCustomerTest {
     timeStampChecker.assertCorrect(completedDocument.getCreatedOn());
 
 
-    logger.info("Check that document can't be changed after completion");
+    logger.info("Check that document can't be changed or removed after completion");
     try {
       createDocumentPage(customer.getIdentifier(), customerDocument.getIdentifier(), 9);
       Assert.fail("Adding another page after the document is completed shouldn't be possible.");
@@ -144,6 +175,11 @@ public class TestDocuments extends AbstractCustomerTest {
       Assert.fail("Changing a document after it is completed shouldn't be possible.");
     }
     catch (final CompletedDocumentCannotBeChangedException ignored) {}
+    try {
+      customerDocumentsManager.deleteDocument(customer.getIdentifier(), customerDocument.getIdentifier());
+      Assert.fail("Changing a document after it is completed shouldn't be possible.");
+    }
+    catch (final CompletedDocumentCannotBeChangedException ignored) {}
 
 
     logger.info("Check that document can't be uncompleted");
diff --git a/component-test/src/main/java/io/mifos/customer/listener/DocumentEventListener.java b/component-test/src/main/java/io/mifos/customer/listener/DocumentEventListener.java
index 346df99..2293397 100644
--- a/component-test/src/main/java/io/mifos/customer/listener/DocumentEventListener.java
+++ b/component-test/src/main/java/io/mifos/customer/listener/DocumentEventListener.java
@@ -61,6 +61,16 @@ public class DocumentEventListener {
 
   @JmsListener(
       destination = CustomerEventConstants.DESTINATION,
+      selector = CustomerEventConstants.SELECTOR_DELETE_DOCUMENT
+  )
+  public void deleteDocumentEvent(
+      @Header(TenantHeaderFilter.TENANT_HEADER) final String tenant,
+      final String payload) {
+    this.eventRecorder.event(tenant, CustomerEventConstants.DELETE_DOCUMENT, payload, DocumentEvent.class);
+  }
+
+  @JmsListener(
+      destination = CustomerEventConstants.DESTINATION,
       selector = CustomerEventConstants.SELECTOR_POST_DOCUMENT_PAGE
   )
   public void postDocumentPageEvent(
diff --git a/service/src/main/java/io/mifos/customer/service/internal/command/DeleteDocumentCommand.java b/service/src/main/java/io/mifos/customer/service/internal/command/DeleteDocumentCommand.java
new file mode 100644
index 0000000..f1b17e6
--- /dev/null
+++ b/service/src/main/java/io/mifos/customer/service/internal/command/DeleteDocumentCommand.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2017 The Mifos Initiative.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.mifos.customer.service.internal.command;
+
+/**
+ * @author Myrle Krantz
+ */
+public class DeleteDocumentCommand {
+  private final String customerIdentifier;
+  private final String documentIdentifier;
+
+  public DeleteDocumentCommand(String customerIdentifier, String documentIdentifier) {
+    this.customerIdentifier = customerIdentifier;
+    this.documentIdentifier = documentIdentifier;
+  }
+
+  public String getCustomerIdentifier() {
+    return customerIdentifier;
+  }
+
+  public String getDocumentIdentifier() {
+    return documentIdentifier;
+  }
+
+  @Override
+  public String toString() {
+    return "DeleteDocumentCommand{" +
+        "customerIdentifier='" + customerIdentifier + '\'' +
+        ", documentIdentifier='" + documentIdentifier + '\'' +
+        '}';
+  }
+}
diff --git a/service/src/main/java/io/mifos/customer/service/internal/command/handler/DocumentCommandHandler.java b/service/src/main/java/io/mifos/customer/service/internal/command/handler/DocumentCommandHandler.java
index 5be492e..9c27aab 100644
--- a/service/src/main/java/io/mifos/customer/service/internal/command/handler/DocumentCommandHandler.java
+++ b/service/src/main/java/io/mifos/customer/service/internal/command/handler/DocumentCommandHandler.java
@@ -100,6 +100,22 @@ public class DocumentCommandHandler {
 
   @Transactional
   @CommandHandler
+  @EventEmitter(selectorName = CustomerEventConstants.SELECTOR_NAME, selectorValue = CustomerEventConstants.DELETE_DOCUMENT)
+  public DocumentEvent process(final DeleteDocumentCommand command) throws IOException {
+    final DocumentEntity existingDocument = documentRepository.findByCustomerIdAndDocumentIdentifier(
+        command.getCustomerIdentifier(), command.getDocumentIdentifier())
+        .orElseThrow(() ->
+            ServiceException.notFound("Document ''{0}'' for customer ''{1}'' not found",
+                command.getDocumentIdentifier(), command.getCustomerIdentifier()));
+    documentPageRepository.findByCustomerIdAndDocumentIdentifier(command.getCustomerIdentifier(), command.getDocumentIdentifier())
+        .forEach(documentPageRepository::delete);
+    documentRepository.delete(existingDocument);
+
+    return new DocumentEvent(command.getCustomerIdentifier(), command.getDocumentIdentifier());
+  }
+
+  @Transactional
+  @CommandHandler
   @EventEmitter(selectorName = CustomerEventConstants.SELECTOR_NAME, selectorValue = CustomerEventConstants.POST_DOCUMENT_COMPLETE)
   public DocumentEvent process(final CompleteDocumentCommand command) throws IOException {
     final DocumentEntity documentEntity = documentRepository.findByCustomerIdAndDocumentIdentifier(
diff --git a/service/src/main/java/io/mifos/customer/service/rest/controller/DocumentsRestController.java b/service/src/main/java/io/mifos/customer/service/rest/controller/DocumentsRestController.java
index c0db7c8..fcf07be 100644
--- a/service/src/main/java/io/mifos/customer/service/rest/controller/DocumentsRestController.java
+++ b/service/src/main/java/io/mifos/customer/service/rest/controller/DocumentsRestController.java
@@ -138,6 +138,28 @@ public class DocumentsRestController {
 
   @Permittable(value = AcceptedTokenType.TENANT, groupId = PermittableGroupIds.DOCUMENTS)
   @RequestMapping(
+      value = "/{documentidentifier}",
+      method = RequestMethod.DELETE,
+      produces = MediaType.APPLICATION_JSON_VALUE,
+      consumes = MediaType.ALL_VALUE
+  )
+  public @ResponseBody
+  ResponseEntity<Void> deleteDocument(
+      @PathVariable("customeridentifier") final String customerIdentifier,
+      @PathVariable("documentidentifier") final String documentIdentifier) {
+    throwIfCustomerNotExists(customerIdentifier);
+    throwIfCustomerDocumentNotExists(customerIdentifier, documentIdentifier);
+
+    throwIfDocumentCompleted(customerIdentifier, documentIdentifier);
+
+    commandGateway.process(new DeleteDocumentCommand(customerIdentifier, documentIdentifier));
+
+    return ResponseEntity.accepted().build();
+  }
+
+
+  @Permittable(value = AcceptedTokenType.TENANT, groupId = PermittableGroupIds.DOCUMENTS)
+  @RequestMapping(
       value = "/{documentidentifier}/completed",
       method = RequestMethod.POST,
       produces = MediaType.APPLICATION_JSON_VALUE,

-- 
To stop receiving notification emails like this one, please contact
myrle@apache.org.

[fineract-cn-customer] 37/46: Merge pull request #15 from myrlen/develop

Posted by my...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

myrle pushed a commit to branch develop
in repository https://gitbox.apache.org/repos/asf/fineract-cn-customer.git

commit 1456f73da22342a587ea2d511855a049162e491e
Merge: 2071809 683ba37
Author: myrlen <my...@kuelap.io>
AuthorDate: Fri Oct 20 11:41:58 2017 +0200

    Merge pull request #15 from myrlen/develop
    
    Added document description and impemented PUT endpoint.

 api/build.gradle                                   |  4 ++
 .../customer/api/v1/CustomerEventConstants.java    |  2 +
 .../api/v1/client/CustomerDocumentsManager.java    | 15 +++++
 .../customer/api/v1/domain/CustomerDocument.java   | 18 +++++-
 .../api/v1/domain/CustomerDocumentTest.java        | 70 ++++++++++++++++++++++
 .../io/mifos/customer/AbstractCustomerTest.java    | 18 ++++++
 .../main/java/io/mifos/customer/TestDocuments.java | 48 +++++++++------
 .../customer/listener/DocumentEventListener.java   | 10 ++++
 .../customer/util/CustomerDocumentGenerator.java   |  1 +
 .../internal/command/ChangeDocumentCommand.java    | 49 +++++++++++++++
 .../command/handler/DocumentCommandHandler.java    | 23 +++++--
 .../service/internal/mapper/DocumentMapper.java    |  2 +
 .../internal/repository/DocumentEntity.java        | 14 ++++-
 .../rest/controller/DocumentsRestController.java   | 29 +++++++--
 .../mariadb/V8__documents_description.sql          | 17 ++++++
 15 files changed, 291 insertions(+), 29 deletions(-)

-- 
To stop receiving notification emails like this one, please contact
myrle@apache.org.

[fineract-cn-customer] 36/46: Adding change document description functionality.

Posted by my...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

myrle pushed a commit to branch develop
in repository https://gitbox.apache.org/repos/asf/fineract-cn-customer.git

commit 683ba372d7da2948ffd0650dfd16d1db83d3fa0d
Author: Myrle Krantz <my...@apache.org>
AuthorDate: Fri Oct 20 11:40:50 2017 +0200

    Adding change document description functionality.
---
 .../customer/api/v1/CustomerEventConstants.java    |  2 +
 .../api/v1/client/CustomerDocumentsManager.java    | 15 +++++++
 .../main/java/io/mifos/customer/TestDocuments.java | 12 ++++++
 .../customer/listener/DocumentEventListener.java   | 10 +++++
 .../internal/command/ChangeDocumentCommand.java    | 49 ++++++++++++++++++++++
 .../command/handler/DocumentCommandHandler.java    | 23 ++++++++--
 .../rest/controller/DocumentsRestController.java   | 29 +++++++++++--
 7 files changed, 132 insertions(+), 8 deletions(-)

diff --git a/api/src/main/java/io/mifos/customer/api/v1/CustomerEventConstants.java b/api/src/main/java/io/mifos/customer/api/v1/CustomerEventConstants.java
index 03854ad..7a47b73 100644
--- a/api/src/main/java/io/mifos/customer/api/v1/CustomerEventConstants.java
+++ b/api/src/main/java/io/mifos/customer/api/v1/CustomerEventConstants.java
@@ -48,6 +48,7 @@ public interface CustomerEventConstants {
   String DELETE_PORTRAIT = "delete-portrait";
 
   String POST_DOCUMENT = "post-document";
+  String PUT_DOCUMENT = "put-document";
   String POST_DOCUMENT_PAGE = "post-document-page";
   String DELETE_DOCUMENT_PAGE = "delete-document-page";
   String POST_DOCUMENT_COMPLETE = "post-document-complete";
@@ -79,6 +80,7 @@ public interface CustomerEventConstants {
   String SELECTOR_DELETE_PORTRAIT = SELECTOR_NAME + " = '" + DELETE_PORTRAIT + "'";
 
   String SELECTOR_POST_DOCUMENT = SELECTOR_NAME + " = '" + POST_DOCUMENT + "'";
+  String SELECTOR_PUT_DOCUMENT = SELECTOR_NAME + " = '" + PUT_DOCUMENT + "'";
   String SELECTOR_POST_DOCUMENT_PAGE = SELECTOR_NAME + " = '" + POST_DOCUMENT_PAGE + "'";
   String SELECTOR_DELETE_DOCUMENT_PAGE = SELECTOR_NAME + " = '" + DELETE_DOCUMENT_PAGE + "'";
   String SELECTOR_POST_DOCUMENT_COMPLETE = SELECTOR_NAME + " = '" + POST_DOCUMENT_COMPLETE + "'";
diff --git a/api/src/main/java/io/mifos/customer/api/v1/client/CustomerDocumentsManager.java b/api/src/main/java/io/mifos/customer/api/v1/client/CustomerDocumentsManager.java
index 35b9be9..96fdf02 100644
--- a/api/src/main/java/io/mifos/customer/api/v1/client/CustomerDocumentsManager.java
+++ b/api/src/main/java/io/mifos/customer/api/v1/client/CustomerDocumentsManager.java
@@ -71,6 +71,21 @@ public interface CustomerDocumentsManager {
       @RequestBody final CustomerDocument customerDocument);
 
 
+  @RequestMapping(
+      value = "/customers/{customeridentifier}/documents/{documentidentifier}",
+      method = RequestMethod.PUT,
+      produces = MediaType.APPLICATION_JSON_VALUE,
+      consumes = MediaType.APPLICATION_JSON_VALUE
+  )
+  @ThrowsExceptions({
+      @ThrowsException(status = HttpStatus.BAD_REQUEST, exception = DocumentValidationException.class)
+  })
+  void changeDocument(
+      @PathVariable("customeridentifier") final String customerIdentifier,
+      @PathVariable("documentidentifier") final String documentIdentifier,
+      @RequestBody final CustomerDocument customerDocument);
+
+
   /**
    * Once a document is "completed" its name and images cannot be changed again.  Only completed
    * documents should be referenced by other services.
diff --git a/component-test/src/main/java/io/mifos/customer/TestDocuments.java b/component-test/src/main/java/io/mifos/customer/TestDocuments.java
index fa44409..071f247 100644
--- a/component-test/src/main/java/io/mifos/customer/TestDocuments.java
+++ b/component-test/src/main/java/io/mifos/customer/TestDocuments.java
@@ -104,6 +104,18 @@ public class TestDocuments extends AbstractCustomerTest {
     createDocumentPage(customer.getIdentifier(), customerDocument.getIdentifier(), 2);
 
 
+    logger.info("Check that a document's description can be changed.");
+    customerDocument.setDescription("new description");
+    customerDocumentsManager.changeDocument(customer.getIdentifier(), customerDocument.getIdentifier(), customerDocument);
+    Assert.assertTrue(eventRecorder.wait(CustomerEventConstants.PUT_DOCUMENT,
+        new DocumentEvent(customer.getIdentifier(), customerDocument.getIdentifier())));
+
+    {
+      final CustomerDocument changedCustomerDocument = customerDocumentsManager.getDocument(customer.getIdentifier(), customerDocument.getIdentifier());
+      Assert.assertEquals(customerDocument, changedCustomerDocument);
+    }
+
+
     logger.info("Check that a valid document can be completed");
     final TimeStampChecker timeStampChecker = TimeStampChecker.roughlyNow();
     customerDocumentsManager.completeDocument(customer.getIdentifier(), customerDocument.getIdentifier(), true);
diff --git a/component-test/src/main/java/io/mifos/customer/listener/DocumentEventListener.java b/component-test/src/main/java/io/mifos/customer/listener/DocumentEventListener.java
index 6875afd..346df99 100644
--- a/component-test/src/main/java/io/mifos/customer/listener/DocumentEventListener.java
+++ b/component-test/src/main/java/io/mifos/customer/listener/DocumentEventListener.java
@@ -51,6 +51,16 @@ public class DocumentEventListener {
 
   @JmsListener(
       destination = CustomerEventConstants.DESTINATION,
+      selector = CustomerEventConstants.SELECTOR_PUT_DOCUMENT
+  )
+  public void putDocumentEvent(
+      @Header(TenantHeaderFilter.TENANT_HEADER) final String tenant,
+      final String payload) {
+    this.eventRecorder.event(tenant, CustomerEventConstants.PUT_DOCUMENT, payload, DocumentEvent.class);
+  }
+
+  @JmsListener(
+      destination = CustomerEventConstants.DESTINATION,
       selector = CustomerEventConstants.SELECTOR_POST_DOCUMENT_PAGE
   )
   public void postDocumentPageEvent(
diff --git a/service/src/main/java/io/mifos/customer/service/internal/command/ChangeDocumentCommand.java b/service/src/main/java/io/mifos/customer/service/internal/command/ChangeDocumentCommand.java
new file mode 100644
index 0000000..6684818
--- /dev/null
+++ b/service/src/main/java/io/mifos/customer/service/internal/command/ChangeDocumentCommand.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2017 The Mifos Initiative.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.mifos.customer.service.internal.command;
+
+import io.mifos.customer.api.v1.domain.CustomerDocument;
+
+/**
+ * @author Myrle Krantz
+ */
+public class ChangeDocumentCommand {
+  private final String customerIdentifier;
+  private final CustomerDocument customerDocument;
+
+  public ChangeDocumentCommand(
+      final String customerIdentifier,
+      final CustomerDocument customerDocument) {
+    this.customerIdentifier = customerIdentifier;
+    this.customerDocument = customerDocument;
+  }
+
+  public String getCustomerIdentifier() {
+    return customerIdentifier;
+  }
+
+  public CustomerDocument getCustomerDocument() {
+    return customerDocument;
+  }
+
+  @Override
+  public String toString() {
+    return "ChangeDocumentCommand{" +
+        "customerIdentifier='" + customerIdentifier + '\'' +
+        ", customerDocument=" + customerDocument.getIdentifier() +
+        '}';
+  }
+}
diff --git a/service/src/main/java/io/mifos/customer/service/internal/command/handler/DocumentCommandHandler.java b/service/src/main/java/io/mifos/customer/service/internal/command/handler/DocumentCommandHandler.java
index 2ee4498..5be492e 100644
--- a/service/src/main/java/io/mifos/customer/service/internal/command/handler/DocumentCommandHandler.java
+++ b/service/src/main/java/io/mifos/customer/service/internal/command/handler/DocumentCommandHandler.java
@@ -23,10 +23,7 @@ import io.mifos.core.lang.ServiceException;
 import io.mifos.customer.api.v1.CustomerEventConstants;
 import io.mifos.customer.api.v1.events.DocumentEvent;
 import io.mifos.customer.api.v1.events.DocumentPageEvent;
-import io.mifos.customer.service.internal.command.CompleteDocumentCommand;
-import io.mifos.customer.service.internal.command.CreateDocumentCommand;
-import io.mifos.customer.service.internal.command.CreateDocumentPageCommand;
-import io.mifos.customer.service.internal.command.DeleteDocumentPageCommand;
+import io.mifos.customer.service.internal.command.*;
 import io.mifos.customer.service.internal.mapper.DocumentMapper;
 import io.mifos.customer.service.internal.repository.*;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -85,6 +82,24 @@ public class DocumentCommandHandler {
 
   @Transactional
   @CommandHandler
+  @EventEmitter(selectorName = CustomerEventConstants.SELECTOR_NAME, selectorValue = CustomerEventConstants.PUT_DOCUMENT)
+  public DocumentEvent process(final ChangeDocumentCommand command) throws IOException {
+    final DocumentEntity existingDocument = documentRepository.findByCustomerIdAndDocumentIdentifier(
+        command.getCustomerIdentifier(), command.getCustomerDocument().getIdentifier())
+        .orElseThrow(() ->
+            ServiceException.notFound("Document ''{0}'' for customer ''{1}'' not found",
+                command.getCustomerDocument().getIdentifier(), command.getCustomerIdentifier()));
+
+    final CustomerEntity customerEntity = customerRepository.findByIdentifier(command.getCustomerIdentifier());
+    final DocumentEntity documentEntity = DocumentMapper.map(command.getCustomerDocument(), customerEntity);
+    documentEntity.setId(existingDocument.getId());
+    documentRepository.save(documentEntity);
+
+    return new DocumentEvent(command.getCustomerIdentifier(), command.getCustomerDocument().getIdentifier());
+  }
+
+  @Transactional
+  @CommandHandler
   @EventEmitter(selectorName = CustomerEventConstants.SELECTOR_NAME, selectorValue = CustomerEventConstants.POST_DOCUMENT_COMPLETE)
   public DocumentEvent process(final CompleteDocumentCommand command) throws IOException {
     final DocumentEntity documentEntity = documentRepository.findByCustomerIdAndDocumentIdentifier(
diff --git a/service/src/main/java/io/mifos/customer/service/rest/controller/DocumentsRestController.java b/service/src/main/java/io/mifos/customer/service/rest/controller/DocumentsRestController.java
index 8cd97d8..79c6aae 100644
--- a/service/src/main/java/io/mifos/customer/service/rest/controller/DocumentsRestController.java
+++ b/service/src/main/java/io/mifos/customer/service/rest/controller/DocumentsRestController.java
@@ -21,10 +21,7 @@ import io.mifos.core.command.gateway.CommandGateway;
 import io.mifos.core.lang.ServiceException;
 import io.mifos.customer.PermittableGroupIds;
 import io.mifos.customer.api.v1.domain.CustomerDocument;
-import io.mifos.customer.service.internal.command.CompleteDocumentCommand;
-import io.mifos.customer.service.internal.command.CreateDocumentCommand;
-import io.mifos.customer.service.internal.command.CreateDocumentPageCommand;
-import io.mifos.customer.service.internal.command.DeleteDocumentPageCommand;
+import io.mifos.customer.service.internal.command.*;
 import io.mifos.customer.service.internal.repository.DocumentPageEntity;
 import io.mifos.customer.service.internal.service.CustomerService;
 import io.mifos.customer.service.internal.service.DocumentService;
@@ -115,6 +112,30 @@ public class DocumentsRestController {
 
   @Permittable(value = AcceptedTokenType.TENANT, groupId = PermittableGroupIds.DOCUMENTS)
   @RequestMapping(
+      value = "/{documentidentifier}",
+      method = RequestMethod.PUT,
+      produces = MediaType.APPLICATION_JSON_VALUE,
+      consumes = MediaType.APPLICATION_JSON_VALUE
+  )
+  public @ResponseBody
+  ResponseEntity<Void> changeDocument(
+      @PathVariable("customeridentifier") final String customerIdentifier,
+      @PathVariable("documentidentifier") final String documentIdentifier,
+      @RequestBody final @Valid CustomerDocument instance) {
+    throwIfCustomerNotExists(customerIdentifier);
+    throwIfCustomerDocumentNotExists(customerIdentifier, documentIdentifier);
+
+    if (!instance.getIdentifier().equals(documentIdentifier))
+      throw ServiceException.badRequest("Document identifier in request body must match document identifier in request path.");
+
+    commandGateway.process(new ChangeDocumentCommand(customerIdentifier, instance));
+
+    return ResponseEntity.accepted().build();
+  }
+
+
+  @Permittable(value = AcceptedTokenType.TENANT, groupId = PermittableGroupIds.DOCUMENTS)
+  @RequestMapping(
       value = "/{documentidentifier}/completed",
       method = RequestMethod.POST,
       produces = MediaType.APPLICATION_JSON_VALUE,

-- 
To stop receiving notification emails like this one, please contact
myrle@apache.org.

[fineract-cn-customer] 26/46: Merge pull request #10 from markusgeiss/develop

Posted by my...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

myrle pushed a commit to branch develop
in repository https://gitbox.apache.org/repos/asf/fineract-cn-customer.git

commit 5184b15bf1f636fa81b643c1852e2995c33b8221
Merge: 9f5e721 48bfd66
Author: Markus Geiss <ma...@kuelap.io>
AuthorDate: Thu Sep 21 13:12:36 2017 +0200

    Merge pull request #10 from markusgeiss/develop
    
    fixed NPE when checking four eye task

 .../java/io/mifos/customer/TestTaskInstance.java   | 33 +++++++++++++++++++++-
 .../rest/controller/CustomerRestController.java    | 13 +++++----
 2 files changed, 39 insertions(+), 7 deletions(-)

-- 
To stop receiving notification emails like this one, please contact
myrle@apache.org.

[fineract-cn-customer] 41/46: TestCustomer.shouldDeletePortrait was failing intermittently with an InternalServerError resulting from a NullPointerException in the GET endpoint implementation. The delete transaction was getting committed between the query if the portrait exits and the actually acquisition of the portrait in the rest implementation. I changed the repository to return an Optional.empty instead of a null if the identifier doesn't correspond to a customer. This enabled me to replace two calls with one a [...]

Posted by my...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

myrle pushed a commit to branch develop
in repository https://gitbox.apache.org/repos/asf/fineract-cn-customer.git

commit 5e05d4a57244184ef3b92757dc79b975cc489dad
Author: Myrle Krantz <my...@apache.org>
AuthorDate: Fri Oct 20 12:45:17 2017 +0200

    TestCustomer.shouldDeletePortrait was failing intermittently with an
    InternalServerError resulting from a NullPointerException in the GET
    endpoint implementation.  The delete transaction was getting committed
    between the query if the portrait exits and the actually acquisition of
    the portrait in the rest implementation.  I changed the repository to
    return an Optional.empty instead of a null if the identifier doesn't
    correspond to a customer.  This enabled me to replace two calls with one
    and eliminate the potential for a race condition.  This in turn probably
    eliminated several other potential NullPointerExceptions.
---
 .../main/java/io/mifos/customer/TestCustomer.java  |   6 +-
 .../command/handler/CustomerAggregate.java         |  87 +++------
 .../command/handler/DocumentCommandHandler.java    |  18 +-
 .../internal/command/handler/TaskAggregate.java    |  12 +-
 .../internal/repository/CommandRepository.java     |   4 +-
 .../internal/repository/CustomerRepository.java    |   4 +-
 .../repository/IdentificationCardRepository.java   |   4 +-
 .../service/internal/service/CustomerService.java  | 213 ++++++++-------------
 .../service/internal/service/TaskService.java      |   7 +-
 .../rest/controller/CustomerRestController.java    |  21 +-
 10 files changed, 148 insertions(+), 228 deletions(-)

diff --git a/component-test/src/main/java/io/mifos/customer/TestCustomer.java b/component-test/src/main/java/io/mifos/customer/TestCustomer.java
index d11d5a8..5ce3f66 100644
--- a/component-test/src/main/java/io/mifos/customer/TestCustomer.java
+++ b/component-test/src/main/java/io/mifos/customer/TestCustomer.java
@@ -411,17 +411,17 @@ public class TestCustomer extends AbstractCustomerTest {
 
     this.customerManager.createCustomer(customer);
 
-    this.eventRecorder.wait(CustomerEventConstants.POST_CUSTOMER, customer.getIdentifier());
+    Assert.assertTrue(this.eventRecorder.wait(CustomerEventConstants.POST_CUSTOMER, customer.getIdentifier()));
 
     final MockMultipartFile firstFile = new MockMultipartFile("portrait", "test.png", MediaType.IMAGE_PNG_VALUE, "i don't care".getBytes());
 
     this.customerManager.postPortrait(customer.getIdentifier(), firstFile);
 
-    this.eventRecorder.wait(CustomerEventConstants.POST_PORTRAIT, customer.getIdentifier());
+    Assert.assertTrue(this.eventRecorder.wait(CustomerEventConstants.POST_PORTRAIT, customer.getIdentifier()));
 
     this.customerManager.deletePortrait(customer.getIdentifier());
 
-    this.eventRecorder.wait(CustomerEventConstants.DELETE_PORTRAIT, customer.getIdentifier());
+    Assert.assertTrue(this.eventRecorder.wait(CustomerEventConstants.DELETE_PORTRAIT, customer.getIdentifier()));
 
     this.customerManager.getPortrait(customer.getIdentifier());
   }
diff --git a/service/src/main/java/io/mifos/customer/service/internal/command/handler/CustomerAggregate.java b/service/src/main/java/io/mifos/customer/service/internal/command/handler/CustomerAggregate.java
index fa306a5..07e5ebc 100644
--- a/service/src/main/java/io/mifos/customer/service/internal/command/handler/CustomerAggregate.java
+++ b/service/src/main/java/io/mifos/customer/service/internal/command/handler/CustomerAggregate.java
@@ -25,53 +25,11 @@ import io.mifos.customer.api.v1.CustomerEventConstants;
 import io.mifos.customer.api.v1.domain.Command;
 import io.mifos.customer.api.v1.domain.Customer;
 import io.mifos.customer.api.v1.events.ScanEvent;
-import io.mifos.customer.catalog.service.internal.repository.CatalogEntity;
-import io.mifos.customer.catalog.service.internal.repository.CatalogRepository;
-import io.mifos.customer.catalog.service.internal.repository.FieldEntity;
-import io.mifos.customer.catalog.service.internal.repository.FieldRepository;
-import io.mifos.customer.catalog.service.internal.repository.FieldValueEntity;
-import io.mifos.customer.catalog.service.internal.repository.FieldValueRepository;
-import io.mifos.customer.service.ServiceConstants;
-import io.mifos.customer.service.internal.command.ActivateCustomerCommand;
-import io.mifos.customer.service.internal.command.CloseCustomerCommand;
-import io.mifos.customer.service.internal.command.CreateCustomerCommand;
-import io.mifos.customer.service.internal.command.CreateIdentificationCardCommand;
-import io.mifos.customer.service.internal.command.CreateIdentificationCardScanCommand;
-import io.mifos.customer.service.internal.command.CreatePortraitCommand;
-import io.mifos.customer.service.internal.command.DeleteIdentificationCardCommand;
-import io.mifos.customer.service.internal.command.DeleteIdentificationCardScanCommand;
-import io.mifos.customer.service.internal.command.DeletePortraitCommand;
-import io.mifos.customer.service.internal.command.LockCustomerCommand;
-import io.mifos.customer.service.internal.command.ReopenCustomerCommand;
-import io.mifos.customer.service.internal.command.UnlockCustomerCommand;
-import io.mifos.customer.service.internal.command.UpdateAddressCommand;
-import io.mifos.customer.service.internal.command.UpdateContactDetailsCommand;
-import io.mifos.customer.service.internal.command.UpdateCustomerCommand;
-import io.mifos.customer.service.internal.command.UpdateIdentificationCardCommand;
-import io.mifos.customer.service.internal.mapper.AddressMapper;
-import io.mifos.customer.service.internal.mapper.CommandMapper;
-import io.mifos.customer.service.internal.mapper.ContactDetailMapper;
-import io.mifos.customer.service.internal.mapper.CustomerMapper;
-import io.mifos.customer.service.internal.mapper.FieldValueMapper;
-import io.mifos.customer.service.internal.mapper.IdentificationCardMapper;
-import io.mifos.customer.service.internal.mapper.IdentificationCardScanMapper;
-import io.mifos.customer.service.internal.mapper.PortraitMapper;
-import io.mifos.customer.service.internal.repository.AddressEntity;
-import io.mifos.customer.service.internal.repository.AddressRepository;
-import io.mifos.customer.service.internal.repository.CommandRepository;
-import io.mifos.customer.service.internal.repository.ContactDetailEntity;
-import io.mifos.customer.service.internal.repository.ContactDetailRepository;
-import io.mifos.customer.service.internal.repository.CustomerEntity;
-import io.mifos.customer.service.internal.repository.CustomerRepository;
-import io.mifos.customer.service.internal.repository.IdentificationCardEntity;
-import io.mifos.customer.service.internal.repository.IdentificationCardRepository;
-import io.mifos.customer.service.internal.repository.IdentificationCardScanEntity;
-import io.mifos.customer.service.internal.repository.IdentificationCardScanRepository;
-import io.mifos.customer.service.internal.repository.PortraitEntity;
-import io.mifos.customer.service.internal.repository.PortraitRepository;
-import org.slf4j.Logger;
+import io.mifos.customer.catalog.service.internal.repository.*;
+import io.mifos.customer.service.internal.command.*;
+import io.mifos.customer.service.internal.mapper.*;
+import io.mifos.customer.service.internal.repository.*;
 import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.beans.factory.annotation.Qualifier;
 import org.springframework.transaction.annotation.Transactional;
 import org.springframework.web.multipart.MultipartFile;
 
@@ -84,11 +42,9 @@ import java.util.List;
 import java.util.Optional;
 import java.util.stream.Collectors;
 
-@SuppressWarnings("unused")
+@SuppressWarnings({"unused", "UnusedReturnValue"})
 @Aggregate
 public class CustomerAggregate {
-
-  private final Logger logger;
   private final AddressRepository addressRepository;
   private final CustomerRepository customerRepository;
   private final IdentificationCardRepository identificationCardRepository;
@@ -102,8 +58,7 @@ public class CustomerAggregate {
   private final TaskAggregate taskAggregate;
 
   @Autowired
-  public CustomerAggregate(@Qualifier(ServiceConstants.LOGGER_NAME) final Logger logger,
-                           final AddressRepository addressRepository,
+  public CustomerAggregate(final AddressRepository addressRepository,
                            final CustomerRepository customerRepository,
                            final IdentificationCardRepository identificationCardRepository,
                            final IdentificationCardScanRepository identificationCardScanRepository,
@@ -115,7 +70,6 @@ public class CustomerAggregate {
                            final CommandRepository commandRepository,
                            final TaskAggregate taskAggregate) {
     super();
-    this.logger = logger;
     this.addressRepository = addressRepository;
     this.customerRepository = customerRepository;
     this.identificationCardRepository = identificationCardRepository;
@@ -170,7 +124,7 @@ public class CustomerAggregate {
   public String updateCustomer(final UpdateCustomerCommand updateCustomerCommand) {
     final Customer customer = updateCustomerCommand.customer();
 
-    final CustomerEntity customerEntity = this.customerRepository.findByIdentifier(customer.getIdentifier());
+    final CustomerEntity customerEntity = findCustomerEntityOrThrow(customer.getIdentifier());
 
     customerEntity.setGivenName(customer.getGivenName());
     customerEntity.setMiddleName(customer.getMiddleName());
@@ -218,7 +172,7 @@ public class CustomerAggregate {
   @CommandHandler
   @EventEmitter(selectorName = CustomerEventConstants.SELECTOR_NAME, selectorValue = CustomerEventConstants.ACTIVATE_CUSTOMER)
   public String activateCustomer(final ActivateCustomerCommand activateCustomerCommand) {
-    final CustomerEntity customerEntity = this.customerRepository.findByIdentifier(activateCustomerCommand.identifier());
+    final CustomerEntity customerEntity = findCustomerEntityOrThrow(activateCustomerCommand.identifier());
 
     if (this.taskAggregate.openTasksForCustomerExist(customerEntity, Command.Action.ACTIVATE.name())) {
       throw ServiceException.conflict("Open Tasks for customer {0} exists.", activateCustomerCommand.identifier());
@@ -241,7 +195,8 @@ public class CustomerAggregate {
   @CommandHandler
   @EventEmitter(selectorName = CustomerEventConstants.SELECTOR_NAME, selectorValue = CustomerEventConstants.LOCK_CUSTOMER)
   public String lockCustomer(final LockCustomerCommand lockCustomerCommand) {
-    final CustomerEntity customerEntity = this.customerRepository.findByIdentifier(lockCustomerCommand.identifier());
+    final CustomerEntity customerEntity = findCustomerEntityOrThrow(lockCustomerCommand.identifier());
+
     customerEntity.setCurrentState(Customer.State.LOCKED.name());
     customerEntity.setLastModifiedBy(UserContextHolder.checkedGetUser());
     customerEntity.setLastModifiedOn(LocalDateTime.now(Clock.systemUTC()));
@@ -261,7 +216,7 @@ public class CustomerAggregate {
   @CommandHandler
   @EventEmitter(selectorName = CustomerEventConstants.SELECTOR_NAME, selectorValue = CustomerEventConstants.UNLOCK_CUSTOMER)
   public String unlockCustomer(final UnlockCustomerCommand unlockCustomerCommand) {
-    final CustomerEntity customerEntity = this.customerRepository.findByIdentifier(unlockCustomerCommand.identifier());
+    final CustomerEntity customerEntity = findCustomerEntityOrThrow(unlockCustomerCommand.identifier());
 
     if (this.taskAggregate.openTasksForCustomerExist(customerEntity, Command.Action.UNLOCK.name())) {
       throw ServiceException.conflict("Open Tasks for customer {0} exists.", unlockCustomerCommand.identifier());
@@ -284,7 +239,8 @@ public class CustomerAggregate {
   @CommandHandler
   @EventEmitter(selectorName = CustomerEventConstants.SELECTOR_NAME, selectorValue = CustomerEventConstants.CLOSE_CUSTOMER)
   public String closeCustomer(final CloseCustomerCommand closeCustomerCommand) {
-    final CustomerEntity customerEntity = this.customerRepository.findByIdentifier(closeCustomerCommand.identifier());
+    final CustomerEntity customerEntity = findCustomerEntityOrThrow(closeCustomerCommand.identifier());
+
     customerEntity.setCurrentState(Customer.State.CLOSED.name());
     customerEntity.setLastModifiedBy(UserContextHolder.checkedGetUser());
     customerEntity.setLastModifiedOn(LocalDateTime.now(Clock.systemUTC()));
@@ -304,7 +260,7 @@ public class CustomerAggregate {
   @CommandHandler
   @EventEmitter(selectorName = CustomerEventConstants.SELECTOR_NAME, selectorValue = CustomerEventConstants.REOPEN_CUSTOMER)
   public String reopenCustomer(final ReopenCustomerCommand reopenCustomerCommand) {
-    final CustomerEntity customerEntity = this.customerRepository.findByIdentifier(reopenCustomerCommand.identifier());
+    final CustomerEntity customerEntity = findCustomerEntityOrThrow(reopenCustomerCommand.identifier());
 
     if (this.taskAggregate.openTasksForCustomerExist(customerEntity, Command.Action.REOPEN.name())) {
       throw ServiceException.conflict("Open Tasks for customer {0} exists.", reopenCustomerCommand.identifier());
@@ -327,7 +283,7 @@ public class CustomerAggregate {
   @CommandHandler
   @EventEmitter(selectorName = CustomerEventConstants.SELECTOR_NAME, selectorValue = CustomerEventConstants.PUT_ADDRESS)
   public String updateAddress(final UpdateAddressCommand updateAddressCommand) {
-    final CustomerEntity customerEntity = this.customerRepository.findByIdentifier(updateAddressCommand.identifier());
+    final CustomerEntity customerEntity = findCustomerEntityOrThrow(updateAddressCommand.identifier());
     customerEntity.setLastModifiedBy(UserContextHolder.checkedGetUser());
     customerEntity.setLastModifiedOn(LocalDateTime.now(Clock.systemUTC()));
 
@@ -347,7 +303,7 @@ public class CustomerAggregate {
   @CommandHandler
   @EventEmitter(selectorName = CustomerEventConstants.SELECTOR_NAME, selectorValue = CustomerEventConstants.PUT_CONTACT_DETAILS)
   public String updateContactDetails(final UpdateContactDetailsCommand updateContactDetailsCommand) {
-    final CustomerEntity customerEntity = this.customerRepository.findByIdentifier(updateContactDetailsCommand.identifier());
+    final CustomerEntity customerEntity = findCustomerEntityOrThrow(updateContactDetailsCommand.identifier());
     customerEntity.setLastModifiedBy(UserContextHolder.checkedGetUser());
     customerEntity.setLastModifiedOn(LocalDateTime.now(Clock.systemUTC()));
 
@@ -374,7 +330,7 @@ public class CustomerAggregate {
   @CommandHandler
   @EventEmitter(selectorName = CustomerEventConstants.SELECTOR_NAME, selectorValue = CustomerEventConstants.POST_IDENTIFICATION_CARD)
   public String createIdentificationCard(final CreateIdentificationCardCommand createIdentificationCardCommand) {
-    final CustomerEntity customerEntity = this.customerRepository.findByIdentifier(createIdentificationCardCommand.identifier());
+    final CustomerEntity customerEntity = findCustomerEntityOrThrow(createIdentificationCardCommand.identifier());
 
     final IdentificationCardEntity identificationCardEntity = IdentificationCardMapper.map(createIdentificationCardCommand.identificationCard());
 
@@ -507,7 +463,7 @@ public class CustomerAggregate {
       return null;
     }
 
-    final CustomerEntity customerEntity = this.customerRepository.findByIdentifier(createPortraitCommand.identifier());
+    final CustomerEntity customerEntity = findCustomerEntityOrThrow(createPortraitCommand.identifier());
 
     final PortraitEntity portraitEntity = PortraitMapper.map(createPortraitCommand.portrait());
     portraitEntity.setCustomer(customerEntity);
@@ -525,7 +481,7 @@ public class CustomerAggregate {
   @CommandHandler
   @EventEmitter(selectorName = CustomerEventConstants.SELECTOR_NAME, selectorValue = CustomerEventConstants.DELETE_PORTRAIT)
   public String deletePortrait(final DeletePortraitCommand deletePortraitCommand) throws IOException {
-    final CustomerEntity customerEntity = this.customerRepository.findByIdentifier(deletePortraitCommand.identifier());
+    final CustomerEntity customerEntity = findCustomerEntityOrThrow(deletePortraitCommand.identifier());
 
     this.portraitRepository.deleteByCustomer(customerEntity);
 
@@ -557,4 +513,9 @@ public class CustomerAggregate {
             .collect(Collectors.toList())
     );
   }
+
+  private CustomerEntity findCustomerEntityOrThrow(String identifier) {
+    return this.customerRepository.findByIdentifier(identifier)
+        .orElseThrow(() -> ServiceException.notFound("Customer ''{0}'' not found", identifier));
+  }
 }
diff --git a/service/src/main/java/io/mifos/customer/service/internal/command/handler/DocumentCommandHandler.java b/service/src/main/java/io/mifos/customer/service/internal/command/handler/DocumentCommandHandler.java
index 9c27aab..c91fcc0 100644
--- a/service/src/main/java/io/mifos/customer/service/internal/command/handler/DocumentCommandHandler.java
+++ b/service/src/main/java/io/mifos/customer/service/internal/command/handler/DocumentCommandHandler.java
@@ -71,11 +71,9 @@ public class DocumentCommandHandler {
   @CommandHandler
   @EventEmitter(selectorName = CustomerEventConstants.SELECTOR_NAME, selectorValue = CustomerEventConstants.POST_DOCUMENT)
   public DocumentEvent process(final CreateDocumentCommand command) throws IOException {
-
-
-    final CustomerEntity customerEntity = customerRepository.findByIdentifier(command.getCustomerIdentifier());
-    final DocumentEntity documentEntity = DocumentMapper.map(command.getCustomerDocument(), customerEntity);
-    documentRepository.save(documentEntity);
+    customerRepository.findByIdentifier(command.getCustomerIdentifier())
+        .map(customerEntity -> DocumentMapper.map(command.getCustomerDocument(), customerEntity))
+        .ifPresent(documentRepository::save);
 
     return new DocumentEvent(command.getCustomerIdentifier(), command.getCustomerDocument().getIdentifier());
   }
@@ -90,10 +88,12 @@ public class DocumentCommandHandler {
             ServiceException.notFound("Document ''{0}'' for customer ''{1}'' not found",
                 command.getCustomerDocument().getIdentifier(), command.getCustomerIdentifier()));
 
-    final CustomerEntity customerEntity = customerRepository.findByIdentifier(command.getCustomerIdentifier());
-    final DocumentEntity documentEntity = DocumentMapper.map(command.getCustomerDocument(), customerEntity);
-    documentEntity.setId(existingDocument.getId());
-    documentRepository.save(documentEntity);
+    customerRepository.findByIdentifier(command.getCustomerIdentifier())
+        .map(customerEntity -> DocumentMapper.map(command.getCustomerDocument(), customerEntity))
+        .ifPresent(documentEntity -> {
+          documentEntity.setId(existingDocument.getId());
+          documentRepository.save(documentEntity);
+        });
 
     return new DocumentEvent(command.getCustomerIdentifier(), command.getCustomerDocument().getIdentifier());
   }
diff --git a/service/src/main/java/io/mifos/customer/service/internal/command/handler/TaskAggregate.java b/service/src/main/java/io/mifos/customer/service/internal/command/handler/TaskAggregate.java
index 5e1bac6..ab4cb41 100644
--- a/service/src/main/java/io/mifos/customer/service/internal/command/handler/TaskAggregate.java
+++ b/service/src/main/java/io/mifos/customer/service/internal/command/handler/TaskAggregate.java
@@ -19,6 +19,7 @@ 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.ServiceException;
 import io.mifos.customer.api.v1.CustomerEventConstants;
 import io.mifos.customer.api.v1.domain.Command;
 import io.mifos.customer.api.v1.domain.TaskDefinition;
@@ -95,8 +96,7 @@ public class TaskAggregate {
     final TaskDefinitionEntity taskDefinitionEntity =
         this.taskDefinitionRepository.findByIdentifier(addTaskDefinitionToCustomerCommand.taskIdentifier());
 
-    final CustomerEntity customerEntity =
-        this.customerRepository.findByIdentifier(addTaskDefinitionToCustomerCommand.customerIdentifier());
+    final CustomerEntity customerEntity = findCustomerEntityOrThrow(addTaskDefinitionToCustomerCommand.customerIdentifier());
 
     this.taskInstanceRepository.save(TaskInstanceMapper.create(taskDefinitionEntity, customerEntity));
 
@@ -107,8 +107,7 @@ public class TaskAggregate {
   @CommandHandler
   @EventEmitter(selectorName = CustomerEventConstants.SELECTOR_NAME, selectorValue = CustomerEventConstants.PUT_CUSTOMER)
   public String executeTaskForCustomer(final ExecuteTaskForCustomerCommand executeTaskForCustomerCommand) {
-    final CustomerEntity customerEntity =
-        this.customerRepository.findByIdentifier(executeTaskForCustomerCommand.customerIdentifier());
+    final CustomerEntity customerEntity = findCustomerEntityOrThrow(executeTaskForCustomerCommand.customerIdentifier());
     final List<TaskInstanceEntity> taskInstanceEntities = this.taskInstanceRepository.findByCustomer(customerEntity);
     if (taskInstanceEntities != null) {
       final Optional<TaskInstanceEntity> taskInstanceEntityOptional = taskInstanceEntities
@@ -160,4 +159,9 @@ public class TaskAggregate {
       return false;
     }
   }
+
+  private CustomerEntity findCustomerEntityOrThrow(String identifier) {
+    return this.customerRepository.findByIdentifier(identifier)
+        .orElseThrow(() -> ServiceException.notFound("Customer ''{0}'' not found", identifier));
+  }
 }
diff --git a/service/src/main/java/io/mifos/customer/service/internal/repository/CommandRepository.java b/service/src/main/java/io/mifos/customer/service/internal/repository/CommandRepository.java
index 7d0be4f..1d69ea9 100644
--- a/service/src/main/java/io/mifos/customer/service/internal/repository/CommandRepository.java
+++ b/service/src/main/java/io/mifos/customer/service/internal/repository/CommandRepository.java
@@ -17,9 +17,9 @@ package io.mifos.customer.service.internal.repository;
 
 import org.springframework.data.jpa.repository.JpaRepository;
 
-import java.util.List;
+import java.util.stream.Stream;
 
 public interface CommandRepository extends JpaRepository<CommandEntity, Long> {
 
-  List<CommandEntity> findByCustomer(final CustomerEntity customerEntity);
+  Stream<CommandEntity> findByCustomer(final CustomerEntity customerEntity);
 }
diff --git a/service/src/main/java/io/mifos/customer/service/internal/repository/CustomerRepository.java b/service/src/main/java/io/mifos/customer/service/internal/repository/CustomerRepository.java
index 0a85fa9..61391ad 100644
--- a/service/src/main/java/io/mifos/customer/service/internal/repository/CustomerRepository.java
+++ b/service/src/main/java/io/mifos/customer/service/internal/repository/CustomerRepository.java
@@ -22,6 +22,8 @@ import org.springframework.data.jpa.repository.Query;
 import org.springframework.data.repository.query.Param;
 import org.springframework.stereotype.Repository;
 
+import java.util.Optional;
+
 @Repository
 public interface CustomerRepository extends JpaRepository<CustomerEntity, Long> {
 
@@ -31,7 +33,7 @@ public interface CustomerRepository extends JpaRepository<CustomerEntity, Long>
   Page<CustomerEntity> findByIdentifierContainingOrGivenNameContainingOrSurnameContaining(
       final String identifier, final String givenName, final String surname, final Pageable pageable);
 
-  CustomerEntity findByIdentifier(final String identifier);
+  Optional<CustomerEntity> findByIdentifier(final String identifier);
 
   Page<CustomerEntity> findByCurrentStateNot(final String state, final Pageable pageable);
 
diff --git a/service/src/main/java/io/mifos/customer/service/internal/repository/IdentificationCardRepository.java b/service/src/main/java/io/mifos/customer/service/internal/repository/IdentificationCardRepository.java
index 294c249..7903e82 100644
--- a/service/src/main/java/io/mifos/customer/service/internal/repository/IdentificationCardRepository.java
+++ b/service/src/main/java/io/mifos/customer/service/internal/repository/IdentificationCardRepository.java
@@ -20,8 +20,8 @@ import org.springframework.data.jpa.repository.Query;
 import org.springframework.data.repository.query.Param;
 import org.springframework.stereotype.Repository;
 
-import java.util.List;
 import java.util.Optional;
+import java.util.stream.Stream;
 
 @Repository
 public interface IdentificationCardRepository extends JpaRepository<IdentificationCardEntity, Long> {
@@ -31,5 +31,5 @@ public interface IdentificationCardRepository extends JpaRepository<Identificati
 
   Optional<IdentificationCardEntity> findByNumber(final String number);
 
-  List<IdentificationCardEntity> findByCustomer(final CustomerEntity customerEntity);
+  Stream<IdentificationCardEntity> findByCustomer(final CustomerEntity customerEntity);
 }
diff --git a/service/src/main/java/io/mifos/customer/service/internal/service/CustomerService.java b/service/src/main/java/io/mifos/customer/service/internal/service/CustomerService.java
index cc886aa..280b509 100644
--- a/service/src/main/java/io/mifos/customer/service/internal/service/CustomerService.java
+++ b/service/src/main/java/io/mifos/customer/service/internal/service/CustomerService.java
@@ -15,42 +15,14 @@
  */
 package io.mifos.customer.service.internal.service;
 
-import io.mifos.customer.api.v1.domain.Command;
-import io.mifos.customer.api.v1.domain.Customer;
-import io.mifos.customer.api.v1.domain.CustomerPage;
-import io.mifos.customer.api.v1.domain.IdentificationCard;
-import io.mifos.customer.api.v1.domain.IdentificationCardScan;
-import io.mifos.customer.api.v1.domain.ProcessStep;
-import io.mifos.customer.api.v1.domain.TaskDefinition;
+import io.mifos.customer.api.v1.domain.*;
 import io.mifos.customer.catalog.api.v1.domain.Value;
 import io.mifos.customer.catalog.service.internal.repository.FieldEntity;
 import io.mifos.customer.catalog.service.internal.repository.FieldValueEntity;
 import io.mifos.customer.catalog.service.internal.repository.FieldValueRepository;
-import io.mifos.customer.service.ServiceConstants;
-import io.mifos.customer.service.internal.mapper.AddressMapper;
-import io.mifos.customer.service.internal.mapper.CommandMapper;
-import io.mifos.customer.service.internal.mapper.ContactDetailMapper;
-import io.mifos.customer.service.internal.mapper.CustomerMapper;
-import io.mifos.customer.service.internal.mapper.IdentificationCardMapper;
-import io.mifos.customer.service.internal.mapper.IdentificationCardScanMapper;
-import io.mifos.customer.service.internal.mapper.TaskDefinitionMapper;
-import io.mifos.customer.service.internal.repository.CommandEntity;
-import io.mifos.customer.service.internal.repository.CommandRepository;
-import io.mifos.customer.service.internal.repository.ContactDetailEntity;
-import io.mifos.customer.service.internal.repository.ContactDetailRepository;
-import io.mifos.customer.service.internal.repository.CustomerEntity;
-import io.mifos.customer.service.internal.repository.CustomerRepository;
-import io.mifos.customer.service.internal.repository.IdentificationCardEntity;
-import io.mifos.customer.service.internal.repository.IdentificationCardRepository;
-import io.mifos.customer.service.internal.repository.IdentificationCardScanEntity;
-import io.mifos.customer.service.internal.repository.IdentificationCardScanRepository;
-import io.mifos.customer.service.internal.repository.PortraitEntity;
-import io.mifos.customer.service.internal.repository.PortraitRepository;
-import io.mifos.customer.service.internal.repository.TaskDefinitionRepository;
-import io.mifos.customer.service.internal.repository.TaskInstanceRepository;
-import org.slf4j.Logger;
+import io.mifos.customer.service.internal.mapper.*;
+import io.mifos.customer.service.internal.repository.*;
 import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.beans.factory.annotation.Qualifier;
 import org.springframework.data.domain.Page;
 import org.springframework.data.domain.Pageable;
 import org.springframework.stereotype.Service;
@@ -60,11 +32,11 @@ import java.util.Collections;
 import java.util.List;
 import java.util.Optional;
 import java.util.stream.Collectors;
+import java.util.stream.Stream;
 
 @Service
 public class CustomerService {
 
-  private final Logger logger;
   private final CustomerRepository customerRepository;
   private final IdentificationCardRepository identificationCardRepository;
   private final IdentificationCardScanRepository identificationCardScanRepository;
@@ -76,8 +48,7 @@ public class CustomerService {
   private final TaskInstanceRepository taskInstanceRepository;
 
   @Autowired
-  public CustomerService(@Qualifier(ServiceConstants.LOGGER_NAME) final Logger logger,
-                         final CustomerRepository customerRepository,
+  public CustomerService(final CustomerRepository customerRepository,
                          final IdentificationCardRepository identificationCardRepository,
                          final IdentificationCardScanRepository identificationCardScanRepository,
                          final PortraitRepository portraitRepository,
@@ -87,7 +58,6 @@ public class CustomerService {
                          final TaskDefinitionRepository taskDefinitionRepository,
                          final TaskInstanceRepository taskInstanceRepository) {
     super();
-    this.logger = logger;
     this.customerRepository = customerRepository;
     this.identificationCardRepository = identificationCardRepository;
     this.identificationCardScanRepository = identificationCardScanRepository;
@@ -103,10 +73,6 @@ public class CustomerService {
     return this.customerRepository.existsByIdentifier(identifier);
   }
 
-  public Boolean portraitExists(final String identifier) {
-    return this.portraitRepository.existsByIdentifier(identifier);
-  }
-
   public Boolean identificationCardExists(final String number) {
     return this.identificationCardRepository.existsByNumber(number);
   }
@@ -118,41 +84,39 @@ public class CustomerService {
   }
 
   public Optional<Customer> findCustomer(final String identifier) {
-    final CustomerEntity customerEntity = this.customerRepository.findByIdentifier(identifier);
-    if (customerEntity != null) {
-      final Customer customer = CustomerMapper.map(customerEntity);
-      customer.setAddress(AddressMapper.map(customerEntity.getAddress()));
-
-      final List<ContactDetailEntity> contactDetailEntities = this.contactDetailRepository.findByCustomer(customerEntity);
-      if (contactDetailEntities != null) {
-        customer.setContactDetails(
-            contactDetailEntities
-                .stream()
-                  .map(ContactDetailMapper::map)
-                  .collect(Collectors.toList())
-        );
-      }
-
-      final List<FieldValueEntity> fieldValueEntities = this.fieldValueRepository.findByCustomer(customerEntity);
-      if (fieldValueEntities != null) {
-        customer.setCustomValues(
-            fieldValueEntities
-                .stream()
-                .map(fieldValueEntity -> {
-                  final Value value = new Value();
-                  value.setValue(fieldValueEntity.getValue());
-                  final FieldEntity fieldEntity = fieldValueEntity.getField();
-                  value.setCatalogIdentifier(fieldEntity.getCatalog().getIdentifier());
-                  value.setFieldIdentifier(fieldEntity.getIdentifier());
-                  return value;
-                }).collect(Collectors.toList())
-        );
-      }
-
-      return Optional.of(customer);
-    } else {
-      return Optional.empty();
-    }
+    return customerRepository.findByIdentifier(identifier)
+        .map(customerEntity -> {
+          final Customer customer = CustomerMapper.map(customerEntity);
+          customer.setAddress(AddressMapper.map(customerEntity.getAddress()));
+
+          final List<ContactDetailEntity> contactDetailEntities = this.contactDetailRepository.findByCustomer(customerEntity);
+          if (contactDetailEntities != null) {
+            customer.setContactDetails(
+                contactDetailEntities
+                    .stream()
+                    .map(ContactDetailMapper::map)
+                    .collect(Collectors.toList())
+            );
+          }
+
+          final List<FieldValueEntity> fieldValueEntities = this.fieldValueRepository.findByCustomer(customerEntity);
+          if (fieldValueEntities != null) {
+            customer.setCustomValues(
+                fieldValueEntities
+                    .stream()
+                    .map(fieldValueEntity -> {
+                      final Value value = new Value();
+                      value.setValue(fieldValueEntity.getValue());
+                      final FieldEntity fieldEntity = fieldValueEntity.getField();
+                      value.setCatalogIdentifier(fieldEntity.getCatalog().getIdentifier());
+                      value.setFieldIdentifier(fieldEntity.getIdentifier());
+                      return value;
+                    }).collect(Collectors.toList())
+            );
+          }
+
+          return customer;
+        });
   }
 
   public CustomerPage fetchCustomer(final String term, final Boolean includeClosed, final Pageable pageable) {
@@ -186,31 +150,23 @@ public class CustomerService {
     return customerPage;
   }
 
-  public final List<Command> fetchCommandsByCustomer(final String identifier) {
-    final CustomerEntity customerEntity = this.customerRepository.findByIdentifier(identifier);
-    final List<CommandEntity> commands = this.commandRepository.findByCustomer(customerEntity);
-    if (commands != null) {
-      return commands.stream().map(CommandMapper::map).collect(Collectors.toList());
-    } else {
-      return Collections.emptyList();
-    }
+  public final Stream<Command> fetchCommandsByCustomer(final String identifier) {
+    return customerRepository.findByIdentifier(identifier)
+        .map(commandRepository::findByCustomer)
+        .orElse(Stream.empty())
+        .map(CommandMapper::map);
   }
 
-  public final PortraitEntity findPortrait(final String identifier) {
-    final CustomerEntity customerEntity = this.customerRepository.findByIdentifier(identifier);
-
-    return this.portraitRepository.findByCustomer(customerEntity);
+  public final Optional<PortraitEntity> findPortrait(final String identifier) {
+    return customerRepository.findByIdentifier(identifier)
+        .map(portraitRepository::findByCustomer);
   }
 
-  public final List<IdentificationCard> fetchIdentificationCardsByCustomer(final String identifier) {
-    final CustomerEntity customerEntity = this.customerRepository.findByIdentifier(identifier);
-    final List<IdentificationCardEntity> identificationCards = this.identificationCardRepository.findByCustomer(customerEntity);
-
-    if (identificationCards != null) {
-      return identificationCards.stream().map(IdentificationCardMapper::map).collect(Collectors.toList());
-    } else {
-      return Collections.emptyList();
-    }
+  public final Stream<IdentificationCard> fetchIdentificationCardsByCustomer(final String identifier) {
+    return customerRepository.findByIdentifier(identifier)
+        .map(identificationCardRepository::findByCustomer)
+        .orElse(Stream.empty())
+        .map(IdentificationCardMapper::map);
   }
 
   public Optional<IdentificationCard> findIdentificationCard(final String number) {
@@ -229,8 +185,7 @@ public class CustomerService {
 
   private Optional<IdentificationCardScanEntity> findIdentificationCardEntity(final String number, final String identifier) {
     final Optional<IdentificationCardEntity> cardEntity = this.identificationCardRepository.findByNumber(number);
-    final Optional<IdentificationCardScanEntity> cardScanEntity = cardEntity.flatMap(card -> this.identificationCardScanRepository.findByIdentifierAndIdentificationCard(identifier, card));
-    return cardScanEntity;
+    return cardEntity.flatMap(card -> this.identificationCardScanRepository.findByIdentifierAndIdentificationCard(identifier, card));
   }
 
   public Optional<IdentificationCardScan> findIdentificationCardScan(final String number, final String identifier) {
@@ -242,29 +197,32 @@ public class CustomerService {
   }
 
   public List<ProcessStep> getProcessSteps(final String customerIdentifier) {
-    final ArrayList<ProcessStep> processSteps = new ArrayList<>();
-    final CustomerEntity customerEntity = this.customerRepository.findByIdentifier(customerIdentifier);
-
-    final Customer.State state = Customer.State.valueOf(customerEntity.getCurrentState());
-    switch (state) {
-      case PENDING:
-        processSteps.add(this.buildProcessStep(customerEntity, Command.Action.ACTIVATE));
-        processSteps.add(this.buildProcessStep(customerEntity, Command.Action.CLOSE));
-        break;
-      case ACTIVE:
-        processSteps.add(this.buildProcessStep(customerEntity, Command.Action.LOCK));
-        processSteps.add(this.buildProcessStep(customerEntity, Command.Action.CLOSE));
-        break;
-      case LOCKED:
-        processSteps.add(this.buildProcessStep(customerEntity, Command.Action.UNLOCK));
-        processSteps.add(this.buildProcessStep(customerEntity, Command.Action.CLOSE));
-        break;
-      case CLOSED:
-        processSteps.add(this.buildProcessStep(customerEntity, Command.Action.REOPEN));
-        break;
-    }
-
-    return processSteps;
+    return customerRepository.findByIdentifier(customerIdentifier)
+        .map(customerEntity -> {
+          final List<ProcessStep> processSteps = new ArrayList<>();
+
+          final Customer.State state = Customer.State.valueOf(customerEntity.getCurrentState());
+          switch (state) {
+            case PENDING:
+              processSteps.add(this.buildProcessStep(customerEntity, Command.Action.ACTIVATE));
+              processSteps.add(this.buildProcessStep(customerEntity, Command.Action.CLOSE));
+              break;
+            case ACTIVE:
+              processSteps.add(this.buildProcessStep(customerEntity, Command.Action.LOCK));
+              processSteps.add(this.buildProcessStep(customerEntity, Command.Action.CLOSE));
+              break;
+            case LOCKED:
+              processSteps.add(this.buildProcessStep(customerEntity, Command.Action.UNLOCK));
+              processSteps.add(this.buildProcessStep(customerEntity, Command.Action.CLOSE));
+              break;
+            case CLOSED:
+              processSteps.add(this.buildProcessStep(customerEntity, Command.Action.REOPEN));
+              break;
+          }
+
+          return processSteps;
+        })
+        .orElse(Collections.emptyList());
   }
 
   private ProcessStep buildProcessStep(final CustomerEntity customerEntity, final Command.Action action) {
@@ -276,14 +234,13 @@ public class CustomerService {
 
     final ArrayList<TaskDefinition> taskDefinitions = new ArrayList<>();
     this.taskDefinitionRepository.findByAssignedCommandsContaining(action.name())
-        .forEach(taskDefinitionEntity -> {
-          this.taskInstanceRepository.findByCustomerAndTaskDefinition(customerEntity, taskDefinitionEntity)
-              .forEach(taskInstanceEntity -> {
-                if (taskInstanceEntity.getExecutedBy() == null) {
-                  taskDefinitions.add(TaskDefinitionMapper.map(taskDefinitionEntity));
-                }
-              });
-        });
+        .forEach(taskDefinitionEntity ->
+            this.taskInstanceRepository.findByCustomerAndTaskDefinition(customerEntity, taskDefinitionEntity)
+            .forEach(taskInstanceEntity -> {
+              if (taskInstanceEntity.getExecutedBy() == null) {
+                taskDefinitions.add(TaskDefinitionMapper.map(taskDefinitionEntity));
+              }
+            }));
     processStep.setTaskDefinitions(taskDefinitions);
 
     return processStep;
diff --git a/service/src/main/java/io/mifos/customer/service/internal/service/TaskService.java b/service/src/main/java/io/mifos/customer/service/internal/service/TaskService.java
index 66132fc..a751601 100644
--- a/service/src/main/java/io/mifos/customer/service/internal/service/TaskService.java
+++ b/service/src/main/java/io/mifos/customer/service/internal/service/TaskService.java
@@ -17,7 +17,6 @@ package io.mifos.customer.service.internal.service;
 
 import io.mifos.customer.api.v1.domain.TaskDefinition;
 import io.mifos.customer.service.internal.mapper.TaskDefinitionMapper;
-import io.mifos.customer.service.internal.repository.CustomerEntity;
 import io.mifos.customer.service.internal.repository.CustomerRepository;
 import io.mifos.customer.service.internal.repository.TaskDefinitionEntity;
 import io.mifos.customer.service.internal.repository.TaskDefinitionRepository;
@@ -25,6 +24,7 @@ import io.mifos.customer.service.internal.repository.TaskInstanceRepository;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 
+import java.util.Collections;
 import java.util.List;
 import java.util.Optional;
 import java.util.stream.Collectors;
@@ -67,8 +67,9 @@ public class TaskService {
   }
 
   public List<TaskDefinition> findTasksByCustomer(final String customerIdentifier, Boolean includeExecuted) {
-    final CustomerEntity customerEntity = this.customerRepository.findByIdentifier(customerIdentifier);
-    return this.taskInstanceRepository.findByCustomer(customerEntity)
+    return customerRepository.findByIdentifier(customerIdentifier)
+        .map(taskInstanceRepository::findByCustomer)
+        .orElse(Collections.emptyList())
         .stream()
         .filter(taskInstanceEntity -> (includeExecuted ? true : taskInstanceEntity.getExecutedBy() == null))
         .map(taskInstanceEntity -> TaskDefinitionMapper.map(taskInstanceEntity.getTaskDefinition()))
diff --git a/service/src/main/java/io/mifos/customer/service/rest/controller/CustomerRestController.java b/service/src/main/java/io/mifos/customer/service/rest/controller/CustomerRestController.java
index d07124e..d5010f5 100644
--- a/service/src/main/java/io/mifos/customer/service/rest/controller/CustomerRestController.java
+++ b/service/src/main/java/io/mifos/customer/service/rest/controller/CustomerRestController.java
@@ -79,6 +79,8 @@ import javax.validation.Valid;
 import javax.validation.constraints.Size;
 import java.util.List;
 import java.util.Optional;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
 
 @RestController
 @RequestMapping("/")
@@ -267,7 +269,7 @@ public class CustomerRestController {
   @ResponseBody
   ResponseEntity<List<Command>> fetchCustomerCommands(@PathVariable("identifier") final String identifier) {
     if (this.customerService.customerExists(identifier)) {
-      return ResponseEntity.ok(this.customerService.fetchCommandsByCustomer(identifier));
+      return ResponseEntity.ok(this.customerService.fetchCommandsByCustomer(identifier).collect(Collectors.toList()));
     } else {
       throw ServiceException.notFound("Customer {0} not found.", identifier);
     }
@@ -315,8 +317,8 @@ public class CustomerRestController {
         final TaskDefinition taskDefinition = optionalTaskDefinition.get();
         switch (TaskDefinition.Type.valueOf(taskDefinition.getType())) {
           case ID_CARD:
-            final List<IdentificationCard> identificationCards = this.customerService.fetchIdentificationCardsByCustomer(identifier);
-            if (identificationCards.isEmpty()) {
+            final Stream<IdentificationCard> identificationCards = this.customerService.fetchIdentificationCardsByCustomer(identifier);
+            if (!identificationCards.findAny().isPresent()) {
               throw ServiceException.conflict("No identification cards for customer found.");
             }
             break;
@@ -401,7 +403,7 @@ public class CustomerRestController {
   )
   public @ResponseBody ResponseEntity<List<IdentificationCard>> fetchIdentificationCards(@PathVariable("identifier") final String identifier) {
     this.throwIfCustomerNotExists(identifier);
-    return ResponseEntity.ok(this.customerService.fetchIdentificationCardsByCustomer(identifier));
+    return ResponseEntity.ok(this.customerService.fetchIdentificationCardsByCustomer(identifier).collect(Collectors.toList()));
   }
 
   @Permittable(value = AcceptedTokenType.TENANT, groupId = PermittableGroupIds.IDENTIFICATIONS)
@@ -612,9 +614,8 @@ public class CustomerRestController {
       consumes = MediaType.ALL_VALUE
   )
   public ResponseEntity<byte[]> getPortrait(@PathVariable("identifier") final String identifier) {
-    this.throwIfPortraitNotExists(identifier);
-
-    final PortraitEntity portrait = this.customerService.findPortrait(identifier);
+    final PortraitEntity portrait = this.customerService.findPortrait(identifier)
+        .orElseThrow(() -> ServiceException.notFound("Portrait for Customer ''{0}'' not found.", identifier));
 
     return ResponseEntity
             .ok()
@@ -759,12 +760,6 @@ public class CustomerRestController {
     }
   }
 
-  private void throwIfPortraitNotExists(final String identifier) {
-    if (!this.customerService.portraitExists(identifier)) {
-      throw ServiceException.notFound("Portrait for Customer {0} not found.", identifier);
-    }
-  }
-
   private void throwIfIdentificationCardNotExists(final String number) {
     if (!this.customerService.identificationCardExists(number)) {
       throw ServiceException.notFound("Identification card {0} not found.", number);

-- 
To stop receiving notification emails like this one, please contact
myrle@apache.org.

[fineract-cn-customer] 42/46: Merge pull request #17 from myrlen/develop

Posted by my...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

myrle pushed a commit to branch develop
in repository https://gitbox.apache.org/repos/asf/fineract-cn-customer.git

commit b0e57a28a4672f7b33dc3afb13cdaed876cd17d0
Merge: aacadcb 5e05d4a
Author: myrlen <my...@kuelap.io>
AuthorDate: Fri Oct 20 12:49:02 2017 +0200

    Merge pull request #17 from myrlen/develop
    
    TestCustomer.shouldDeletePortrait was failing intermittently

 .../main/java/io/mifos/customer/TestCustomer.java  |   6 +-
 .../command/handler/CustomerAggregate.java         |  87 +++------
 .../command/handler/DocumentCommandHandler.java    |  18 +-
 .../internal/command/handler/TaskAggregate.java    |  12 +-
 .../internal/repository/CommandRepository.java     |   4 +-
 .../internal/repository/CustomerRepository.java    |   4 +-
 .../repository/IdentificationCardRepository.java   |   4 +-
 .../service/internal/service/CustomerService.java  | 213 ++++++++-------------
 .../service/internal/service/TaskService.java      |   7 +-
 .../rest/controller/CustomerRestController.java    |  21 +-
 10 files changed, 148 insertions(+), 228 deletions(-)

-- 
To stop receiving notification emails like this one, please contact
myrle@apache.org.

[fineract-cn-customer] 12/46: Merge pull request #3 from markusgeiss/develop

Posted by my...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

myrle pushed a commit to branch develop
in repository https://gitbox.apache.org/repos/asf/fineract-cn-customer.git

commit fdabad40c6baf052aba4a49cdc03d287e6d9f38e
Merge: 73def97 50a054e
Author: Markus Geiss <ma...@kuelap.io>
AuthorDate: Wed Sep 6 12:03:43 2017 +0200

    Merge pull request #3 from markusgeiss/develop
    
    added validation to prevent empty commands in task definition

 api/src/main/java/io/mifos/customer/api/v1/domain/TaskDefinition.java | 2 ++
 1 file changed, 2 insertions(+)

-- 
To stop receiving notification emails like this one, please contact
myrle@apache.org.

[fineract-cn-customer] 15/46: added filtering for task based on command

Posted by my...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

myrle pushed a commit to branch develop
in repository https://gitbox.apache.org/repos/asf/fineract-cn-customer.git

commit f078702d45124811a6e8dbf280de8305224adcc5
Author: mgeiss <mg...@mifos.org>
AuthorDate: Wed Sep 6 13:53:09 2017 +0200

    added filtering for task based on command
---
 .../service/internal/command/handler/CustomerAggregate.java         | 6 +++---
 .../customer/service/internal/command/handler/TaskAggregate.java    | 3 ++-
 2 files changed, 5 insertions(+), 4 deletions(-)

diff --git a/service/src/main/java/io/mifos/customer/service/internal/command/handler/CustomerAggregate.java b/service/src/main/java/io/mifos/customer/service/internal/command/handler/CustomerAggregate.java
index 917b364..b41ddfb 100644
--- a/service/src/main/java/io/mifos/customer/service/internal/command/handler/CustomerAggregate.java
+++ b/service/src/main/java/io/mifos/customer/service/internal/command/handler/CustomerAggregate.java
@@ -231,7 +231,7 @@ public class CustomerAggregate {
   public String activateCustomer(final ActivateCustomerCommand activateCustomerCommand) {
     final CustomerEntity customerEntity = this.customerRepository.findByIdentifier(activateCustomerCommand.identifier());
 
-    if (this.taskAggregate.openTasksForCustomerExist(customerEntity)) {
+    if (this.taskAggregate.openTasksForCustomerExist(customerEntity, Command.Action.ACTIVATE.name())) {
       throw ServiceException.conflict("Open Tasks for customer {0} exists.", activateCustomerCommand.identifier());
     }
 
@@ -274,7 +274,7 @@ public class CustomerAggregate {
   public String unlockCustomer(final UnlockCustomerCommand unlockCustomerCommand) {
     final CustomerEntity customerEntity = this.customerRepository.findByIdentifier(unlockCustomerCommand.identifier());
 
-    if (this.taskAggregate.openTasksForCustomerExist(customerEntity)) {
+    if (this.taskAggregate.openTasksForCustomerExist(customerEntity, Command.Action.UNLOCK.name())) {
       throw ServiceException.conflict("Open Tasks for customer {0} exists.", unlockCustomerCommand.identifier());
     }
 
@@ -317,7 +317,7 @@ public class CustomerAggregate {
   public String reopenCustomer(final ReopenCustomerCommand reopenCustomerCommand) {
     final CustomerEntity customerEntity = this.customerRepository.findByIdentifier(reopenCustomerCommand.identifier());
 
-    if (this.taskAggregate.openTasksForCustomerExist(customerEntity)) {
+    if (this.taskAggregate.openTasksForCustomerExist(customerEntity, Command.Action.REOPEN.name())) {
       throw ServiceException.conflict("Open Tasks for customer {0} exists.", reopenCustomerCommand.identifier());
     }
 
diff --git a/service/src/main/java/io/mifos/customer/service/internal/command/handler/TaskAggregate.java b/service/src/main/java/io/mifos/customer/service/internal/command/handler/TaskAggregate.java
index 9a0417c..65a03d5 100644
--- a/service/src/main/java/io/mifos/customer/service/internal/command/handler/TaskAggregate.java
+++ b/service/src/main/java/io/mifos/customer/service/internal/command/handler/TaskAggregate.java
@@ -145,13 +145,14 @@ public class TaskAggregate {
   }
 
   @Transactional
-  public Boolean openTasksForCustomerExist(final CustomerEntity customerEntity) {
+  public Boolean openTasksForCustomerExist(final CustomerEntity customerEntity, final String command) {
     final List<TaskInstanceEntity> taskInstanceEntities = this.taskInstanceRepository.findByCustomer(customerEntity);
 
     //noinspection SimplifiableIfStatement
     if (taskInstanceEntities != null) {
       return taskInstanceEntities
           .stream()
+              .filter(taskInstanceEntity -> taskInstanceEntity.getTaskDefinition().getAssignedCommands().contains(command))
               .filter(taskInstanceEntity -> taskInstanceEntity.getTaskDefinition().isMandatory())
               .anyMatch(taskInstanceEntity -> taskInstanceEntity.getExecutedBy() == null);
     } else {

-- 
To stop receiving notification emails like this one, please contact
myrle@apache.org.

[fineract-cn-customer] 45/46: added time zone 'Z' handling to ease UI usage

Posted by my...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

myrle pushed a commit to branch develop
in repository https://gitbox.apache.org/repos/asf/fineract-cn-customer.git

commit dfad7e2f347bd6838249a790aa299bfbaed4a739
Author: mgeiss <mg...@mifos.org>
AuthorDate: Tue Oct 24 07:01:32 2017 -0400

    added time zone 'Z' handling to ease UI usage
---
 .../src/main/java/io/mifos/customer/TestCustomer.java        |  2 +-
 .../customer/service/internal/mapper/CustomerMapper.java     | 12 ++++++++++--
 2 files changed, 11 insertions(+), 3 deletions(-)

diff --git a/component-test/src/main/java/io/mifos/customer/TestCustomer.java b/component-test/src/main/java/io/mifos/customer/TestCustomer.java
index f5e53f3..66dc41e 100644
--- a/component-test/src/main/java/io/mifos/customer/TestCustomer.java
+++ b/component-test/src/main/java/io/mifos/customer/TestCustomer.java
@@ -218,7 +218,7 @@ public class TestCustomer extends AbstractCustomerTest {
   @Test
   public void shouldUnlockClient() throws Exception {
     final Customer customer = CustomerGenerator.createRandomCustomer();
-    final String applicationDate = DateConverter.toIsoString(LocalDate.now(Clock.systemUTC()));
+    final String applicationDate = DateConverter.toIsoString(LocalDate.now(Clock.systemUTC())).substring(0, 10);
     customer.setApplicationDate(applicationDate);
     this.customerManager.createCustomer(customer);
 
diff --git a/service/src/main/java/io/mifos/customer/service/internal/mapper/CustomerMapper.java b/service/src/main/java/io/mifos/customer/service/internal/mapper/CustomerMapper.java
index 6cf7569..2e8f867 100644
--- a/service/src/main/java/io/mifos/customer/service/internal/mapper/CustomerMapper.java
+++ b/service/src/main/java/io/mifos/customer/service/internal/mapper/CustomerMapper.java
@@ -46,7 +46,13 @@ public final class CustomerMapper {
     customerEntity.setAssignedEmployee(customer.getAssignedEmployee());
     customerEntity.setCurrentState(customer.getCurrentState());
     if (customer.getApplicationDate() != null) {
-      customerEntity.setApplicationDate(DateConverter.dateFromIsoString(customer.getApplicationDate()));
+      final String editedApplicationDate;
+      if (!customer.getApplicationDate().endsWith("Z")) {
+        editedApplicationDate = customer.getApplicationDate() + "Z";
+      } else {
+        editedApplicationDate = customer.getApplicationDate();
+      }
+      customerEntity.setApplicationDate(DateConverter.dateFromIsoString(editedApplicationDate));
     }
     customerEntity.setCreatedBy(UserContextHolder.checkedGetUser());
     customerEntity.setCreatedOn(LocalDateTime.now(Clock.systemUTC()));
@@ -68,7 +74,9 @@ public final class CustomerMapper {
     customer.setAssignedEmployee(customerEntity.getAssignedEmployee());
     customer.setCurrentState(customerEntity.getCurrentState());
     if (customerEntity.getApplicationDate() != null) {
-      customer.setApplicationDate(DateConverter.toIsoString(customerEntity.getApplicationDate()));
+      final String editedApplicationDate =
+          DateConverter.toIsoString(customerEntity.getApplicationDate()).substring(0, 10);
+      customer.setApplicationDate(editedApplicationDate);
     }
     customer.setCreatedBy(customerEntity.getCreatedBy());
     customer.setCreatedOn(DateConverter.toIsoString(customerEntity.getCreatedOn()));

-- 
To stop receiving notification emails like this one, please contact
myrle@apache.org.

[fineract-cn-customer] 05/46: Merge pull request #14 from crain/develop

Posted by my...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

myrle pushed a commit to branch develop
in repository https://gitbox.apache.org/repos/asf/fineract-cn-customer.git

commit 65d25fe85c4d5d6943828af9f023112012d61cc7
Merge: 1500da3 498c48e
Author: Mark <cr...@users.noreply.github.com>
AuthorDate: Wed Jul 19 18:34:33 2017 +0200

    Merge pull request #14 from crain/develop
    
    Add identification card scan upload

 .../customer/api/v1/CustomerEventConstants.java    |   6 +
 .../customer/api/v1/client/CustomerManager.java    |  83 +++++--
 .../api/v1/client/ScanAlreadyExistsException.java  |  19 +-
 .../api/v1/client/ScanNotFoundException.java       |  19 +-
 .../api/v1/client/ScanValidationException.java     |  17 +-
 .../api/v1/domain/IdentificationCardScan.java      |  38 ++--
 .../io/mifos/customer/api/v1/events/ScanEvent.java |  52 +++++
 .../main/java/io/mifos/customer/TestCustomer.java  | 105 +--------
 .../io/mifos/customer/TestIdentificationCards.java | 252 +++++++++++++++++++++
 .../src/main/java/io/mifos/customer/TestSuite.java |   1 +
 .../customer/listener/CustomerEventListener.java   |  19 ++
 .../java/io/mifos/customer/util/ScanGenerator.java |  39 ++++
 .../CreateIdentificationCardScanCommand.java       |  52 +++++
 .../DeleteIdentificationCardScanCommand.java       |  32 +--
 .../command/handler/CustomerAggregate.java         |  64 ++++++
 .../mapper/IdentificationCardScanMapper.java       |  44 ++++
 .../repository/IdentificationCardScanEntity.java   | 128 +++++++++++
 .../IdentificationCardScanRepository.java          |  35 +++
 .../service/internal/service/CustomerService.java  |  45 +++-
 .../rest/controller/CustomerRestController.java    | 151 +++++++++++-
 .../mariadb/V4__identification_card_scans.sql      |  30 +++
 21 files changed, 1016 insertions(+), 215 deletions(-)

-- 
To stop receiving notification emails like this one, please contact
myrle@apache.org.

[fineract-cn-customer] 21/46: refactored payroll into own service

Posted by my...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

myrle pushed a commit to branch develop
in repository https://gitbox.apache.org/repos/asf/fineract-cn-customer.git

commit 167341d129b1413ba10a83eec20b2aa950b33397
Author: mgeiss <mg...@mifos.org>
AuthorDate: Mon Sep 18 17:10:03 2017 +0200

    refactored payroll into own service
---
 .../customer/api/v1/CustomerEventConstants.java    |   3 -
 .../customer/api/v1/client/CustomerManager.java    |  26 -----
 .../PayrollDistributionValidationException.java    |  19 ---
 .../customer/api/v1/domain/PayrollAllocation.java  |  58 ----------
 .../api/v1/domain/PayrollDistribution.java         |  86 --------------
 .../io/mifos/customer/TestPayrollDistribution.java | 115 ------------------
 .../customer/listener/CustomerEventListener.java   |   9 --
 .../command/SetPayrollDistributionCommand.java     |  38 ------
 .../command/handler/CustomerAggregate.java         |  63 +---------
 .../internal/mapper/PayrollDistributionMapper.java |  55 ---------
 .../repository/PayrollAllocationEntity.java        |  89 --------------
 .../repository/PayrollAllocationRepository.java    |  22 ----
 .../repository/PayrollDistributionEntity.java      | 128 ---------------------
 .../repository/PayrollDistributionRepository.java  |  24 ----
 .../service/internal/service/CustomerService.java  |  13 +--
 .../rest/controller/CustomerRestController.java    |  34 ------
 .../mariadb/V6__drop_pyroll_distributions.sql      |  18 +++
 17 files changed, 20 insertions(+), 780 deletions(-)

diff --git a/api/src/main/java/io/mifos/customer/api/v1/CustomerEventConstants.java b/api/src/main/java/io/mifos/customer/api/v1/CustomerEventConstants.java
index 21793b6..ab5a9a3 100644
--- a/api/src/main/java/io/mifos/customer/api/v1/CustomerEventConstants.java
+++ b/api/src/main/java/io/mifos/customer/api/v1/CustomerEventConstants.java
@@ -46,7 +46,6 @@ public interface CustomerEventConstants {
 
   String POST_PORTRAIT = "post-portrait";
   String DELETE_PORTRAIT = "delete-portrait";
-  String PUT_PAYROLL_DISTRIBUTION = "put-payroll-distribution";
 
   String SELECTOR_INITIALIZE = SELECTOR_NAME + " = '" + INITIALIZE + "'";
 
@@ -73,6 +72,4 @@ public interface CustomerEventConstants {
 
   String SELECTOR_PUT_PORTRAIT = SELECTOR_NAME + " = '" + POST_PORTRAIT + "'";
   String SELECTOR_DELETE_PORTRAIT = SELECTOR_NAME + " = '" + DELETE_PORTRAIT + "'";
-
-  String SELECTOR_PUT_PAYROLL_DISTRIBUTION = SELECTOR_NAME + " = '" + PUT_PAYROLL_DISTRIBUTION + "'";
 }
diff --git a/api/src/main/java/io/mifos/customer/api/v1/client/CustomerManager.java b/api/src/main/java/io/mifos/customer/api/v1/client/CustomerManager.java
index 024dd2c..1d56aea 100644
--- a/api/src/main/java/io/mifos/customer/api/v1/client/CustomerManager.java
+++ b/api/src/main/java/io/mifos/customer/api/v1/client/CustomerManager.java
@@ -26,7 +26,6 @@ import io.mifos.customer.api.v1.domain.Customer;
 import io.mifos.customer.api.v1.domain.CustomerPage;
 import io.mifos.customer.api.v1.domain.IdentificationCard;
 import io.mifos.customer.api.v1.domain.IdentificationCardScan;
-import io.mifos.customer.api.v1.domain.PayrollDistribution;
 import io.mifos.customer.api.v1.domain.ProcessStep;
 import io.mifos.customer.api.v1.domain.TaskDefinition;
 import org.springframework.cloud.netflix.feign.FeignClient;
@@ -39,7 +38,6 @@ import org.springframework.web.bind.annotation.RequestMethod;
 import org.springframework.web.bind.annotation.RequestParam;
 import org.springframework.web.multipart.MultipartFile;
 
-import javax.validation.Valid;
 import javax.validation.constraints.Size;
 import java.util.List;
 
@@ -393,28 +391,4 @@ public interface CustomerManager {
   )
   @ThrowsException(status = HttpStatus.NOT_FOUND, exception = CustomerNotFoundException.class)
   List<ProcessStep> fetchProcessSteps(@PathVariable(value = "identifier") final String customerIdentifier);
-
-  @RequestMapping(
-      value = "/customers/{identifier}/payroll",
-      method = RequestMethod.PUT,
-      produces = MediaType.APPLICATION_JSON_VALUE,
-      consumes = MediaType.APPLICATION_JSON_VALUE
-  )
-  @ThrowsExceptions({
-      @ThrowsException(status = HttpStatus.NOT_FOUND, exception = CustomerNotFoundException.class),
-      @ThrowsException(status = HttpStatus.BAD_REQUEST, exception = PayrollDistributionValidationException.class)
-  })
-  void setPayrollDistribution(@PathVariable(value = "identifier") final String customerIdentifier,
-                              @RequestBody @Valid final PayrollDistribution payrollDistribution);
-
-  @RequestMapping(
-      value = "/customers/{identifier}/payroll",
-      method = RequestMethod.GET,
-      produces = MediaType.ALL_VALUE,
-      consumes = MediaType.APPLICATION_JSON_VALUE
-  )
-  @ThrowsExceptions({
-      @ThrowsException(status = HttpStatus.NOT_FOUND, exception = CustomerNotFoundException.class)
-  })
-  PayrollDistribution getPayrollDistribution(@PathVariable(value = "identifier") final String customerIdentifier);
 }
diff --git a/api/src/main/java/io/mifos/customer/api/v1/client/PayrollDistributionValidationException.java b/api/src/main/java/io/mifos/customer/api/v1/client/PayrollDistributionValidationException.java
deleted file mode 100644
index 52caca1..0000000
--- a/api/src/main/java/io/mifos/customer/api/v1/client/PayrollDistributionValidationException.java
+++ /dev/null
@@ -1,19 +0,0 @@
-/*
- * Copyright 2017 The Mifos Initiative.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *    http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package io.mifos.customer.api.v1.client;
-
-public class PayrollDistributionValidationException extends RuntimeException {
-}
diff --git a/api/src/main/java/io/mifos/customer/api/v1/domain/PayrollAllocation.java b/api/src/main/java/io/mifos/customer/api/v1/domain/PayrollAllocation.java
deleted file mode 100644
index 10d49cd..0000000
--- a/api/src/main/java/io/mifos/customer/api/v1/domain/PayrollAllocation.java
+++ /dev/null
@@ -1,58 +0,0 @@
-/*
- * Copyright 2017 The Mifos Initiative.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *    http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package io.mifos.customer.api.v1.domain;
-
-import io.mifos.core.lang.validation.constraints.ValidIdentifier;
-
-import javax.validation.constraints.DecimalMin;
-import java.math.BigDecimal;
-
-public class PayrollAllocation {
-
-  @ValidIdentifier
-  private String accountNumber;
-  @DecimalMin("0.001")
-  private BigDecimal amount;
-  private Boolean proportional = Boolean.FALSE;
-
-  public PayrollAllocation() {
-    super();
-  }
-
-  public String getAccountNumber() {
-    return this.accountNumber;
-  }
-
-  public void setAccountNumber(final String accountNumber) {
-    this.accountNumber = accountNumber;
-  }
-
-  public BigDecimal getAmount() {
-    return this.amount;
-  }
-
-  public void setAmount(final BigDecimal amount) {
-    this.amount = amount;
-  }
-
-  public Boolean getProportional() {
-    return this.proportional;
-  }
-
-  public void setProportional(final Boolean proportional) {
-    this.proportional = proportional;
-  }
-}
diff --git a/api/src/main/java/io/mifos/customer/api/v1/domain/PayrollDistribution.java b/api/src/main/java/io/mifos/customer/api/v1/domain/PayrollDistribution.java
deleted file mode 100644
index d905f11..0000000
--- a/api/src/main/java/io/mifos/customer/api/v1/domain/PayrollDistribution.java
+++ /dev/null
@@ -1,86 +0,0 @@
-/*
- * Copyright 2017 The Mifos Initiative.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *    http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package io.mifos.customer.api.v1.domain;
-
-import io.mifos.core.lang.validation.constraints.ValidIdentifier;
-
-import javax.validation.Valid;
-import java.util.HashSet;
-import java.util.Set;
-
-public class PayrollDistribution {
-
-  @ValidIdentifier
-  private String mainAccountNumber;
-  @Valid
-  private Set<PayrollAllocation> payrollAllocations = new HashSet<>();
-  private String createdBy;
-  private String createdOn;
-  private String lastModifiedBy;
-  private String lastModifiedOn;
-
-  public PayrollDistribution() {
-    super();
-  }
-
-  public String getMainAccountNumber() {
-    return this.mainAccountNumber;
-  }
-
-  public void setMainAccountNumber(final String mainAccountNumber) {
-    this.mainAccountNumber = mainAccountNumber;
-  }
-
-  public Set<PayrollAllocation> getPayrollAllocations() {
-    return this.payrollAllocations;
-  }
-
-  public void setPayrollAllocations(final Set<PayrollAllocation> payrollAllocations) {
-    this.payrollAllocations = payrollAllocations;
-  }
-
-  public String getCreatedBy() {
-    return this.createdBy;
-  }
-
-  public void setCreatedBy(final String createdBy) {
-    this.createdBy = createdBy;
-  }
-
-  public String getCreatedOn() {
-    return this.createdOn;
-  }
-
-  public void setCreatedOn(final String createdOn) {
-    this.createdOn = createdOn;
-  }
-
-  public String getLastModifiedBy() {
-    return this.lastModifiedBy;
-  }
-
-  public void setLastModifiedBy(final String lastModifiedBy) {
-    this.lastModifiedBy = lastModifiedBy;
-  }
-
-  public String getLastModifiedOn() {
-    return this.lastModifiedOn;
-  }
-
-  public void setLastModifiedOn(final String lastModifiedOn) {
-    this.lastModifiedOn = lastModifiedOn;
-  }
-}
diff --git a/component-test/src/main/java/io/mifos/customer/TestPayrollDistribution.java b/component-test/src/main/java/io/mifos/customer/TestPayrollDistribution.java
deleted file mode 100644
index 89f6da4..0000000
--- a/component-test/src/main/java/io/mifos/customer/TestPayrollDistribution.java
+++ /dev/null
@@ -1,115 +0,0 @@
-/*
- * Copyright 2017 The Mifos Initiative.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *    http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package io.mifos.customer;
-
-import com.google.common.collect.Sets;
-import io.mifos.customer.api.v1.CustomerEventConstants;
-import io.mifos.customer.api.v1.domain.Customer;
-import io.mifos.customer.api.v1.domain.PayrollAllocation;
-import io.mifos.customer.api.v1.domain.PayrollDistribution;
-import io.mifos.customer.util.CustomerGenerator;
-import org.junit.Assert;
-import org.junit.Test;
-
-import java.math.BigDecimal;
-import java.util.Optional;
-
-public class TestPayrollDistribution extends AbstractCustomerTest {
-
-  @Test
-  public void shouldSetInitialPayrollDistribution() throws Exception {
-    final Customer customer = CustomerGenerator.createRandomCustomer();
-    this.customerManager.createCustomer(customer);
-
-    Assert.assertTrue(this.eventRecorder.wait(CustomerEventConstants.POST_CUSTOMER, customer.getIdentifier()));
-
-    final PayrollAllocation firstPayrollAllocation = new PayrollAllocation();
-    firstPayrollAllocation.setAccountNumber("08154712");
-    firstPayrollAllocation.setAmount(BigDecimal.valueOf(234.56D));
-
-    final PayrollAllocation secondPayrollAllocation = new PayrollAllocation();
-    secondPayrollAllocation.setAccountNumber("08154713");
-    secondPayrollAllocation.setAmount(BigDecimal.valueOf(5.00D));
-    secondPayrollAllocation.setProportional(Boolean.TRUE);
-
-    final PayrollDistribution payrollDistribution =  new PayrollDistribution();
-    payrollDistribution.setMainAccountNumber("08154711");
-    payrollDistribution.setPayrollAllocations(Sets.newHashSet(firstPayrollAllocation, secondPayrollAllocation));
-    this.customerManager.setPayrollDistribution(customer.getIdentifier(), payrollDistribution);
-
-    Assert.assertTrue(this.eventRecorder.wait(CustomerEventConstants.PUT_PAYROLL_DISTRIBUTION, customer.getIdentifier()));
-
-    final PayrollDistribution fetchedPayrollDistribution = this.customerManager.getPayrollDistribution(customer.getIdentifier());
-
-    this.compare(payrollDistribution, fetchedPayrollDistribution);
-  }
-
-  @Test
-  public void shouldUpdatePayrollDistribution() throws Exception {
-    final Customer customer = CustomerGenerator.createRandomCustomer();
-    this.customerManager.createCustomer(customer);
-
-    Assert.assertTrue(this.eventRecorder.wait(CustomerEventConstants.POST_CUSTOMER, customer.getIdentifier()));
-
-    final PayrollAllocation firstPayrollAllocation = new PayrollAllocation();
-    firstPayrollAllocation.setAccountNumber("08154712");
-    firstPayrollAllocation.setAmount(BigDecimal.valueOf(234.56D));
-
-    final PayrollAllocation secondPayrollAllocation = new PayrollAllocation();
-    secondPayrollAllocation.setAccountNumber("08154713");
-    secondPayrollAllocation.setAmount(BigDecimal.valueOf(5.00D));
-    secondPayrollAllocation.setProportional(Boolean.TRUE);
-
-    final PayrollDistribution payrollDistribution =  new PayrollDistribution();
-    payrollDistribution.setMainAccountNumber("08154711");
-    payrollDistribution.setPayrollAllocations(Sets.newHashSet(firstPayrollAllocation, secondPayrollAllocation));
-    this.customerManager.setPayrollDistribution(customer.getIdentifier(), payrollDistribution);
-
-    Assert.assertTrue(this.eventRecorder.wait(CustomerEventConstants.PUT_PAYROLL_DISTRIBUTION, customer.getIdentifier()));
-    this.eventRecorder.clear();
-
-    final PayrollDistribution fetchedPayrollDistribution = this.customerManager.getPayrollDistribution(customer.getIdentifier());
-
-    final PayrollAllocation replacedPayrollAllocation = new PayrollAllocation();
-    replacedPayrollAllocation.setAccountNumber("08154714");
-    replacedPayrollAllocation.setAmount(BigDecimal.valueOf(10.00D));
-    replacedPayrollAllocation.setProportional(Boolean.TRUE);
-
-    final PayrollDistribution replacedPayrollDistribution = new PayrollDistribution();
-    replacedPayrollDistribution.setMainAccountNumber(fetchedPayrollDistribution.getMainAccountNumber());
-    replacedPayrollDistribution.setPayrollAllocations(Sets.newHashSet(replacedPayrollAllocation));
-    this.customerManager.setPayrollDistribution(customer.getIdentifier(), replacedPayrollDistribution);
-
-    Assert.assertTrue(this.eventRecorder.wait(CustomerEventConstants.PUT_PAYROLL_DISTRIBUTION, customer.getIdentifier()));
-
-    final PayrollDistribution updatedPayrollDistribution = this.customerManager.getPayrollDistribution(customer.getIdentifier());
-
-    this.compare(replacedPayrollDistribution, updatedPayrollDistribution);
-
-    final Optional<PayrollAllocation> optionalPayrollAllocation =
-        updatedPayrollDistribution.getPayrollAllocations().stream().findFirst();
-
-    final PayrollAllocation payrollAllocation = optionalPayrollAllocation.orElseThrow(IllegalStateException::new);
-    Assert.assertEquals(replacedPayrollAllocation.getAccountNumber(), payrollAllocation.getAccountNumber());
-    Assert.assertTrue(replacedPayrollAllocation.getAmount().compareTo(payrollAllocation.getAmount()) == 0);
-    Assert.assertEquals(replacedPayrollAllocation.getProportional(), payrollAllocation.getProportional());
-  }
-
-  private void compare(final PayrollDistribution payrollDistribution, final PayrollDistribution fetchedPayrollDistribution) {
-    Assert.assertEquals(payrollDistribution.getMainAccountNumber(), fetchedPayrollDistribution.getMainAccountNumber());
-    Assert.assertTrue(payrollDistribution.getPayrollAllocations().size() == fetchedPayrollDistribution.getPayrollAllocations().size());
-  }
-}
diff --git a/component-test/src/main/java/io/mifos/customer/listener/CustomerEventListener.java b/component-test/src/main/java/io/mifos/customer/listener/CustomerEventListener.java
index 47ac0b5..5c1d2e1 100644
--- a/component-test/src/main/java/io/mifos/customer/listener/CustomerEventListener.java
+++ b/component-test/src/main/java/io/mifos/customer/listener/CustomerEventListener.java
@@ -178,13 +178,4 @@ public class CustomerEventListener {
                                final String payload) {
     this.eventRecorder.event(tenant, CustomerEventConstants.DELETE_PORTRAIT, payload, String.class);
   }
-
-  @JmsListener(
-      destination = CustomerEventConstants.DESTINATION,
-      selector = CustomerEventConstants.SELECTOR_PUT_PAYROLL_DISTRIBUTION
-  )
-  public void onSetPayrollDistribution(@Header(TenantHeaderFilter.TENANT_HEADER) final String tenant,
-                                       final String payload) {
-    this.eventRecorder.event(tenant, CustomerEventConstants.PUT_PAYROLL_DISTRIBUTION, payload, String.class);
-  }
 }
diff --git a/service/src/main/java/io/mifos/customer/service/internal/command/SetPayrollDistributionCommand.java b/service/src/main/java/io/mifos/customer/service/internal/command/SetPayrollDistributionCommand.java
deleted file mode 100644
index 9d456dd..0000000
--- a/service/src/main/java/io/mifos/customer/service/internal/command/SetPayrollDistributionCommand.java
+++ /dev/null
@@ -1,38 +0,0 @@
-/*
- * Copyright 2017 The Mifos Initiative.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *    http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package io.mifos.customer.service.internal.command;
-
-import io.mifos.customer.api.v1.domain.PayrollDistribution;
-
-public class SetPayrollDistributionCommand {
-  private final String customerIdentifier;
-  private final PayrollDistribution payrollDistribution;
-
-  public SetPayrollDistributionCommand(final String customerIdentifier,
-                                       final PayrollDistribution payrollDistribution) {
-    super();
-    this.customerIdentifier = customerIdentifier;
-    this.payrollDistribution = payrollDistribution;
-  }
-
-  public String customerIdentifier() {
-    return this.customerIdentifier;
-  }
-
-  public PayrollDistribution payrollDistribution() {
-    return this.payrollDistribution;
-  }
-}
diff --git a/service/src/main/java/io/mifos/customer/service/internal/command/handler/CustomerAggregate.java b/service/src/main/java/io/mifos/customer/service/internal/command/handler/CustomerAggregate.java
index b41ddfb..c20252c 100644
--- a/service/src/main/java/io/mifos/customer/service/internal/command/handler/CustomerAggregate.java
+++ b/service/src/main/java/io/mifos/customer/service/internal/command/handler/CustomerAggregate.java
@@ -24,7 +24,6 @@ import io.mifos.core.lang.ServiceException;
 import io.mifos.customer.api.v1.CustomerEventConstants;
 import io.mifos.customer.api.v1.domain.Command;
 import io.mifos.customer.api.v1.domain.Customer;
-import io.mifos.customer.api.v1.domain.PayrollDistribution;
 import io.mifos.customer.api.v1.events.ScanEvent;
 import io.mifos.customer.catalog.service.internal.repository.CatalogEntity;
 import io.mifos.customer.catalog.service.internal.repository.CatalogRepository;
@@ -44,7 +43,6 @@ import io.mifos.customer.service.internal.command.DeleteIdentificationCardScanCo
 import io.mifos.customer.service.internal.command.DeletePortraitCommand;
 import io.mifos.customer.service.internal.command.LockCustomerCommand;
 import io.mifos.customer.service.internal.command.ReopenCustomerCommand;
-import io.mifos.customer.service.internal.command.SetPayrollDistributionCommand;
 import io.mifos.customer.service.internal.command.UnlockCustomerCommand;
 import io.mifos.customer.service.internal.command.UpdateAddressCommand;
 import io.mifos.customer.service.internal.command.UpdateContactDetailsCommand;
@@ -69,10 +67,6 @@ import io.mifos.customer.service.internal.repository.IdentificationCardEntity;
 import io.mifos.customer.service.internal.repository.IdentificationCardRepository;
 import io.mifos.customer.service.internal.repository.IdentificationCardScanEntity;
 import io.mifos.customer.service.internal.repository.IdentificationCardScanRepository;
-import io.mifos.customer.service.internal.repository.PayrollAllocationEntity;
-import io.mifos.customer.service.internal.repository.PayrollAllocationRepository;
-import io.mifos.customer.service.internal.repository.PayrollDistributionEntity;
-import io.mifos.customer.service.internal.repository.PayrollDistributionRepository;
 import io.mifos.customer.service.internal.repository.PortraitEntity;
 import io.mifos.customer.service.internal.repository.PortraitRepository;
 import org.slf4j.Logger;
@@ -106,8 +100,6 @@ public class CustomerAggregate {
   private final FieldRepository fieldRepository;
   private final CommandRepository commandRepository;
   private final TaskAggregate taskAggregate;
-  private final PayrollDistributionRepository payrollDistributionRepository;
-  private final PayrollAllocationRepository payrollAllocationRepository;
 
   @Autowired
   public CustomerAggregate(@Qualifier(ServiceConstants.LOGGER_NAME) final Logger logger,
@@ -121,9 +113,7 @@ public class CustomerAggregate {
                            final CatalogRepository catalogRepository,
                            final FieldRepository fieldRepository,
                            final CommandRepository commandRepository,
-                           final TaskAggregate taskAggregate,
-                           final PayrollDistributionRepository payrollDistributionRepository,
-                           final PayrollAllocationRepository payrollAllocationRepository) {
+                           final TaskAggregate taskAggregate) {
     super();
     this.logger = logger;
     this.addressRepository = addressRepository;
@@ -137,8 +127,6 @@ public class CustomerAggregate {
     this.fieldRepository = fieldRepository;
     this.commandRepository = commandRepository;
     this.taskAggregate = taskAggregate;
-    this.payrollDistributionRepository = payrollDistributionRepository;
-    this.payrollAllocationRepository = payrollAllocationRepository;
   }
 
   @Transactional
@@ -548,55 +536,6 @@ public class CustomerAggregate {
     return deletePortraitCommand.identifier();
   }
 
-  @Transactional
-  @CommandHandler
-  @EventEmitter(selectorName = CustomerEventConstants.SELECTOR_NAME, selectorValue = CustomerEventConstants.PUT_PAYROLL_DISTRIBUTION)
-  public String setPayrollDistribution(final SetPayrollDistributionCommand setPayrollDistributionCommand) {
-    final CustomerEntity customerEntity =
-        this.customerRepository.findByIdentifier(setPayrollDistributionCommand.customerIdentifier());
-
-    final LocalDateTime now = LocalDateTime.now(Clock.systemUTC());
-
-    final PayrollDistributionEntity payrollDistributionEntity =
-        this.payrollDistributionRepository.findByCustomer(customerEntity).orElseGet(() -> {
-          final PayrollDistributionEntity newPayrollDistribution = new PayrollDistributionEntity();
-          newPayrollDistribution.setCustomer(customerEntity);
-          newPayrollDistribution.setCreatedBy(UserContextHolder.checkedGetUser());
-          newPayrollDistribution.setCreatedOn(now);
-          return newPayrollDistribution;
-        });
-
-    if (payrollDistributionEntity.getId() != null) {
-      this.payrollAllocationRepository.deleteByPayrollDistribution(payrollDistributionEntity);
-      this.payrollAllocationRepository.flush();
-
-      payrollDistributionEntity.setLastModifiedBy(UserContextHolder.checkedGetUser());
-      payrollDistributionEntity.setLastModifiedOn(now);
-      payrollDistributionEntity.setPayrollAllocationEntities(null);
-    }
-
-    final PayrollDistribution payrollDistribution = setPayrollDistributionCommand.payrollDistribution();
-    payrollDistributionEntity.setMainAccountNumber(payrollDistribution.getMainAccountNumber());
-
-    final PayrollDistributionEntity savedPayrollDistributionEntity =
-        this.payrollDistributionRepository.save(payrollDistributionEntity);
-
-    this.payrollAllocationRepository.save(payrollDistribution.getPayrollAllocations()
-        .stream()
-        .map(payrollAllocation -> {
-          final PayrollAllocationEntity payrollAllocationEntity = new PayrollAllocationEntity();
-          payrollAllocationEntity.setPayrollDistribution(savedPayrollDistributionEntity);
-          payrollAllocationEntity.setAccountNumber(payrollAllocation.getAccountNumber());
-          payrollAllocationEntity.setAmount(payrollAllocation.getAmount());
-          payrollAllocationEntity.setProportional(payrollAllocation.getProportional());
-          return payrollAllocationEntity;
-        })
-        .collect(Collectors.toList())
-    );
-
-    return setPayrollDistributionCommand.customerIdentifier();
-  }
-
   private void setCustomValues(final Customer customer, final CustomerEntity savedCustomerEntity) {
     this.fieldValueRepository.save(
         customer.getCustomValues()
diff --git a/service/src/main/java/io/mifos/customer/service/internal/mapper/PayrollDistributionMapper.java b/service/src/main/java/io/mifos/customer/service/internal/mapper/PayrollDistributionMapper.java
deleted file mode 100644
index c7cc514..0000000
--- a/service/src/main/java/io/mifos/customer/service/internal/mapper/PayrollDistributionMapper.java
+++ /dev/null
@@ -1,55 +0,0 @@
-/*
- * Copyright 2017 The Mifos Initiative.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *    http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package io.mifos.customer.service.internal.mapper;
-
-import io.mifos.core.lang.DateConverter;
-import io.mifos.customer.api.v1.domain.PayrollAllocation;
-import io.mifos.customer.api.v1.domain.PayrollDistribution;
-import io.mifos.customer.service.internal.repository.PayrollDistributionEntity;
-
-import java.util.stream.Collectors;
-
-public class PayrollDistributionMapper {
-
-  private PayrollDistributionMapper() {
-    super();
-  }
-
-  public static PayrollDistribution map(final PayrollDistributionEntity payrollDistributionEntity) {
-    final PayrollDistribution payrollDistribution = new PayrollDistribution();
-    payrollDistribution.setMainAccountNumber(payrollDistributionEntity.getMainAccountNumber());
-    payrollDistribution.setCreatedBy(payrollDistributionEntity.getCreatedBy());
-    payrollDistribution.setCreatedOn(DateConverter.toIsoString(payrollDistributionEntity.getCreatedOn()));
-    if (payrollDistributionEntity.getLastModifiedBy() != null) {
-      payrollDistribution.setLastModifiedBy(payrollDistributionEntity.getLastModifiedBy());
-      payrollDistribution.setLastModifiedOn(DateConverter.toIsoString(payrollDistributionEntity.getLastModifiedOn()));
-    }
-
-    if (payrollDistributionEntity.getPayrollAllocationEntities() != null) {
-      payrollDistribution.setPayrollAllocations(
-          payrollDistributionEntity.getPayrollAllocationEntities().stream().map(payrollAllocationEntity -> {
-            final PayrollAllocation payrollAllocation = new PayrollAllocation();
-            payrollAllocation.setAccountNumber(payrollAllocationEntity.getAccountNumber());
-            payrollAllocation.setAmount(payrollAllocationEntity.getAmount());
-            payrollAllocation.setProportional(payrollAllocationEntity.getProportional());
-            return payrollAllocation;
-          }).collect(Collectors.toSet())
-      );
-    }
-
-    return payrollDistribution;
-  }
-}
diff --git a/service/src/main/java/io/mifos/customer/service/internal/repository/PayrollAllocationEntity.java b/service/src/main/java/io/mifos/customer/service/internal/repository/PayrollAllocationEntity.java
deleted file mode 100644
index a08907c..0000000
--- a/service/src/main/java/io/mifos/customer/service/internal/repository/PayrollAllocationEntity.java
+++ /dev/null
@@ -1,89 +0,0 @@
-/*
- * Copyright 2017 The Mifos Initiative.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *    http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package io.mifos.customer.service.internal.repository;
-
-import javax.persistence.Column;
-import javax.persistence.Entity;
-import javax.persistence.GeneratedValue;
-import javax.persistence.GenerationType;
-import javax.persistence.Id;
-import javax.persistence.JoinColumn;
-import javax.persistence.ManyToOne;
-import javax.persistence.Table;
-import java.math.BigDecimal;
-
-@Entity
-@Table(name = "maat_payroll_allocations")
-public class PayrollAllocationEntity {
-
-  @Id
-  @GeneratedValue(strategy = GenerationType.IDENTITY)
-  @Column(name = "id", nullable = false)
-  private Long id;
-  @ManyToOne
-  @JoinColumn(name = "payroll_distribution_id", nullable = false)
-  private PayrollDistributionEntity payrollDistribution;
-  @Column(name = "account_number", nullable = false, length = 34)
-  private String accountNumber;
-  @Column(name = "amount", nullable = false, precision = 15, scale = 5)
-  private BigDecimal amount;
-  @Column(name = "proportional", nullable = false)
-  private Boolean proportional = Boolean.FALSE;
-
-  public PayrollAllocationEntity() {
-    super();
-  }
-
-  public Long getId() {
-    return this.id;
-  }
-
-  public void setId(final Long id) {
-    this.id = id;
-  }
-
-  public PayrollDistributionEntity getPayrollDistribution() {
-    return this.payrollDistribution;
-  }
-
-  public void setPayrollDistribution(final PayrollDistributionEntity payrollDistribution) {
-    this.payrollDistribution = payrollDistribution;
-  }
-
-  public String getAccountNumber() {
-    return this.accountNumber;
-  }
-
-  public void setAccountNumber(final String accountNumber) {
-    this.accountNumber = accountNumber;
-  }
-
-  public BigDecimal getAmount() {
-    return this.amount;
-  }
-
-  public void setAmount(final BigDecimal amount) {
-    this.amount = amount;
-  }
-
-  public Boolean getProportional() {
-    return this.proportional;
-  }
-
-  public void setProportional(final Boolean proportional) {
-    this.proportional = proportional;
-  }
-}
diff --git a/service/src/main/java/io/mifos/customer/service/internal/repository/PayrollAllocationRepository.java b/service/src/main/java/io/mifos/customer/service/internal/repository/PayrollAllocationRepository.java
deleted file mode 100644
index 9d418f3..0000000
--- a/service/src/main/java/io/mifos/customer/service/internal/repository/PayrollAllocationRepository.java
+++ /dev/null
@@ -1,22 +0,0 @@
-/*
- * Copyright 2017 The Mifos Initiative.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *    http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package io.mifos.customer.service.internal.repository;
-
-import org.springframework.data.jpa.repository.JpaRepository;
-
-public interface PayrollAllocationRepository extends JpaRepository<PayrollAllocationEntity, Long> {
-  void deleteByPayrollDistribution(final PayrollDistributionEntity payrollDistributionEntity);
-}
diff --git a/service/src/main/java/io/mifos/customer/service/internal/repository/PayrollDistributionEntity.java b/service/src/main/java/io/mifos/customer/service/internal/repository/PayrollDistributionEntity.java
deleted file mode 100644
index 5cb62b9..0000000
--- a/service/src/main/java/io/mifos/customer/service/internal/repository/PayrollDistributionEntity.java
+++ /dev/null
@@ -1,128 +0,0 @@
-/*
- * Copyright 2017 The Mifos Initiative.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *    http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package io.mifos.customer.service.internal.repository;
-
-import io.mifos.core.mariadb.util.LocalDateTimeConverter;
-
-import javax.persistence.CascadeType;
-import javax.persistence.Column;
-import javax.persistence.Convert;
-import javax.persistence.Entity;
-import javax.persistence.FetchType;
-import javax.persistence.GeneratedValue;
-import javax.persistence.GenerationType;
-import javax.persistence.Id;
-import javax.persistence.JoinColumn;
-import javax.persistence.OneToMany;
-import javax.persistence.OneToOne;
-import javax.persistence.Table;
-import java.time.LocalDateTime;
-import java.util.List;
-
-@Entity
-@Table(name = "maat_payroll_distributions")
-public class PayrollDistributionEntity {
-
-  @Id
-  @GeneratedValue(strategy = GenerationType.IDENTITY)
-  @Column(name = "id", nullable = false)
-  private Long id;
-  @OneToOne(cascade = CascadeType.ALL)
-  @JoinColumn(name = "customer_id", nullable = false)
-  private CustomerEntity customer;
-  @Column(name = "main_account_number", nullable = false, length = 34)
-  private String mainAccountNumber;
-  @OneToMany(fetch = FetchType.EAGER, mappedBy = "payrollDistribution")
-  private List<PayrollAllocationEntity> payrollAllocationEntities;
-  @Column(name = "created_by")
-  private String createdBy;
-  @Column(name = "created_on")
-  @Convert(converter = LocalDateTimeConverter.class)
-  private LocalDateTime createdOn;
-  @Column(name = "last_modified_by")
-  private String lastModifiedBy;
-  @Column(name = "last_modified_on")
-  @Convert(converter = LocalDateTimeConverter.class)
-  private LocalDateTime lastModifiedOn;
-
-  public PayrollDistributionEntity() {
-    super();
-  }
-
-  public Long getId() {
-    return this.id;
-  }
-
-  public void setId(final Long id) {
-    this.id = id;
-  }
-
-  public CustomerEntity getCustomer() {
-    return this.customer;
-  }
-
-  public void setCustomer(final CustomerEntity customer) {
-    this.customer = customer;
-  }
-
-  public String getMainAccountNumber() {
-    return this.mainAccountNumber;
-  }
-
-  public void setMainAccountNumber(final String mainAccountNumber) {
-    this.mainAccountNumber = mainAccountNumber;
-  }
-
-  public List<PayrollAllocationEntity> getPayrollAllocationEntities() {
-    return this.payrollAllocationEntities;
-  }
-
-  public void setPayrollAllocationEntities(final List<PayrollAllocationEntity> payrollAllocationEntities) {
-    this.payrollAllocationEntities = payrollAllocationEntities;
-  }
-
-  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/customer/service/internal/repository/PayrollDistributionRepository.java b/service/src/main/java/io/mifos/customer/service/internal/repository/PayrollDistributionRepository.java
deleted file mode 100644
index f332432..0000000
--- a/service/src/main/java/io/mifos/customer/service/internal/repository/PayrollDistributionRepository.java
+++ /dev/null
@@ -1,24 +0,0 @@
-/*
- * Copyright 2017 The Mifos Initiative.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *    http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package io.mifos.customer.service.internal.repository;
-
-import org.springframework.data.jpa.repository.JpaRepository;
-
-import java.util.Optional;
-
-public interface PayrollDistributionRepository extends JpaRepository<PayrollDistributionEntity, Long> {
-  Optional<PayrollDistributionEntity> findByCustomer(final CustomerEntity customerEntity);
-}
diff --git a/service/src/main/java/io/mifos/customer/service/internal/service/CustomerService.java b/service/src/main/java/io/mifos/customer/service/internal/service/CustomerService.java
index 3901f0d..cc886aa 100644
--- a/service/src/main/java/io/mifos/customer/service/internal/service/CustomerService.java
+++ b/service/src/main/java/io/mifos/customer/service/internal/service/CustomerService.java
@@ -20,7 +20,6 @@ import io.mifos.customer.api.v1.domain.Customer;
 import io.mifos.customer.api.v1.domain.CustomerPage;
 import io.mifos.customer.api.v1.domain.IdentificationCard;
 import io.mifos.customer.api.v1.domain.IdentificationCardScan;
-import io.mifos.customer.api.v1.domain.PayrollDistribution;
 import io.mifos.customer.api.v1.domain.ProcessStep;
 import io.mifos.customer.api.v1.domain.TaskDefinition;
 import io.mifos.customer.catalog.api.v1.domain.Value;
@@ -34,7 +33,6 @@ import io.mifos.customer.service.internal.mapper.ContactDetailMapper;
 import io.mifos.customer.service.internal.mapper.CustomerMapper;
 import io.mifos.customer.service.internal.mapper.IdentificationCardMapper;
 import io.mifos.customer.service.internal.mapper.IdentificationCardScanMapper;
-import io.mifos.customer.service.internal.mapper.PayrollDistributionMapper;
 import io.mifos.customer.service.internal.mapper.TaskDefinitionMapper;
 import io.mifos.customer.service.internal.repository.CommandEntity;
 import io.mifos.customer.service.internal.repository.CommandRepository;
@@ -46,7 +44,6 @@ import io.mifos.customer.service.internal.repository.IdentificationCardEntity;
 import io.mifos.customer.service.internal.repository.IdentificationCardRepository;
 import io.mifos.customer.service.internal.repository.IdentificationCardScanEntity;
 import io.mifos.customer.service.internal.repository.IdentificationCardScanRepository;
-import io.mifos.customer.service.internal.repository.PayrollDistributionRepository;
 import io.mifos.customer.service.internal.repository.PortraitEntity;
 import io.mifos.customer.service.internal.repository.PortraitRepository;
 import io.mifos.customer.service.internal.repository.TaskDefinitionRepository;
@@ -77,7 +74,6 @@ public class CustomerService {
   private final CommandRepository commandRepository;
   private final TaskDefinitionRepository taskDefinitionRepository;
   private final TaskInstanceRepository taskInstanceRepository;
-  private final PayrollDistributionRepository payrollDistributionRepository;
 
   @Autowired
   public CustomerService(@Qualifier(ServiceConstants.LOGGER_NAME) final Logger logger,
@@ -89,8 +85,7 @@ public class CustomerService {
                          final FieldValueRepository fieldValueRepository,
                          final CommandRepository commandRepository,
                          final TaskDefinitionRepository taskDefinitionRepository,
-                         final TaskInstanceRepository taskInstanceRepository,
-                         final PayrollDistributionRepository payrollDistributionRepository) {
+                         final TaskInstanceRepository taskInstanceRepository) {
     super();
     this.logger = logger;
     this.customerRepository = customerRepository;
@@ -102,7 +97,6 @@ public class CustomerService {
     this.commandRepository = commandRepository;
     this.taskDefinitionRepository = taskDefinitionRepository;
     this.taskInstanceRepository = taskInstanceRepository;
-    this.payrollDistributionRepository = payrollDistributionRepository;
   }
 
   public Boolean customerExists(final String identifier) {
@@ -294,9 +288,4 @@ public class CustomerService {
 
     return processStep;
   }
-
-  public Optional<PayrollDistribution> getPayrollDistribution(final String customerIdentifier) {
-    final CustomerEntity customerEntity = this.customerRepository.findByIdentifier(customerIdentifier);
-    return this.payrollDistributionRepository.findByCustomer(customerEntity).map(PayrollDistributionMapper::map);
-  }
 }
diff --git a/service/src/main/java/io/mifos/customer/service/rest/controller/CustomerRestController.java b/service/src/main/java/io/mifos/customer/service/rest/controller/CustomerRestController.java
index d7ad048..c3c6e94 100644
--- a/service/src/main/java/io/mifos/customer/service/rest/controller/CustomerRestController.java
+++ b/service/src/main/java/io/mifos/customer/service/rest/controller/CustomerRestController.java
@@ -29,7 +29,6 @@ import io.mifos.customer.api.v1.domain.Customer;
 import io.mifos.customer.api.v1.domain.CustomerPage;
 import io.mifos.customer.api.v1.domain.IdentificationCard;
 import io.mifos.customer.api.v1.domain.IdentificationCardScan;
-import io.mifos.customer.api.v1.domain.PayrollDistribution;
 import io.mifos.customer.api.v1.domain.ProcessStep;
 import io.mifos.customer.api.v1.domain.TaskDefinition;
 import io.mifos.customer.catalog.service.internal.service.FieldValueValidator;
@@ -49,7 +48,6 @@ import io.mifos.customer.service.internal.command.ExecuteTaskForCustomerCommand;
 import io.mifos.customer.service.internal.command.InitializeServiceCommand;
 import io.mifos.customer.service.internal.command.LockCustomerCommand;
 import io.mifos.customer.service.internal.command.ReopenCustomerCommand;
-import io.mifos.customer.service.internal.command.SetPayrollDistributionCommand;
 import io.mifos.customer.service.internal.command.UnlockCustomerCommand;
 import io.mifos.customer.service.internal.command.UpdateAddressCommand;
 import io.mifos.customer.service.internal.command.UpdateContactDetailsCommand;
@@ -746,38 +744,6 @@ public class CustomerRestController {
     return ResponseEntity.ok(this.customerService.getProcessSteps(customerIdentifier));
   }
 
-  @Permittable(value = AcceptedTokenType.TENANT, groupId = PermittableGroupIds.CUSTOMER)
-  @RequestMapping(
-      value = "/customers/{identifier}/payroll",
-      method = RequestMethod.PUT,
-      produces = MediaType.APPLICATION_JSON_VALUE,
-      consumes = MediaType.APPLICATION_JSON_VALUE
-  )
-  public
-  @ResponseBody
-  ResponseEntity<Void> setPayrollDistribution(@PathVariable(value = "identifier") final String customerIdentifier,
-                                              @RequestBody @Valid final PayrollDistribution payrollDistribution) {
-    this.throwIfCustomerNotExists(customerIdentifier);
-    this.commandGateway.process(new SetPayrollDistributionCommand(customerIdentifier, payrollDistribution));
-    return ResponseEntity.accepted().build();
-  }
-
-  @Permittable(value = AcceptedTokenType.TENANT, groupId = PermittableGroupIds.CUSTOMER)
-  @RequestMapping(
-      value = "/customers/{identifier}/payroll",
-      method = RequestMethod.GET,
-      produces = MediaType.APPLICATION_JSON_VALUE,
-      consumes = MediaType.ALL_VALUE
-  )
-  public
-  @ResponseBody
-  ResponseEntity<PayrollDistribution> getPayrollDistribution(@PathVariable(value = "identifier") final String customerIdentifier) {
-    this.throwIfCustomerNotExists(customerIdentifier);
-    return ResponseEntity.ok(
-        this.customerService.getPayrollDistribution(customerIdentifier).orElse(null)
-    );
-  }
-
   private Pageable createPageRequest(final Integer pageIndex, final Integer size, final String sortColumn, final String sortDirection) {
     final Integer pageIndexToUse = pageIndex != null ? pageIndex : 0;
     final Integer sizeToUse = size != null ? size : 20;
diff --git a/service/src/main/resources/db/migrations/mariadb/V6__drop_pyroll_distributions.sql b/service/src/main/resources/db/migrations/mariadb/V6__drop_pyroll_distributions.sql
new file mode 100644
index 0000000..78c9690
--- /dev/null
+++ b/service/src/main/resources/db/migrations/mariadb/V6__drop_pyroll_distributions.sql
@@ -0,0 +1,18 @@
+--
+-- Copyright 2017 The Mifos Initiative.
+--
+-- Licensed under the Apache License, Version 2.0 (the "License");
+-- you may not use this file except in compliance with the License.
+-- You may obtain a copy of the License at
+--
+--    http://www.apache.org/licenses/LICENSE-2.0
+--
+-- Unless required by applicable law or agreed to in writing, software
+-- distributed under the License is distributed on an "AS IS" BASIS,
+-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+-- See the License for the specific language governing permissions and
+-- limitations under the License.
+--
+
+DROP TABLE maat_payroll_allocations;
+DROP TABLE maat_payroll_distributions;
\ No newline at end of file

-- 
To stop receiving notification emails like this one, please contact
myrle@apache.org.

[fineract-cn-customer] 44/46: Merge pull request #18 from markusgeiss/develop

Posted by my...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

myrle pushed a commit to branch develop
in repository https://gitbox.apache.org/repos/asf/fineract-cn-customer.git

commit 47897f008efc35a8f610841039c4fe178df74558
Merge: b0e57a2 be60ba6
Author: Markus Geiss <ma...@kuelap.io>
AuthorDate: Sun Oct 22 07:52:45 2017 +0200

    Merge pull request #18 from markusgeiss/develop
    
    added application date for customer

 .../io/mifos/customer/api/v1/domain/Customer.java     |  9 +++++++++
 .../src/main/java/io/mifos/customer/TestCustomer.java |  7 +++++++
 .../internal/command/handler/CustomerAggregate.java   |  3 +++
 .../service/internal/mapper/CustomerMapper.java       |  6 ++++++
 .../service/internal/repository/CustomerEntity.java   | 13 +++++++++++++
 .../mariadb/V9__add_application_date_to_customer.sql  | 19 +++++++++++++++++++
 6 files changed, 57 insertions(+)

-- 
To stop receiving notification emails like this one, please contact
myrle@apache.org.

[fineract-cn-customer] 14/46: Merge pull request #4 from markusgeiss/develop

Posted by my...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

myrle pushed a commit to branch develop
in repository https://gitbox.apache.org/repos/asf/fineract-cn-customer.git

commit 3ed6029528c2963ff4572dc874705a07d135520b
Merge: fdabad4 136016e
Author: Markus Geiss <ma...@kuelap.io>
AuthorDate: Wed Sep 6 12:16:30 2017 +0200

    Merge pull request #4 from markusgeiss/develop
    
    replaced delimiter in update task

 .../customer/service/internal/command/handler/TaskAggregate.java     | 5 ++---
 1 file changed, 2 insertions(+), 3 deletions(-)

-- 
To stop receiving notification emails like this one, please contact
myrle@apache.org.