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:23:58 UTC

[fineract-cn-office] branch develop created (now 449ca5b)

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-office.git.


      at 449ca5b  removing duplicate resources from build

This branch includes the following new commits:

     new a632226  Initial Commit
     new 102fb07  renamed API class
     new dbf7472  enhanced logging for demo server usage
     new a952ee3  Merge pull request #1 from markusgeiss/develop
     new fd66142  Change postal code to have NULL constraint
     new de1b1ac  Add check if office has employee before delete
     new 346a8a1  Add check if duplicate identifier is used when creating a branch office
     new a17e82e  Merge remote-tracking branch 'remotes/upstream/develop' into develop
     new f3f662f  Merge pull request #2 from crain/develop
     new 1d744b0  edited logback
     new 29d279f  Merge pull request #3 from mgeiss/develop
     new 17b1466  added path configurer to allow dots in url
     new a87f825  Merge pull request #4 from mgeiss/develop
     new 449ca5b  removing duplicate resources from build

The 14 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.


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

[fineract-cn-office] 07/14: Add check if duplicate identifier is used when creating a branch office

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-office.git

commit 346a8a1d68bf63aa42655daff56524e8a11da52e
Author: Mark <ma...@gmail.com>
AuthorDate: Wed Apr 12 17:31:53 2017 +0200

    Add check if duplicate identifier is used when creating a branch office
---
 .../mifos/office/api/v1/client/OrganizationManager.java  |  5 ++++-
 .../src/main/java/io/mifos/office/TestOffice.java        | 16 ++++++++++++++++
 .../office/rest/controller/OfficeRestController.java     |  8 ++++++++
 3 files changed, 28 insertions(+), 1 deletion(-)

diff --git a/api/src/main/java/io/mifos/office/api/v1/client/OrganizationManager.java b/api/src/main/java/io/mifos/office/api/v1/client/OrganizationManager.java
index 3ef36b2..27831ac 100644
--- a/api/src/main/java/io/mifos/office/api/v1/client/OrganizationManager.java
+++ b/api/src/main/java/io/mifos/office/api/v1/client/OrganizationManager.java
@@ -90,7 +90,10 @@ public interface OrganizationManager {
       consumes = MediaType.APPLICATION_JSON_VALUE,
       produces = MediaType.APPLICATION_JSON_VALUE
   )
-  @ThrowsException(status = HttpStatus.NOT_FOUND, exception = NotFoundException.class)
+  @ThrowsExceptions({
+          @ThrowsException(status = HttpStatus.NOT_FOUND, exception = NotFoundException.class),
+          @ThrowsException(status = HttpStatus.CONFLICT, exception = AlreadyExistsException.class)
+  })
   void addBranch(@PathVariable("identifier") final String identifier, @RequestBody final Office office);
 
   @RequestMapping(
diff --git a/component-test/src/main/java/io/mifos/office/TestOffice.java b/component-test/src/main/java/io/mifos/office/TestOffice.java
index 6399eef..97f1b66 100644
--- a/component-test/src/main/java/io/mifos/office/TestOffice.java
+++ b/component-test/src/main/java/io/mifos/office/TestOffice.java
@@ -202,6 +202,22 @@ public class TestOffice {
   }
 
   @Test
+  public void shouldNotAddBranchDuplicate() throws Exception {
+    final Office office = OfficeFactory.createRandomOffice();
+    this.organizationManager.createOffice(office);
+    this.eventRecorder.wait(EventConstants.OPERATION_POST_OFFICE, office.getIdentifier());
+    try {
+      this.organizationManager.addBranch(office.getIdentifier(), office);
+      Assert.fail();
+    } catch (final AlreadyExistsException ex) {
+      // do nothing, expected
+    }
+
+    this.organizationManager.deleteOffice(office.getIdentifier());
+    this.eventRecorder.wait(EventConstants.OPERATION_DELETE_OFFICE, office.getIdentifier());
+  }
+
+  @Test
   public void shouldSetAddressOfOffice() throws Exception {
     final Office office = OfficeFactory.createRandomOffice();
     this.organizationManager.createOffice(office);
diff --git a/service/src/main/java/io/mifos/office/rest/controller/OfficeRestController.java b/service/src/main/java/io/mifos/office/rest/controller/OfficeRestController.java
index ab30f63..70162fe 100644
--- a/service/src/main/java/io/mifos/office/rest/controller/OfficeRestController.java
+++ b/service/src/main/java/io/mifos/office/rest/controller/OfficeRestController.java
@@ -173,6 +173,14 @@ public class OfficeRestController {
       throw ServiceException.notFound("Parent office {0} not found.", identifier);
     }
 
+    if (office == null) {
+      throw ServiceException.badRequest("An office must be given.");
+    }
+
+    if (this.officeService.officeExists(office.getIdentifier())) {
+      throw ServiceException.conflict("Office {0} already exists.", office.getIdentifier());
+    }
+
     this.commandGateway.process(new AddBranchCommand(identifier, office));
     return ResponseEntity.accepted().build();
   }

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

[fineract-cn-office] 14/14: removing duplicate resources from build

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-office.git

commit 449ca5b939de9f047f16040e7c1627adc5c41ce7
Author: Myrle Krantz <my...@apache.org>
AuthorDate: Thu Jul 13 00:12:38 2017 +0200

    removing duplicate resources from build
---
 shared.gradle | 1 +
 1 file changed, 1 insertion(+)

diff --git a/shared.gradle b/shared.gradle
index caa8c64..337c6f4 100644
--- a/shared.gradle
+++ b/shared.gradle
@@ -57,6 +57,7 @@ dependencies {
 
 jar {
     from sourceSets.main.allSource
+    duplicatesStrategy = DuplicatesStrategy.EXCLUDE
 }
 
 license {

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

[fineract-cn-office] 08/14: Merge remote-tracking branch 'remotes/upstream/develop' 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-office.git

commit a17e82e4340f33245e0c12df9e279d288ad6b0b6
Merge: 346a8a1 a952ee3
Author: Mark <ma...@gmail.com>
AuthorDate: Wed Apr 12 17:33:25 2017 +0200

    Merge remote-tracking branch 'remotes/upstream/develop' into develop

 service/src/main/resources/logback.xml | 25 ++++++++++++++++++++-----
 1 file changed, 20 insertions(+), 5 deletions(-)

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

[fineract-cn-office] 06/14: Add check if office has employee before delete

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-office.git

commit de1b1acfa022ee8c721749e47b437bf45d27603e
Author: Mark <ma...@gmail.com>
AuthorDate: Wed Apr 12 17:21:38 2017 +0200

    Add check if office has employee before delete
---
 .../src/main/java/io/mifos/office/TestOffice.java  | 47 ++++++++++++++++++++--
 .../internal/repository/EmployeeRepository.java    |  3 ++
 .../office/internal/service/OfficeService.java     | 22 +++++-----
 .../rest/controller/OfficeRestController.java      | 10 ++++-
 4 files changed, 66 insertions(+), 16 deletions(-)

diff --git a/component-test/src/main/java/io/mifos/office/TestOffice.java b/component-test/src/main/java/io/mifos/office/TestOffice.java
index c28d48d..6399eef 100644
--- a/component-test/src/main/java/io/mifos/office/TestOffice.java
+++ b/component-test/src/main/java/io/mifos/office/TestOffice.java
@@ -24,15 +24,14 @@ import io.mifos.core.test.fixture.mariadb.MariaDBInitializer;
 import io.mifos.core.test.listener.EnableEventRecording;
 import io.mifos.core.test.listener.EventRecorder;
 import io.mifos.office.api.v1.EventConstants;
-import io.mifos.office.api.v1.client.AlreadyExistsException;
-import io.mifos.office.api.v1.client.BadRequestException;
-import io.mifos.office.api.v1.client.OrganizationManager;
-import io.mifos.office.api.v1.client.NotFoundException;
+import io.mifos.office.api.v1.client.*;
 import io.mifos.office.api.v1.domain.Address;
+import io.mifos.office.api.v1.domain.Employee;
 import io.mifos.office.api.v1.domain.Office;
 import io.mifos.office.api.v1.domain.OfficePage;
 import io.mifos.office.rest.config.OfficeRestConfiguration;
 import io.mifos.office.util.AddressFactory;
+import io.mifos.office.util.EmployeeFactory;
 import io.mifos.office.util.OfficeFactory;
 import org.apache.commons.lang3.RandomStringUtils;
 import org.junit.*;
@@ -281,6 +280,46 @@ public class TestOffice {
     Assert.assertEquals(parent.getIdentifier(), savedBranch.getParentIdentifier());
   }
 
+  @Test(expected = NotFoundException.class)
+  public void shouldDeleteOffice() throws Exception{
+    final Office office = OfficeFactory.createRandomOffice();
+    this.organizationManager.createOffice(office);
+    this.eventRecorder.wait(EventConstants.OPERATION_POST_OFFICE, office.getIdentifier());
+
+    this.organizationManager.deleteOffice(office.getIdentifier());
+
+    this.eventRecorder.wait(EventConstants.OPERATION_DELETE_OFFICE, office.getIdentifier());
+
+    this.organizationManager.findOfficeByIdentifier(office.getIdentifier());
+  }
+
+  @Test(expected = ChildrenExistException.class)
+  public void shouldNotDeleteOfficeWithBranches() throws Exception{
+    final Office parent = OfficeFactory.createRandomOffice();
+    this.organizationManager.createOffice(parent);
+    this.eventRecorder.wait(EventConstants.OPERATION_POST_OFFICE, parent.getIdentifier());
+
+    final Office branch = OfficeFactory.createRandomOffice();
+    this.organizationManager.addBranch(parent.getIdentifier(), branch);
+    this.eventRecorder.wait(EventConstants.OPERATION_POST_OFFICE, branch.getIdentifier());
+
+    this.organizationManager.deleteOffice(parent.getIdentifier());
+  }
+
+  @Test(expected = ChildrenExistException.class)
+  public void shouldNotDeleteOfficeWithEmployees() throws Exception{
+    final Office office = OfficeFactory.createRandomOffice();
+    this.organizationManager.createOffice(office);
+    this.eventRecorder.wait(EventConstants.OPERATION_POST_OFFICE, office.getIdentifier());
+
+    final Employee employee = EmployeeFactory.createRandomEmployee();
+    employee.setAssignedOffice(office.getIdentifier());
+    this.organizationManager.createEmployee(employee);
+    this.eventRecorder.wait(EventConstants.OPERATION_POST_EMPLOYEE, employee.getIdentifier());
+
+    this.organizationManager.deleteOffice(office.getIdentifier());
+  }
+
   @Configuration
   @ComponentScan(
       basePackages = "io.mifos.office.listener"
diff --git a/service/src/main/java/io/mifos/office/internal/repository/EmployeeRepository.java b/service/src/main/java/io/mifos/office/internal/repository/EmployeeRepository.java
index 1b9d687..845cf74 100644
--- a/service/src/main/java/io/mifos/office/internal/repository/EmployeeRepository.java
+++ b/service/src/main/java/io/mifos/office/internal/repository/EmployeeRepository.java
@@ -33,4 +33,7 @@ public interface EmployeeRepository extends JpaRepository<EmployeeEntity, Long>
   Page<EmployeeEntity> findByAssignedOffice(final OfficeEntity assignedOffice, final Pageable pageable);
 
   Page<EmployeeEntity> findByIdentifierContaining(String term, Pageable pageRequest);
+
+  @Query("SELECT CASE WHEN COUNT(e) > 0 THEN 'true' ELSE 'false' END FROM EmployeeEntity e WHERE e.assignedOffice = :office")
+  Boolean existsByAssignedOffice(@Param("office") final OfficeEntity assignedOffice);
 }
diff --git a/service/src/main/java/io/mifos/office/internal/service/OfficeService.java b/service/src/main/java/io/mifos/office/internal/service/OfficeService.java
index b2ce26b..e8b29c2 100644
--- a/service/src/main/java/io/mifos/office/internal/service/OfficeService.java
+++ b/service/src/main/java/io/mifos/office/internal/service/OfficeService.java
@@ -22,10 +22,7 @@ import io.mifos.office.api.v1.domain.Office;
 import io.mifos.office.api.v1.domain.OfficePage;
 import io.mifos.office.internal.mapper.AddressMapper;
 import io.mifos.office.internal.mapper.OfficeMapper;
-import io.mifos.office.internal.repository.AddressEntity;
-import io.mifos.office.internal.repository.AddressRepository;
-import io.mifos.office.internal.repository.OfficeEntity;
-import io.mifos.office.internal.repository.OfficeRepository;
+import io.mifos.office.internal.repository.*;
 import org.slf4j.Logger;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.beans.factory.annotation.Qualifier;
@@ -44,15 +41,18 @@ public class OfficeService {
   private final Logger logger;
   private final OfficeRepository officeRepository;
   private final AddressRepository addressRepository;
+  private final EmployeeRepository employeeRepository;
 
   @Autowired
   public OfficeService(@Qualifier(ServiceConstants.SERVICE_LOGGER_NAME) final Logger logger,
                        final OfficeRepository officeRepository,
-                       final AddressRepository addressRepository) {
+                       final AddressRepository addressRepository,
+                       final EmployeeRepository employeeRepository) {
     super();
     this.logger = logger;
     this.officeRepository = officeRepository;
     this.addressRepository = addressRepository;
+    this.employeeRepository = employeeRepository;
   }
 
   public boolean officeExists(final String identifier) {
@@ -61,11 +61,13 @@ public class OfficeService {
 
   public boolean branchExists(final String identifier) {
     final Optional<OfficeEntity> officeEntityOptional = this.officeRepository.findByIdentifier(identifier);
-    if (officeEntityOptional.isPresent()) {
-      return this.officeRepository.existsByParentOfficeId(officeEntityOptional.get().getId());
-    } else {
-      throw ServiceException.notFound("Office {0} not found.", identifier);
-    }
+    return officeEntityOptional.map(officeEntity -> this.officeRepository.existsByParentOfficeId(officeEntity.getId())).orElse(false);
+  }
+
+  public boolean hasEmployees(final String officeIdentifier){
+    return this.officeRepository.findByIdentifier(officeIdentifier)
+            .map(this.employeeRepository::existsByAssignedOffice)
+            .orElse(false);
   }
 
   @Transactional(readOnly = true)
diff --git a/service/src/main/java/io/mifos/office/rest/controller/OfficeRestController.java b/service/src/main/java/io/mifos/office/rest/controller/OfficeRestController.java
index ac60901..ab30f63 100644
--- a/service/src/main/java/io/mifos/office/rest/controller/OfficeRestController.java
+++ b/service/src/main/java/io/mifos/office/rest/controller/OfficeRestController.java
@@ -208,14 +208,20 @@ public class OfficeRestController {
   @ResponseBody
   ResponseEntity<Void> deleteOffice(@PathVariable("identifier") final String identifier)
       throws InterruptedException {
+    if (!this.officeService.officeExists(identifier)) {
+      throw ServiceException.notFound("Office {0} not found.", identifier);
+    }
+
     if (this.officeService.branchExists(identifier)) {
       throw ServiceException.conflict("Office {0} has children.", identifier);
     }
 
-    if (this.officeService.officeExists(identifier)) {
-      this.commandGateway.process(new DeleteOfficeCommand(identifier));
+    if(this.officeService.hasEmployees(identifier)){
+      throw ServiceException.conflict("Office {0} has employees.", identifier);
     }
 
+    this.commandGateway.process(new DeleteOfficeCommand(identifier));
+
     return ResponseEntity.accepted().build();
   }
 

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

[fineract-cn-office] 11/14: Merge pull request #3 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-office.git

commit 29d279ff659fe52a7aa795f52a04b3f32a4fb546
Merge: f3f662f 1d744b0
Author: Markus Geiß <ma...@apache.org>
AuthorDate: Thu Apr 20 15:17:33 2017 +0200

    Merge pull request #3 from mgeiss/develop
    
    edited logback

 service/src/main/resources/logback.xml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

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

[fineract-cn-office] 13/14: Merge pull request #4 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-office.git

commit a87f8258758af2f4046ce6e5ee2c9d0303377ba0
Merge: 29d279f 17b1466
Author: Markus Geiß <ma...@apache.org>
AuthorDate: Wed Apr 26 20:07:36 2017 +0200

    Merge pull request #4 from mgeiss/develop
    
    added path configurer to allow dots in url

 .../java/io/mifos/office/rest/config/OfficeRestConfiguration.java | 8 +++++++-
 1 file changed, 7 insertions(+), 1 deletion(-)

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

[fineract-cn-office] 04/14: Merge pull request #1 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-office.git

commit a952ee3dad954dea056acefd718d270f22392ae0
Merge: 102fb07 dbf7472
Author: Markus Geiß <ma...@apache.org>
AuthorDate: Sat Apr 8 10:32:37 2017 +0200

    Merge pull request #1 from markusgeiss/develop
    
    enhanced logging for demo server usage

 service/src/main/resources/logback.xml | 25 ++++++++++++++++++++-----
 1 file changed, 20 insertions(+), 5 deletions(-)

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

[fineract-cn-office] 12/14: added path configurer to allow dots in url

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-office.git

commit 17b14665007d3f3155d65e90fd1937146271c7a0
Author: mgeiss <mg...@mifos.org>
AuthorDate: Wed Apr 26 17:57:19 2017 +0200

    added path configurer to allow dots in url
---
 .../java/io/mifos/office/rest/config/OfficeRestConfiguration.java | 8 +++++++-
 1 file changed, 7 insertions(+), 1 deletion(-)

diff --git a/service/src/main/java/io/mifos/office/rest/config/OfficeRestConfiguration.java b/service/src/main/java/io/mifos/office/rest/config/OfficeRestConfiguration.java
index 5de5ebc..43c3ad0 100644
--- a/service/src/main/java/io/mifos/office/rest/config/OfficeRestConfiguration.java
+++ b/service/src/main/java/io/mifos/office/rest/config/OfficeRestConfiguration.java
@@ -31,6 +31,8 @@ import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.ComponentScan;
 import org.springframework.context.annotation.Configuration;
 import org.springframework.context.annotation.Import;
+import org.springframework.web.servlet.config.annotation.PathMatchConfigurer;
+import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
 
 @Configuration
 @EnableAutoConfiguration
@@ -47,7 +49,7 @@ import org.springframework.context.annotation.Import;
 @Import({
     OfficeServiceConfiguration.class
 })
-public class OfficeRestConfiguration {
+public class OfficeRestConfiguration extends WebMvcConfigurerAdapter {
 
   public OfficeRestConfiguration() {
     super();
@@ -58,4 +60,8 @@ public class OfficeRestConfiguration {
     return LoggerFactory.getLogger(ServiceConstants.REST_LOGGER_NAME);
   }
 
+  @Override
+  public void configurePathMatch(final PathMatchConfigurer configurer) {
+    configurer.setUseSuffixPatternMatch(Boolean.FALSE);
+  }
 }

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

[fineract-cn-office] 09/14: Merge pull request #2 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-office.git

commit f3f662fca5f6bc5b5b863bbf272233700c8c64d7
Merge: a952ee3 a17e82e
Author: Mark <cr...@users.noreply.github.com>
AuthorDate: Wed Apr 12 17:40:38 2017 +0200

    Merge pull request #2 from crain/develop
    
    Bugfix/Improvements

 .../office/api/v1/client/OrganizationManager.java  |  5 +-
 .../src/main/java/io/mifos/office/TestOffice.java  | 63 ++++++++++++++++++++--
 .../internal/repository/EmployeeRepository.java    |  3 ++
 .../office/internal/service/OfficeService.java     | 22 ++++----
 .../rest/controller/OfficeRestController.java      | 18 ++++++-
 .../db/migrations/mariadb/V1__initial_setup.sql    |  2 +-
 6 files changed, 95 insertions(+), 18 deletions(-)

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

[fineract-cn-office] 03/14: enhanced logging for demo server 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-office.git

commit dbf74721c4d3e525f35917cf5127f20824a5cf2b
Author: mgeiss <mg...@mifos.org>
AuthorDate: Sat Apr 8 09:37:45 2017 +0200

    enhanced logging for demo server usage
---
 service/src/main/resources/logback.xml | 25 ++++++++++++++++++++-----
 1 file changed, 20 insertions(+), 5 deletions(-)

diff --git a/service/src/main/resources/logback.xml b/service/src/main/resources/logback.xml
index 9d04222..023aa98 100644
--- a/service/src/main/resources/logback.xml
+++ b/service/src/main/resources/logback.xml
@@ -1,6 +1,6 @@
 <!--
 
-    Copyright 2017 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.
@@ -17,11 +17,11 @@
 -->
 <configuration>
     <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
-        <file>office.log</file>
+        <file>logs/office.log</file>
         <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
             <fileNamePattern>office.%d{yyyy-MM-dd}.log</fileNamePattern>
-            <maxHistory>30</maxHistory>
-            <totalSizeCap>3GB</totalSizeCap>
+            <maxHistory>7</maxHistory>
+            <totalSizeCap>2GB</totalSizeCap>
         </rollingPolicy>
         <encoder>
             <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
@@ -33,8 +33,23 @@
         </encoder>
     </appender>
 
+    <logger name="com" level="INFO">
+        <appender-ref ref="STDOUT" />
+    </logger>
+
+    <logger name="org" level="INFO">
+        <appender-ref ref="STDOUT" />
+    </logger>
+
+    <logger name="io" level="INFO">
+        <appender-ref ref="STDOUT" />
+    </logger>
+
+    <logger name="net" level="INFO">
+        <appender-ref ref="STDOUT" />
+    </logger>
+
     <root level="DEBUG">
         <appender-ref ref="FILE"/>
-        <appender-ref ref="STDOUT"/>
     </root>
 </configuration>
\ No newline at end of file

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

[fineract-cn-office] 10/14: edited logback

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-office.git

commit 1d744b044c3f9e86e33e3947b86d7aa841cd5a62
Author: mgeiss <mg...@mifos.org>
AuthorDate: Thu Apr 20 15:11:01 2017 +0200

    edited logback
---
 service/src/main/resources/logback.xml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/service/src/main/resources/logback.xml b/service/src/main/resources/logback.xml
index 023aa98..dd09a7a 100644
--- a/service/src/main/resources/logback.xml
+++ b/service/src/main/resources/logback.xml
@@ -19,7 +19,7 @@
     <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
         <file>logs/office.log</file>
         <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
-            <fileNamePattern>office.%d{yyyy-MM-dd}.log</fileNamePattern>
+            <fileNamePattern>logs/archive/office.%d{yyyy-MM-dd}.log</fileNamePattern>
             <maxHistory>7</maxHistory>
             <totalSizeCap>2GB</totalSizeCap>
         </rollingPolicy>

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

[fineract-cn-office] 05/14: Change postal code to have NULL constraint

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-office.git

commit fd66142c94c0666863741555ef7049a63fe20560
Author: Mark <ma...@gmail.com>
AuthorDate: Wed Apr 12 17:01:53 2017 +0200

    Change postal code to have NULL constraint
---
 service/src/main/resources/db/migrations/mariadb/V1__initial_setup.sql | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/service/src/main/resources/db/migrations/mariadb/V1__initial_setup.sql b/service/src/main/resources/db/migrations/mariadb/V1__initial_setup.sql
index 3805815..7165370 100644
--- a/service/src/main/resources/db/migrations/mariadb/V1__initial_setup.sql
+++ b/service/src/main/resources/db/migrations/mariadb/V1__initial_setup.sql
@@ -39,7 +39,7 @@ CREATE TABLE horus_addresses (
   street       VARCHAR(256) NOT NULL,
   city         VARCHAR(256) NOT NULL,
   region       VARCHAR(256) NULL,
-  postal_code  VARCHAR(32)  NOT NULL,
+  postal_code  VARCHAR(32)  NULL,
   country_code VARCHAR(2)   NOT NULL,
   country      VARCHAR(256) NOT NULL,
   CONSTRAINT address_pk

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

[fineract-cn-office] 01/14: Initial Commit

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-office.git

commit a63222687a9d00cf699d49627b33c060a4f29d6a
Author: Markus Geiss <mg...@mifos.org>
AuthorDate: Wed Mar 15 15:33:27 2017 +0100

    Initial Commit
---
 .gitignore                                         |  16 +
 HEADER                                             |  13 +
 LICENSE                                            | 201 +++++++++
 README.md                                          |  28 ++
 api/build.gradle                                   |  35 ++
 api/settings.gradle                                |   1 +
 .../io/mifos/office/api/v1/EventConstants.java     |  51 +++
 .../mifos/office/api/v1/PermittableGroupIds.java   |  26 ++
 .../api/v1/client/AlreadyExistsException.java      |  19 +
 .../office/api/v1/client/BadRequestException.java  |  19 +
 .../api/v1/client/ChildrenExistException.java      |  19 +
 .../office/api/v1/client/NotFoundException.java    |  19 +
 .../mifos/office/api/v1/client/OfficeClient.java   | 223 ++++++++++
 .../io/mifos/office/api/v1/domain/Address.java     |  84 ++++
 .../mifos/office/api/v1/domain/ContactDetail.java  | 108 +++++
 .../io/mifos/office/api/v1/domain/Employee.java    |  86 ++++
 .../mifos/office/api/v1/domain/EmployeePage.java   |  56 +++
 .../java/io/mifos/office/api/v1/domain/Office.java |  73 ++++
 .../io/mifos/office/api/v1/domain/OfficePage.java  |  56 +++
 build.gradle                                       |  36 ++
 component-test/build.gradle                        |  38 ++
 component-test/settings.gradle                     |   1 +
 .../main/java/io/mifos/office/TestEmployee.java    | 401 ++++++++++++++++++
 .../src/main/java/io/mifos/office/TestOffice.java  | 302 ++++++++++++++
 .../office/listener/EmployeeEventListener.java     |  86 ++++
 .../office/listener/MigrationEventListener.java    |  47 +++
 .../mifos/office/listener/OfficeEventListener.java |  97 +++++
 .../java/io/mifos/office/util/AddressFactory.java  |  37 ++
 .../java/io/mifos/office/util/EmployeeFactory.java |  35 ++
 .../java/io/mifos/office/util/OfficeFactory.java   |  34 ++
 component-test/src/main/resources/logback.xml      |  40 ++
 gradle/wrapper/gradle-wrapper.jar                  | Bin 0 -> 54212 bytes
 gradle/wrapper/gradle-wrapper.properties           |   6 +
 gradlew                                            | 172 ++++++++
 gradlew.bat                                        |  84 ++++
 service/build.gradle                               |  63 +++
 service/settings.gradle                            |   1 +
 .../java/io/mifos/office/OfficeApplication.java    |  30 ++
 .../java/io/mifos/office/ServiceConstants.java     |  26 ++
 .../office/internal/command/AddBranchCommand.java  |  38 ++
 .../internal/command/CreateEmployeeCommand.java    |  32 ++
 .../internal/command/CreateOfficeCommand.java      |  32 ++
 .../command/DeleteAddressOfOfficeCommand.java      |  30 ++
 .../command/DeleteContactDetailCommand.java        |  30 ++
 .../internal/command/DeleteEmployeeCommand.java    |  30 ++
 .../internal/command/DeleteOfficeCommand.java      |  30 ++
 .../internal/command/InitializeServiceCommand.java |  23 ++
 .../command/SetAddressForOfficeCommand.java        |  38 ++
 .../internal/command/SetContactDetailsCommand.java |  40 ++
 .../internal/command/UpdateEmployeeCommand.java    |  32 ++
 .../internal/command/UpdateOfficeCommand.java      |  32 ++
 .../command/handler/EmployeeAggregate.java         | 214 ++++++++++
 .../command/handler/MigrationAggregate.java        |  58 +++
 .../internal/command/handler/OfficeAggregate.java  | 223 ++++++++++
 .../office/internal/command/handler/Utils.java     |  29 ++
 .../config/OfficeServiceConfiguration.java         |  58 +++
 .../office/internal/mapper/AddressMapper.java      |  48 +++
 .../internal/mapper/ContactDetailMapper.java       |  44 ++
 .../office/internal/mapper/EmployeeMapper.java     |  46 +++
 .../mifos/office/internal/mapper/OfficeMapper.java |  42 ++
 .../office/internal/repository/AddressEntity.java  | 119 ++++++
 .../internal/repository/AddressRepository.java     |  27 ++
 .../internal/repository/ContactDetailEntity.java   |  99 +++++
 .../repository/ContactDetailRepository.java        |  27 ++
 .../office/internal/repository/EmployeeEntity.java | 160 ++++++++
 .../internal/repository/EmployeeRepository.java    |  36 ++
 .../office/internal/repository/OfficeEntity.java   | 146 +++++++
 .../internal/repository/OfficeRepository.java      |  43 ++
 .../office/internal/service/EmployeeService.java   | 125 ++++++
 .../office/internal/service/OfficeService.java     | 150 +++++++
 .../rest/config/OfficeRestConfiguration.java       |  61 +++
 .../rest/controller/OfficeRestController.java      | 449 +++++++++++++++++++++
 service/src/main/resources/application.yml         |  66 +++
 service/src/main/resources/bootstrap.yml           |  19 +
 .../db/migrations/mariadb/V1__initial_setup.sql    |  84 ++++
 service/src/main/resources/logback.xml             |  40 ++
 settings.gradle                                    |   6 +
 shared.gradle                                      |  73 ++++
 78 files changed, 5548 insertions(+)

diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..f9d7cba
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,16 @@
+.gradle
+.idea
+build/
+target/
+
+# Ignore Gradle GUI config
+gradle-app.setting
+
+# Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored)
+!gradle-wrapper.jar
+
+*.iml
+
+*.log
+
+*.toDelete
diff --git a/HEADER b/HEADER
new file mode 100644
index 0000000..76f9222
--- /dev/null
+++ b/HEADER
@@ -0,0 +1,13 @@
+Copyright ${year} ${name}
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+   http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
\ No newline at end of file
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..100d215
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,201 @@
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source identifier, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object identifier, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source identifier control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
+   APPENDIX: How to apply the Apache License to your work.
+
+      To apply the Apache License to your work, attach the following
+      boilerplate notice, with the fields enclosed by brackets "{}"
+      replaced with your own identifying information. (Don't include
+      the brackets!)  The text should be enclosed in the appropriate
+      comment syntax for the file format. We also recommend that a
+      file or class name and description of purpose be included on the
+      same "printed page" as the copyright notice for easier
+      identification within third-party archives.
+
+   Copyright {yyyy} {name of copyright owner}
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..3cb8403
--- /dev/null
+++ b/README.md
@@ -0,0 +1,28 @@
+# Mifos I/O Office Management
+
+[![Join the chat at https://gitter.im/mifos-initiative/mifos.io](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/mifos-initiative/mifos.io?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
+
+This module manages an organization and its employees.
+
+## Abstract
+Mifos I/O is an application framework for digital financial services, a system to support nationwide and cross-national financial transactions and help to level and speed the creation of an inclusive, interconnected digital economy for every nation in the world.
+
+## Versioning
+The version numbers follow the [Semantic Versioning](http://semver.org/) scheme.
+
+In addition to MAJOR.MINOR.PATCH the following postfixes are used to indicate the development state.
+
+* BUILD-SNAPSHOT - A release currently in development. 
+* M - A _milestone_ release include specific sets of functions and are released as soon as the functionality is complete.
+* RC - A _release candidate_ is a version with potential to be a final product, considered _code complete_.
+* RELEASE - indicates that this release is the best available version and is recommended for all usage.
+
+The versioning layout is {MAJOR}.{MINOR}.{PATCH}-{INDICATOR}[.{PATCH}]. Only milestones and release candidates can  have patch versions. Some examples:
+
+1.2.3-BUILD-SNAPSHOT  
+1.3.5-M.1  
+1.5.7-RC.2  
+2.0.0-RELEASE
+
+## License
+See [LICENSE](LICENSE) file.
\ No newline at end of file
diff --git a/api/build.gradle b/api/build.gradle
new file mode 100644
index 0000000..6906d74
--- /dev/null
+++ b/api/build.gradle
@@ -0,0 +1,35 @@
+buildscript {
+
+    repositories {
+        jcenter()
+    }
+
+    dependencies {
+        classpath 'io.spring.gradle:dependency-management-plugin:0.6.0.RELEASE'
+    }
+}
+
+plugins {
+    id "com.github.hierynomus.license" version "0.13.1"
+}
+
+apply from: '../shared.gradle'
+
+dependencies {
+    compile(
+            [group: 'org.springframework.cloud', name: 'spring-cloud-starter-feign'],
+            [group: 'io.mifos.core', name: 'api', version: versions.frameworkapi],
+            [group: 'org.hibernate', name: 'hibernate-validator', version: versions.validator]
+    )
+}
+
+publishing {
+    publications {
+        api(MavenPublication) {
+            from components.java
+            groupId project.group
+            artifactId project.name
+            version project.version
+        }
+    }
+}
diff --git a/api/settings.gradle b/api/settings.gradle
new file mode 100644
index 0000000..7c8e3dc
--- /dev/null
+++ b/api/settings.gradle
@@ -0,0 +1 @@
+rootProject.name = 'api'
\ No newline at end of file
diff --git a/api/src/main/java/io/mifos/office/api/v1/EventConstants.java b/api/src/main/java/io/mifos/office/api/v1/EventConstants.java
new file mode 100644
index 0000000..dc5fa1c
--- /dev/null
+++ b/api/src/main/java/io/mifos/office/api/v1/EventConstants.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.office.api.v1;
+
+@SuppressWarnings("unused")
+public interface EventConstants {
+
+  String DESTINATION = "office-v1";
+  String OPERATION_HEADER = "operation";
+
+  String INITIALIZE = "initialize";
+
+  String OPERATION_POST_OFFICE = "post-office";
+  String OPERATION_PUT_OFFICE = "put-office";
+  String OPERATION_DELETE_OFFICE = "delete-office";
+  String OPERATION_PUT_ADDRESS = "put-address";
+  String OPERATION_DELETE_ADDRESS = "delete-address";
+
+  String OPERATION_POST_EMPLOYEE = "post-employee";
+  String OPERATION_PUT_EMPLOYEE = "put-employee";
+  String OPERATION_DELETE_EMPLOYEE = "delete-employee";
+  String OPERATION_PUT_CONTACT_DETAIL = "put-contact-detail";
+  String OPERATION_DELETE_CONTACT_DETAIL = "delete-contact-detail";
+
+  String SELECTOR_INITIALIZE = OPERATION_HEADER + " = '" + INITIALIZE + "'";
+
+  String SELECTOR_POST_OFFICE = OPERATION_HEADER + " = '" + OPERATION_POST_OFFICE + "'";
+  String SELECTOR_PUT_OFFICE = OPERATION_HEADER + " = '" + OPERATION_PUT_OFFICE + "'";
+  String SELECTOR_DELETE_OFFICE = OPERATION_HEADER + " = '" + OPERATION_DELETE_OFFICE + "'";
+  String SELECTOR_PUT_ADDRESS = OPERATION_HEADER + " = '" + OPERATION_PUT_ADDRESS + "'";
+  String SELECTOR_DELETE_ADDRESS = OPERATION_HEADER + " = '" + OPERATION_DELETE_ADDRESS + "'";
+
+  String SELECTOR_POST_EMPLOYEE = OPERATION_HEADER + " = '" + OPERATION_POST_EMPLOYEE + "'";
+  String SELECTOR_PUT_EMPLOYEE = OPERATION_HEADER + " = '" + OPERATION_PUT_EMPLOYEE + "'";
+  String SELECTOR_DELETE_EMPLOYEE = OPERATION_HEADER + " = '" + OPERATION_DELETE_EMPLOYEE + "'";
+  String SELECTOR_PUT_CONTACT_DETAIL = OPERATION_HEADER + " = '" + OPERATION_PUT_CONTACT_DETAIL + "'";
+  String SELECTOR_DELETE_CONTACT_DETAIL = OPERATION_HEADER + " = '" + OPERATION_DELETE_CONTACT_DETAIL + "'";
+}
diff --git a/api/src/main/java/io/mifos/office/api/v1/PermittableGroupIds.java b/api/src/main/java/io/mifos/office/api/v1/PermittableGroupIds.java
new file mode 100644
index 0000000..fe481ba
--- /dev/null
+++ b/api/src/main/java/io/mifos/office/api/v1/PermittableGroupIds.java
@@ -0,0 +1,26 @@
+/*
+ * 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.office.api.v1;
+
+/**
+ * @author Myrle Krantz
+ */
+@SuppressWarnings("unused")
+public interface PermittableGroupIds {
+  String OFFICE_MANAGEMENT = "office__v1__offices";
+  String EMPLOYEE_MANAGEMENT = "office__v1__employees";
+  String SELF_MANAGEMENT = "office__v1__self";
+}
diff --git a/api/src/main/java/io/mifos/office/api/v1/client/AlreadyExistsException.java b/api/src/main/java/io/mifos/office/api/v1/client/AlreadyExistsException.java
new file mode 100644
index 0000000..2642bf1
--- /dev/null
+++ b/api/src/main/java/io/mifos/office/api/v1/client/AlreadyExistsException.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.office.api.v1.client;
+
+public class AlreadyExistsException extends RuntimeException {
+}
diff --git a/api/src/main/java/io/mifos/office/api/v1/client/BadRequestException.java b/api/src/main/java/io/mifos/office/api/v1/client/BadRequestException.java
new file mode 100644
index 0000000..8a5dea0
--- /dev/null
+++ b/api/src/main/java/io/mifos/office/api/v1/client/BadRequestException.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.office.api.v1.client;
+
+public class BadRequestException extends RuntimeException {
+}
diff --git a/api/src/main/java/io/mifos/office/api/v1/client/ChildrenExistException.java b/api/src/main/java/io/mifos/office/api/v1/client/ChildrenExistException.java
new file mode 100644
index 0000000..1a6d097
--- /dev/null
+++ b/api/src/main/java/io/mifos/office/api/v1/client/ChildrenExistException.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.office.api.v1.client;
+
+public class ChildrenExistException extends RuntimeException {
+}
diff --git a/api/src/main/java/io/mifos/office/api/v1/client/NotFoundException.java b/api/src/main/java/io/mifos/office/api/v1/client/NotFoundException.java
new file mode 100644
index 0000000..b34d624
--- /dev/null
+++ b/api/src/main/java/io/mifos/office/api/v1/client/NotFoundException.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.office.api.v1.client;
+
+public class NotFoundException extends RuntimeException {
+}
diff --git a/api/src/main/java/io/mifos/office/api/v1/client/OfficeClient.java b/api/src/main/java/io/mifos/office/api/v1/client/OfficeClient.java
new file mode 100644
index 0000000..195ccfd
--- /dev/null
+++ b/api/src/main/java/io/mifos/office/api/v1/client/OfficeClient.java
@@ -0,0 +1,223 @@
+/*
+ * 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.office.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.office.api.v1.domain.Address;
+import io.mifos.office.api.v1.domain.ContactDetail;
+import io.mifos.office.api.v1.domain.Employee;
+import io.mifos.office.api.v1.domain.EmployeePage;
+import io.mifos.office.api.v1.domain.Office;
+import io.mifos.office.api.v1.domain.OfficePage;
+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 java.util.List;
+
+@SuppressWarnings("unused")
+@FeignClient(name="office-v1", path="/office/v1", configuration=CustomFeignClientsConfiguration.class)
+public interface OfficeClient {
+
+  @RequestMapping(
+      value = "/offices",
+      method = RequestMethod.POST,
+      consumes = MediaType.APPLICATION_JSON_VALUE,
+      produces = MediaType.APPLICATION_JSON_VALUE
+  )
+  @ThrowsExceptions({
+      @ThrowsException(status = HttpStatus.NOT_FOUND, exception = NotFoundException.class),
+      @ThrowsException(status = HttpStatus.CONFLICT, exception = AlreadyExistsException.class)
+  })
+  void createOffice(@RequestBody final Office office);
+
+  @RequestMapping(
+      value = "/offices",
+      method = RequestMethod.GET,
+      consumes = MediaType.APPLICATION_JSON_VALUE,
+      produces = MediaType.ALL_VALUE
+  )
+  OfficePage fetchOffices(@RequestParam(value = "term", required = false) final String term,
+                          @RequestParam(value = "pageIndex", required = false) final Integer pageIndex,
+                          @RequestParam(value = "size", required = false) final Integer size,
+                          @RequestParam(value = "sortColumn", required = false) final String sortColumn,
+                          @RequestParam(value = "sortDirection", required = false) final String sortDirection);
+
+  @RequestMapping(
+      value = "/offices/{identifier}",
+      method = RequestMethod.GET,
+      consumes = MediaType.APPLICATION_JSON_VALUE,
+      produces = MediaType.ALL_VALUE
+  )
+  @ThrowsException(status = HttpStatus.NOT_FOUND, exception = NotFoundException.class)
+  Office findOfficeByIdentifier(@PathVariable("identifier") final String identifier);
+
+  @RequestMapping(
+      value = "/offices/{identifier}",
+      method = RequestMethod.PUT,
+      consumes = MediaType.APPLICATION_JSON_VALUE,
+      produces = MediaType.APPLICATION_JSON_VALUE
+  )
+  @ThrowsExceptions({
+      @ThrowsException(status = HttpStatus.NOT_FOUND, exception = NotFoundException.class),
+      @ThrowsException(status = HttpStatus.BAD_REQUEST, exception = BadRequestException.class)
+  })
+  void updateOffice(@PathVariable("identifier") final String identifier, @RequestBody final Office office);
+
+  @RequestMapping(
+      value = "/offices/{identifier}",
+      method = RequestMethod.POST,
+      consumes = MediaType.APPLICATION_JSON_VALUE,
+      produces = MediaType.APPLICATION_JSON_VALUE
+  )
+  @ThrowsException(status = HttpStatus.NOT_FOUND, exception = NotFoundException.class)
+  void addBranch(@PathVariable("identifier") final String identifier, @RequestBody final Office office);
+
+  @RequestMapping(
+      value = "/offices/{identifier}/branches",
+      method = RequestMethod.GET,
+      consumes = MediaType.APPLICATION_JSON_VALUE,
+      produces = MediaType.APPLICATION_JSON_VALUE
+  )
+  @ThrowsException(status = HttpStatus.NOT_FOUND, exception = NotFoundException.class)
+  OfficePage getBranches(@PathVariable("identifier") final String identifier,
+                         @RequestParam(value = "pageIndex", required = false) final Integer pageIndex,
+                         @RequestParam(value = "size", required = false) final Integer size,
+                         @RequestParam(value = "sortColumn", required = false) final String sortColumn,
+                         @RequestParam(value = "sortDirection", required = false) final String sortDirection);
+
+  @RequestMapping(
+      value = "/offices/{identifier}",
+      method = RequestMethod.DELETE,
+      consumes = MediaType.APPLICATION_JSON_VALUE,
+      produces = MediaType.ALL_VALUE
+  )
+  @ThrowsException(status = HttpStatus.CONFLICT, exception = ChildrenExistException.class)
+  void deleteOffice(@PathVariable("identifier") final String identifier);
+
+  @RequestMapping(
+      value = "/offices/{identifier}/address",
+      method = RequestMethod.PUT,
+      consumes = MediaType.APPLICATION_JSON_VALUE,
+      produces = MediaType.APPLICATION_JSON_VALUE
+  )
+  @ThrowsException(status = HttpStatus.NOT_FOUND, exception = NotFoundException.class)
+  void setAddressForOffice(@PathVariable("identifier") final String identifier, @RequestBody final Address address);
+
+  @RequestMapping(
+      value = "/offices/{identifier}/address",
+      method = RequestMethod.GET,
+      consumes = MediaType.APPLICATION_JSON_VALUE,
+      produces = MediaType.ALL_VALUE
+  )
+  Address getAddressOfOffice(@PathVariable("identifier") final String identifier);
+
+  @RequestMapping(
+      value = "/offices/{identifier}/address",
+      method = RequestMethod.DELETE,
+      consumes = MediaType.APPLICATION_JSON_VALUE,
+      produces = MediaType.ALL_VALUE
+  )
+  @ThrowsException(status = HttpStatus.NOT_FOUND, exception = NotFoundException.class)
+  void deleteAddressOfOffice(@PathVariable("identifier") final String identifier);
+
+  @RequestMapping(
+      value = "/employees",
+      method = RequestMethod.POST,
+      consumes = MediaType.APPLICATION_JSON_VALUE,
+      produces = MediaType.APPLICATION_JSON_VALUE
+  )
+  @ThrowsException(status = HttpStatus.CONFLICT, exception = AlreadyExistsException.class)
+  void createEmployee(@RequestBody final Employee employee);
+
+  @RequestMapping(
+      value = "/employees",
+      method = RequestMethod.GET,
+      consumes = MediaType.APPLICATION_JSON_VALUE,
+      produces = MediaType.ALL_VALUE
+  )
+  @ThrowsException(status = HttpStatus.NOT_FOUND, exception = NotFoundException.class)
+  EmployeePage fetchEmployees(@RequestParam(value = "term", required = false) final String term,
+                              @RequestParam(value = "office", required = false) final String officeIdentifier,
+                              @RequestParam(value = "pageIndex", required = false) final Integer pageIndex,
+                              @RequestParam(value = "size", required = false) final Integer size,
+                              @RequestParam(value = "sortColumn", required = false) final String sortColumn,
+                              @RequestParam(value = "sortDirection", required = false) final String sortDirection);
+
+  @RequestMapping(
+      value = "/employees/{useridentifier}",
+      method = RequestMethod.GET,
+      consumes = MediaType.APPLICATION_JSON_VALUE,
+      produces = MediaType.ALL_VALUE
+  )
+  @ThrowsException(status = HttpStatus.NOT_FOUND, exception = NotFoundException.class)
+  Employee findEmployee(@PathVariable("useridentifier") final String identifier);
+
+  @RequestMapping(
+      value = "/employees/{useridentifier}",
+      method = RequestMethod.PUT,
+      consumes = MediaType.APPLICATION_JSON_VALUE,
+      produces = MediaType.APPLICATION_JSON_VALUE
+  )
+  @ThrowsExceptions({
+      @ThrowsException(status = HttpStatus.NOT_FOUND, exception = NotFoundException.class),
+      @ThrowsException(status = HttpStatus.BAD_REQUEST, exception = BadRequestException.class)
+  })
+  void updateEmployee(@PathVariable("useridentifier") final String identifier, @RequestBody final Employee employee);
+
+  @RequestMapping(
+      value = "/employees/{useridentifier}",
+      method = RequestMethod.DELETE,
+      consumes = MediaType.APPLICATION_JSON_VALUE,
+      produces = MediaType.ALL_VALUE
+  )
+  void deleteEmployee(@PathVariable("useridentifier") final String identifier);
+
+  @RequestMapping(
+      value = "/employees/{useridentifier}/contacts",
+      method = RequestMethod.PUT,
+      consumes = MediaType.APPLICATION_JSON_VALUE,
+      produces = MediaType.ALL_VALUE
+  )
+  @ThrowsException(status = HttpStatus.NOT_FOUND, exception = NotFoundException.class)
+  void setContactDetails(@PathVariable("useridentifier") final String identifier,
+                         @RequestBody final List<ContactDetail> contactDetails);
+
+  @RequestMapping(
+      value = "/employees/{useridentifier}/contacts",
+      method = RequestMethod.GET,
+      consumes = MediaType.APPLICATION_JSON_VALUE,
+      produces = MediaType.ALL_VALUE
+  )
+  @ThrowsException(status = HttpStatus.NOT_FOUND, exception = NotFoundException.class)
+  List<ContactDetail> fetchContactDetails(@PathVariable("useridentifier") final String identifier);
+
+  @RequestMapping(
+      value = "/employees/{useridentifier}/contacts",
+      method = RequestMethod.DELETE,
+      consumes = MediaType.APPLICATION_JSON_VALUE,
+      produces = MediaType.ALL_VALUE
+  )
+  @ThrowsException(status = HttpStatus.NOT_FOUND, exception = NotFoundException.class)
+  void deleteContactDetails(@PathVariable("useridentifier") final String identifier);
+}
diff --git a/api/src/main/java/io/mifos/office/api/v1/domain/Address.java b/api/src/main/java/io/mifos/office/api/v1/domain/Address.java
new file mode 100644
index 0000000..198f142
--- /dev/null
+++ b/api/src/main/java/io/mifos/office/api/v1/domain/Address.java
@@ -0,0 +1,84 @@
+/*
+ * 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.office.api.v1.domain;
+
+import org.hibernate.validator.constraints.NotBlank;
+
+public class Address {
+
+  @NotBlank
+  private String street;
+  @NotBlank
+  private String city;
+  private String region;
+  private String postalCode;
+  @NotBlank
+  private String countryCode;
+  @NotBlank
+  private String country;
+
+  public Address() {
+    super();
+  }
+
+  public String getStreet() {
+    return street;
+  }
+
+  public void setStreet(String street) {
+    this.street = street;
+  }
+
+  public String getCity() {
+    return city;
+  }
+
+  public void setCity(String city) {
+    this.city = city;
+  }
+
+  public String getRegion() {
+    return region;
+  }
+
+  public void setRegion(String region) {
+    this.region = region;
+  }
+
+  public String getPostalCode() {
+    return postalCode;
+  }
+
+  public void setPostalCode(String postalCode) {
+    this.postalCode = postalCode;
+  }
+
+  public String getCountryCode() {
+    return countryCode;
+  }
+
+  public void setCountryCode(String countryCode) {
+    this.countryCode = countryCode;
+  }
+
+  public String getCountry() {
+    return country;
+  }
+
+  public void setCountry(String country) {
+    this.country = country;
+  }
+}
diff --git a/api/src/main/java/io/mifos/office/api/v1/domain/ContactDetail.java b/api/src/main/java/io/mifos/office/api/v1/domain/ContactDetail.java
new file mode 100644
index 0000000..f1c348c
--- /dev/null
+++ b/api/src/main/java/io/mifos/office/api/v1/domain/ContactDetail.java
@@ -0,0 +1,108 @@
+/*
+ * 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.office.api.v1.domain;
+
+import org.hibernate.validator.constraints.NotBlank;
+
+import javax.validation.constraints.Max;
+import javax.validation.constraints.Min;
+import javax.validation.constraints.NotNull;
+import java.util.Objects;
+
+public class ContactDetail {
+
+  @NotNull
+  private Type type;
+  @NotNull
+  private Group group;
+  @NotBlank
+  private String value;
+  @Min(1)
+  @Max(127)
+  private Integer preferenceLevel;
+  public ContactDetail() {
+    super();
+  }
+
+  public String getType() {
+    return this.type.name();
+  }
+
+  public void setType(final String type) {
+    this.type = Type.valueOf(type);
+  }
+
+  public String getGroup() {
+    return this.group.name();
+  }
+
+  public void setGroup(final String group) {
+    this.group = Group.valueOf(group);
+  }
+
+  public String getValue() {
+    return this.value;
+  }
+
+  public void setValue(final String value) {
+    this.value = value;
+  }
+
+  public Integer getPreferenceLevel() {
+    return this.preferenceLevel;
+  }
+
+  public void setPreferenceLevel(final Integer preferenceLevel) {
+    this.preferenceLevel = preferenceLevel;
+  }
+
+  public enum Type {
+    EMAIL,
+    PHONE,
+    MOBILE
+  }
+
+  public enum Group {
+    BUSINESS,
+    PRIVATE
+  }
+
+  @Override
+  public boolean equals(Object o) {
+    if (this == o) return true;
+    if (o == null || getClass() != o.getClass()) return false;
+    ContactDetail that = (ContactDetail) o;
+    return type == that.type &&
+            group == that.group &&
+            Objects.equals(value, that.value) &&
+            Objects.equals(preferenceLevel, that.preferenceLevel);
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hash(type, group, value, preferenceLevel);
+  }
+
+  @Override
+  public String toString() {
+    return "ContactDetail{" +
+            "type=" + type +
+            ", group=" + group +
+            ", value='" + value + '\'' +
+            ", preferenceLevel=" + preferenceLevel +
+            '}';
+  }
+}
diff --git a/api/src/main/java/io/mifos/office/api/v1/domain/Employee.java b/api/src/main/java/io/mifos/office/api/v1/domain/Employee.java
new file mode 100644
index 0000000..2bf76db
--- /dev/null
+++ b/api/src/main/java/io/mifos/office/api/v1/domain/Employee.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.office.api.v1.domain;
+
+import io.mifos.core.lang.validation.constraints.ValidIdentifier;
+
+import javax.validation.Valid;
+import java.util.List;
+
+@SuppressWarnings("unused")
+public class Employee {
+
+  @ValidIdentifier
+  private String identifier;
+  private String givenName;
+  private String middleName;
+  private String surname;
+  private String assignedOffice;
+  @Valid
+  private List<ContactDetail> contactDetails;
+
+  public Employee() {
+    super();
+  }
+
+  public String getIdentifier() {
+    return identifier;
+  }
+
+  public void setIdentifier(String identifier) {
+    this.identifier = identifier;
+  }
+
+  public String getGivenName() {
+    return givenName;
+  }
+
+  public void setGivenName(String givenName) {
+    this.givenName = givenName;
+  }
+
+  public String getMiddleName() {
+    return middleName;
+  }
+
+  public void setMiddleName(String middleName) {
+    this.middleName = middleName;
+  }
+
+  public String getSurname() {
+    return surname;
+  }
+
+  public void setSurname(String surname) {
+    this.surname = surname;
+  }
+
+  public String getAssignedOffice() {
+    return assignedOffice;
+  }
+
+  public void setAssignedOffice(String assignedOffice) {
+    this.assignedOffice = assignedOffice;
+  }
+
+  public List<ContactDetail> getContactDetails() {
+    return contactDetails;
+  }
+
+  public void setContactDetails(List<ContactDetail> contactDetails) {
+    this.contactDetails = contactDetails;
+  }
+}
diff --git a/api/src/main/java/io/mifos/office/api/v1/domain/EmployeePage.java b/api/src/main/java/io/mifos/office/api/v1/domain/EmployeePage.java
new file mode 100644
index 0000000..f370f97
--- /dev/null
+++ b/api/src/main/java/io/mifos/office/api/v1/domain/EmployeePage.java
@@ -0,0 +1,56 @@
+/*
+ * 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.office.api.v1.domain;
+
+import javax.validation.Valid;
+import java.util.List;
+
+@SuppressWarnings("unused")
+public class EmployeePage {
+
+  @Valid
+  private List<Employee> employees;
+  private Integer totalPages;
+  private Long totalElements;
+
+  public EmployeePage() {
+    super();
+  }
+
+  public List<Employee> getEmployees() {
+    return this.employees;
+  }
+
+  public void setEmployees(final List<Employee> employees) {
+    this.employees = employees;
+  }
+
+  public Integer getTotalPages() {
+    return this.totalPages;
+  }
+
+  public void setTotalPages(final Integer totalPages) {
+    this.totalPages = totalPages;
+  }
+
+  public Long getTotalElements() {
+    return this.totalElements;
+  }
+
+  public void setTotalElements(final Long totalElements) {
+    this.totalElements = totalElements;
+  }
+}
diff --git a/api/src/main/java/io/mifos/office/api/v1/domain/Office.java b/api/src/main/java/io/mifos/office/api/v1/domain/Office.java
new file mode 100644
index 0000000..4fd3f22
--- /dev/null
+++ b/api/src/main/java/io/mifos/office/api/v1/domain/Office.java
@@ -0,0 +1,73 @@
+/*
+ * 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.office.api.v1.domain;
+
+import io.mifos.core.lang.validation.constraints.ValidIdentifier;
+
+@SuppressWarnings("unused")
+public class Office {
+
+  @ValidIdentifier
+  private String identifier;
+  private String parentIdentifier;
+  private String name;
+  private String description;
+  private Address address;
+
+  public Office() {
+    super();
+  }
+
+  public String getIdentifier() {
+    return identifier;
+  }
+
+  public void setIdentifier(String identifier) {
+    this.identifier = identifier;
+  }
+
+  public String getParentIdentifier() {
+    return this.parentIdentifier;
+  }
+
+  public void setParentIdentifier(final String parentIdentifier) {
+    this.parentIdentifier = parentIdentifier;
+  }
+
+  public String getName() {
+    return name;
+  }
+
+  public void setName(String name) {
+    this.name = name;
+  }
+
+  public String getDescription() {
+    return description;
+  }
+
+  public void setDescription(String description) {
+    this.description = description;
+  }
+
+  public Address getAddress() {
+    return address;
+  }
+
+  public void setAddress(Address address) {
+    this.address = address;
+  }
+}
diff --git a/api/src/main/java/io/mifos/office/api/v1/domain/OfficePage.java b/api/src/main/java/io/mifos/office/api/v1/domain/OfficePage.java
new file mode 100644
index 0000000..0981329
--- /dev/null
+++ b/api/src/main/java/io/mifos/office/api/v1/domain/OfficePage.java
@@ -0,0 +1,56 @@
+/*
+ * 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.office.api.v1.domain;
+
+import javax.validation.Valid;
+import java.util.List;
+
+@SuppressWarnings("unused")
+public class OfficePage {
+
+  @Valid
+  private List<Office> offices;
+  private Integer totalPages;
+  private Long totalElements;
+
+  public OfficePage() {
+    super();
+  }
+
+  public List<Office> getOffices() {
+    return this.offices;
+  }
+
+  public void setOffices(final List<Office> offices) {
+    this.offices = offices;
+  }
+
+  public Integer getTotalPages() {
+    return this.totalPages;
+  }
+
+  public void setTotalPages(final Integer totalPages) {
+    this.totalPages = totalPages;
+  }
+
+  public Long getTotalElements() {
+    return this.totalElements;
+  }
+
+  public void setTotalElements(final Long totalElements) {
+    this.totalElements = totalElements;
+  }
+}
diff --git a/build.gradle b/build.gradle
new file mode 100644
index 0000000..b30bd55
--- /dev/null
+++ b/build.gradle
@@ -0,0 +1,36 @@
+group 'io.mifos'
+
+task publishApiToMavenLocal {
+    dependsOn gradle.includedBuild('api').task(':publishToMavenLocal')
+}
+
+task publishServiceToMavenLocal {
+    mustRunAfter publishApiToMavenLocal
+    dependsOn gradle.includedBuild('service').task(':publishToMavenLocal')
+}
+
+task publishComponentTestToMavenLocal {
+    mustRunAfter publishApiToMavenLocal
+    mustRunAfter publishServiceToMavenLocal
+    dependsOn gradle.includedBuild('component-test').task(':publishToMavenLocal')
+}
+
+task publishToMavenLocal {
+    group 'all'
+    dependsOn publishApiToMavenLocal
+    dependsOn publishServiceToMavenLocal
+    dependsOn publishComponentTestToMavenLocal
+}
+
+task prepareForTest {
+    group 'all'
+    dependsOn publishToMavenLocal
+    dependsOn gradle.includedBuild('component-test').task(':build')
+}
+
+task licenseFormat {
+    group 'all'
+    dependsOn gradle.includedBuild('api').task(':licenseFormat')
+    dependsOn gradle.includedBuild('service').task(':licenseFormat')
+    dependsOn gradle.includedBuild('component-test').task(':licenseFormat')
+}
diff --git a/component-test/build.gradle b/component-test/build.gradle
new file mode 100644
index 0000000..99ae1c5
--- /dev/null
+++ b/component-test/build.gradle
@@ -0,0 +1,38 @@
+buildscript {
+    ext {
+        springBootVersion = '1.4.1.RELEASE'
+    }
+
+    repositories {
+        jcenter()
+    }
+
+    dependencies {
+        classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
+    }
+}
+
+plugins {
+    id "com.github.hierynomus.license" version "0.13.1"
+}
+
+apply from: '../shared.gradle'
+
+dependencies {
+    compile(
+            [group: 'io.mifos.office', name: 'api', version: project.version],
+            [group: 'io.mifos.office', name: 'service', version: project.version],
+            [group: 'io.mifos.anubis', name: 'test', version: versions.frameworkanubis],
+            [group: 'io.mifos.core', name: 'api', version: versions.frameworkapi],
+            [group: 'io.mifos.core', name: 'test', version: versions.frameworktest],
+            [group: 'org.springframework.boot', name: 'spring-boot-starter-test']
+    )
+}
+
+publishing {
+    publications {
+        mavenJava(MavenPublication) {
+            from components.java
+        }
+    }
+}
\ No newline at end of file
diff --git a/component-test/settings.gradle b/component-test/settings.gradle
new file mode 100644
index 0000000..07867cb
--- /dev/null
+++ b/component-test/settings.gradle
@@ -0,0 +1 @@
+rootProject.name = 'component-test'
\ No newline at end of file
diff --git a/component-test/src/main/java/io/mifos/office/TestEmployee.java b/component-test/src/main/java/io/mifos/office/TestEmployee.java
new file mode 100644
index 0000000..9914c77
--- /dev/null
+++ b/component-test/src/main/java/io/mifos/office/TestEmployee.java
@@ -0,0 +1,401 @@
+/*
+ * 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.office;
+
+import io.mifos.anubis.test.v1.TenantApplicationSecurityEnvironmentTestRule;
+import io.mifos.core.api.context.AutoUserContext;
+import io.mifos.core.test.env.TestEnvironment;
+import io.mifos.core.test.fixture.TenantDataStoreContextTestRule;
+import io.mifos.core.test.fixture.cassandra.CassandraInitializer;
+import io.mifos.core.test.fixture.mariadb.MariaDBInitializer;
+import io.mifos.core.test.listener.EnableEventRecording;
+import io.mifos.core.test.listener.EventRecorder;
+import io.mifos.office.api.v1.EventConstants;
+import io.mifos.office.api.v1.client.AlreadyExistsException;
+import io.mifos.office.api.v1.client.BadRequestException;
+import io.mifos.office.api.v1.client.OfficeClient;
+import io.mifos.office.api.v1.client.NotFoundException;
+import io.mifos.office.api.v1.domain.ContactDetail;
+import io.mifos.office.api.v1.domain.Employee;
+import io.mifos.office.api.v1.domain.EmployeePage;
+import io.mifos.office.api.v1.domain.Office;
+import io.mifos.office.rest.config.OfficeRestConfiguration;
+import io.mifos.office.util.EmployeeFactory;
+import io.mifos.office.util.OfficeFactory;
+import org.apache.commons.lang3.RandomStringUtils;
+import org.junit.*;
+import org.junit.rules.RuleChain;
+import org.junit.rules.TestRule;
+import org.junit.runner.RunWith;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.cloud.netflix.feign.EnableFeignClients;
+import org.springframework.cloud.netflix.ribbon.RibbonClient;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.ComponentScan;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Import;
+import org.springframework.test.context.junit4.SpringRunner;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+@RunWith(SpringRunner.class)
+@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT)
+public class TestEmployee {
+  private static final String APP_NAME = "office-v1";
+  private static final String TEST_USER = "thutmosis";
+
+  private final static TestEnvironment testEnvironment = new TestEnvironment(APP_NAME);
+  private final static CassandraInitializer cassandraInitializer = new CassandraInitializer();
+  private final static MariaDBInitializer mariaDBInitializer = new MariaDBInitializer();
+  private final static TenantDataStoreContextTestRule tenantDataStoreContext = TenantDataStoreContextTestRule.forRandomTenantName(cassandraInitializer, mariaDBInitializer);
+
+  @ClassRule
+  public static TestRule orderClassRules = RuleChain
+          .outerRule(testEnvironment)
+          .around(cassandraInitializer)
+          .around(mariaDBInitializer)
+          .around(tenantDataStoreContext);
+
+  @Rule
+  public final TenantApplicationSecurityEnvironmentTestRule tenantApplicationSecurityEnvironment
+          = new TenantApplicationSecurityEnvironmentTestRule(testEnvironment, this::waitForInitialize);
+
+  @Autowired
+  private OfficeClient officeClient;
+
+  @Autowired
+  private EventRecorder eventRecorder;
+
+  private AutoUserContext userContext;
+
+  @Before
+  public void prepareTest() {
+    userContext = tenantApplicationSecurityEnvironment.createAutoUserContext(TestEmployee.TEST_USER);
+  }
+
+  @After
+  public void cleanupTest() {
+    userContext.close();
+  }
+
+  public boolean waitForInitialize() {
+    try {
+      return this.eventRecorder.wait(EventConstants.INITIALIZE, EventConstants.INITIALIZE);
+    } catch (final InterruptedException e) {
+      throw new IllegalStateException(e);
+    }
+  }
+
+  @Test
+  public void shouldCreateEmployee() throws Exception {
+    final Employee employee = EmployeeFactory.createRandomEmployee();
+    {
+      this.officeClient.createEmployee(employee);
+      final boolean found = this.eventRecorder.wait(EventConstants.OPERATION_POST_EMPLOYEE, employee.getIdentifier());
+      Assert.assertTrue(found);
+    }
+
+    final Employee savedEmployee = this.officeClient.findEmployee(employee.getIdentifier());
+
+    Assert.assertNotNull(savedEmployee);
+    Assert.assertEquals(employee.getIdentifier(), savedEmployee.getIdentifier());
+    Assert.assertEquals(employee.getGivenName(), savedEmployee.getGivenName());
+    Assert.assertEquals(employee.getMiddleName(), savedEmployee.getMiddleName());
+    Assert.assertEquals(employee.getSurname(), savedEmployee.getSurname());
+
+    this.officeClient.deleteEmployee(employee.getIdentifier());
+    this.eventRecorder.wait(EventConstants.OPERATION_DELETE_EMPLOYEE, employee.getIdentifier());
+  }
+
+  @Test
+  public void shouldNotCreateEmployeeAlreadyExists() throws Exception {
+    final Employee employee = EmployeeFactory.createRandomEmployee();
+    this.officeClient.createEmployee(employee);
+
+    this.eventRecorder.wait(EventConstants.OPERATION_POST_EMPLOYEE, employee.getIdentifier());
+
+    try {
+      this.officeClient.createEmployee(employee);
+      Assert.fail();
+    } catch (final AlreadyExistsException ex) {
+      // do nothing, expected
+    }
+
+    this.officeClient.deleteEmployee(employee.getIdentifier());
+    this.eventRecorder.wait(EventConstants.OPERATION_DELETE_EMPLOYEE, employee.getIdentifier());
+  }
+
+  @Test
+  public void shouldFindEmployeesByOffice() throws Exception {
+    final Office office = OfficeFactory.createRandomOffice();
+    this.officeClient.createOffice(office);
+    this.eventRecorder.wait(EventConstants.OPERATION_POST_OFFICE, office.getIdentifier());
+
+    final Employee employee = EmployeeFactory.createRandomEmployee();
+    employee.setAssignedOffice(office.getIdentifier());
+    this.officeClient.createEmployee(employee);
+
+    this.eventRecorder.wait(EventConstants.OPERATION_POST_EMPLOYEE, employee.getIdentifier());
+
+    final EmployeePage employeePage = this.officeClient.fetchEmployees(null, office.getIdentifier(), 0, 20, null, null);
+    Assert.assertEquals(Long.valueOf(1L), employeePage.getTotalElements());
+    final Employee savedEmployee = employeePage.getEmployees().get(0);
+    Assert.assertEquals(employee.getIdentifier(), savedEmployee.getIdentifier());
+    Assert.assertEquals(employee.getGivenName(), savedEmployee.getGivenName());
+    Assert.assertEquals(employee.getMiddleName(), savedEmployee.getMiddleName());
+    Assert.assertEquals(employee.getSurname(), savedEmployee.getSurname());
+
+    this.officeClient.deleteEmployee(employee.getIdentifier());
+    this.eventRecorder.wait(EventConstants.OPERATION_DELETE_EMPLOYEE, employee.getIdentifier());
+    this.officeClient.deleteOffice(office.getIdentifier());
+    this.eventRecorder.wait(EventConstants.OPERATION_DELETE_OFFICE, office.getIdentifier());
+  }
+
+  @Test
+  public void shouldFindAllEmployees() throws Exception {
+    final Employee firstEmployee = EmployeeFactory.createRandomEmployee();
+    this.officeClient.createEmployee(firstEmployee);
+    this.eventRecorder.wait(EventConstants.OPERATION_POST_EMPLOYEE, firstEmployee.getIdentifier());
+    final Employee secondEmployee = EmployeeFactory.createRandomEmployee();
+    this.officeClient.createEmployee(secondEmployee);
+    this.eventRecorder.wait(EventConstants.OPERATION_POST_EMPLOYEE, secondEmployee.getIdentifier());
+
+    final EmployeePage employeePage = this.officeClient.fetchEmployees(null, null, 0, 20, null, null);
+    Assert.assertEquals(Long.valueOf(2L), employeePage.getTotalElements());
+
+    this.officeClient.deleteEmployee(firstEmployee.getIdentifier());
+    this.eventRecorder.wait(EventConstants.OPERATION_DELETE_EMPLOYEE, firstEmployee.getIdentifier());
+    this.officeClient.deleteEmployee(secondEmployee.getIdentifier());
+    this.eventRecorder.wait(EventConstants.OPERATION_DELETE_EMPLOYEE, secondEmployee.getIdentifier());
+  }
+
+  @Test
+  public void shouldDeleteEmployee() throws Exception {
+    final Employee employee = EmployeeFactory.createRandomEmployee();
+    this.officeClient.createEmployee(employee);
+    this.eventRecorder.wait(EventConstants.OPERATION_POST_EMPLOYEE, employee.getIdentifier());
+    Assert.assertNotNull(this.officeClient.findEmployee(employee.getIdentifier()));
+
+    {
+      this.officeClient.deleteEmployee(employee.getIdentifier());
+      final boolean found = this.eventRecorder.wait(EventConstants.OPERATION_DELETE_EMPLOYEE, employee.getIdentifier());
+      Assert.assertTrue(found);
+    }
+
+    try {
+      this.officeClient.findEmployee(employee.getIdentifier());
+      Assert.fail();
+    } catch (final NotFoundException ex) {
+      // do nothing, expected
+    }
+  }
+
+  @Test
+  public void shouldUpdateEmployee() throws Exception {
+    final Employee employee = EmployeeFactory.createRandomEmployee();
+    this.officeClient.createEmployee(employee);
+    this.eventRecorder.wait(EventConstants.OPERATION_POST_EMPLOYEE, employee.getIdentifier());
+
+    final Office office = OfficeFactory.createRandomOffice();
+    this.officeClient.createOffice(office);
+    this.eventRecorder.wait(EventConstants.OPERATION_POST_OFFICE, office.getIdentifier());
+
+    final Employee updatedEmployee = EmployeeFactory.createRandomEmployee();
+    updatedEmployee.setIdentifier(employee.getIdentifier());
+    updatedEmployee.setAssignedOffice(office.getIdentifier());
+
+    {
+      this.officeClient.updateEmployee(employee.getIdentifier(), updatedEmployee);
+      final boolean found = this.eventRecorder.wait(EventConstants.OPERATION_PUT_EMPLOYEE, employee.getIdentifier());
+      Assert.assertTrue(found);
+    }
+
+    final Employee savedEmployee = this.officeClient.findEmployee(employee.getIdentifier());
+    Assert.assertNotNull(savedEmployee);
+    Assert.assertEquals(updatedEmployee.getIdentifier(), savedEmployee.getIdentifier());
+    Assert.assertEquals(updatedEmployee.getGivenName(), savedEmployee.getGivenName());
+    Assert.assertEquals(updatedEmployee.getMiddleName(), savedEmployee.getMiddleName());
+    Assert.assertEquals(updatedEmployee.getSurname(), savedEmployee.getSurname());
+    Assert.assertEquals(updatedEmployee.getAssignedOffice(), savedEmployee.getAssignedOffice());
+
+    {
+      this.officeClient.deleteEmployee(employee.getIdentifier());
+      final boolean found = this.eventRecorder.wait(EventConstants.OPERATION_DELETE_EMPLOYEE, employee.getIdentifier());
+      Assert.assertTrue(found);
+    }
+  }
+
+  @Test
+  public void shouldNotUpdateEmployeeCodeMismatch() throws Exception {
+    final Employee employee = EmployeeFactory.createRandomEmployee();
+    this.officeClient.createEmployee(employee);
+    this.eventRecorder.wait(EventConstants.OPERATION_POST_EMPLOYEE, employee.getIdentifier());
+
+    final String originalCode = employee.getIdentifier();
+    employee.setIdentifier(RandomStringUtils.randomAlphanumeric(8));
+
+    try {
+      this.officeClient.updateEmployee(originalCode, employee);
+      Assert.fail();
+    } catch (final BadRequestException ex) {
+      // do nothing, expected
+    }
+
+    this.officeClient.deleteEmployee(originalCode);
+    this.eventRecorder.wait(EventConstants.OPERATION_DELETE_EMPLOYEE, originalCode);
+  }
+
+  @Test
+  public void shouldNotUpdateEmployeeNotFound() throws Exception {
+    final Employee employee = EmployeeFactory.createRandomEmployee();
+    this.officeClient.createEmployee(employee);
+    this.eventRecorder.wait(EventConstants.OPERATION_POST_EMPLOYEE, employee.getIdentifier());
+
+    try {
+      this.officeClient.updateEmployee(RandomStringUtils.randomAlphanumeric(8), employee);
+      Assert.fail();
+    } catch (final NotFoundException ex) {
+      // do nothing, expected
+    }
+
+    this.officeClient.deleteEmployee(employee.getIdentifier());
+    this.eventRecorder.wait(EventConstants.OPERATION_DELETE_EMPLOYEE, employee.getIdentifier());
+  }
+
+  @Test
+  public void shouldSetContactDetailOfEmployee() throws Exception {
+    final Employee employee = EmployeeFactory.createRandomEmployee();
+    this.officeClient.createEmployee(employee);
+    this.eventRecorder.wait(EventConstants.OPERATION_POST_EMPLOYEE, employee.getIdentifier());
+
+    final ContactDetail email = new ContactDetail();
+    email.setType(ContactDetail.Type.EMAIL.name());
+    email.setGroup(ContactDetail.Group.PRIVATE.name());
+    email.setValue("test@example.org");
+    email.setPreferenceLevel(1);
+
+    final ContactDetail phone = new ContactDetail();
+    phone.setType(ContactDetail.Type.PHONE.name());
+    phone.setGroup(ContactDetail.Group.PRIVATE.name());
+    phone.setValue("123456789");
+    phone.setPreferenceLevel(2);
+
+    {
+      this.officeClient.setContactDetails(employee.getIdentifier(), Arrays.asList(email, phone));
+      final boolean found = this.eventRecorder.wait(EventConstants.OPERATION_PUT_CONTACT_DETAIL, employee.getIdentifier());
+      Assert.assertTrue(found);
+    }
+
+    final List<ContactDetail> savedContactDetails = this.officeClient.fetchContactDetails(employee.getIdentifier());
+    Assert.assertNotNull(savedContactDetails);
+    Assert.assertEquals(2, savedContactDetails.size());
+
+    final ContactDetail savedEmail = savedContactDetails.get(0);
+    Assert.assertEquals(email.getType(), savedEmail.getType());
+    Assert.assertEquals(email.getGroup(), savedEmail.getGroup());
+    Assert.assertEquals(email.getValue(), savedEmail.getValue());
+    Assert.assertEquals(email.getPreferenceLevel(), savedEmail.getPreferenceLevel());
+
+    final ContactDetail savedPhone = savedContactDetails.get(1);
+    Assert.assertEquals(phone.getType(), savedPhone.getType());
+    Assert.assertEquals(phone.getGroup(), savedPhone.getGroup());
+    Assert.assertEquals(phone.getValue(), savedPhone.getValue());
+    Assert.assertEquals(phone.getPreferenceLevel(), savedPhone.getPreferenceLevel());
+
+    {
+      this.officeClient.deleteEmployee(employee.getIdentifier());
+      final boolean found = this.eventRecorder.wait(EventConstants.OPERATION_DELETE_EMPLOYEE, employee.getIdentifier());
+      Assert.assertTrue(found);
+    }
+  }
+
+  @Test
+  public void shouldNotSetContactDetailEmployeeNotFound() throws Exception {
+    final ContactDetail contactDetail = new ContactDetail();
+    contactDetail.setType(ContactDetail.Type.EMAIL.name());
+    contactDetail.setGroup(ContactDetail.Group.BUSINESS.name());
+    contactDetail.setValue("employee@example.com");
+
+    try {
+      this.officeClient.setContactDetails(RandomStringUtils.randomAlphanumeric(8), Collections.singletonList(contactDetail));
+      Assert.fail();
+    } catch (final NotFoundException ex) {
+      // do nothing, expected
+    }
+  }
+
+  @Test
+  public void shouldDeleteContactDetailOfEmployee() throws Exception {
+    final Employee employee = EmployeeFactory.createRandomEmployee();
+    this.officeClient.createEmployee(employee);
+    this.eventRecorder.wait(EventConstants.OPERATION_POST_EMPLOYEE, employee.getIdentifier());
+
+    final ContactDetail email = new ContactDetail();
+    email.setType(ContactDetail.Type.EMAIL.name());
+    email.setGroup(ContactDetail.Group.PRIVATE.name());
+    email.setValue("test@example.org");
+    email.setPreferenceLevel(1);
+
+    final ContactDetail phone = new ContactDetail();
+    phone.setType(ContactDetail.Type.PHONE.name());
+    phone.setGroup(ContactDetail.Group.PRIVATE.name());
+    phone.setValue("123456789");
+    phone.setPreferenceLevel(2);
+
+    {
+      this.officeClient.setContactDetails(employee.getIdentifier(), Arrays.asList(email, phone));
+      final boolean found = this.eventRecorder.wait(EventConstants.OPERATION_PUT_CONTACT_DETAIL, employee.getIdentifier());
+      Assert.assertTrue(found);
+    }
+
+    Assert.assertNotNull(this.officeClient.fetchContactDetails(employee.getIdentifier()));
+
+    {
+      this.officeClient.deleteContactDetails(employee.getIdentifier());
+      final boolean found = this.eventRecorder.wait(EventConstants.OPERATION_DELETE_CONTACT_DETAIL, employee.getIdentifier());
+      Assert.assertTrue(found);
+    }
+
+    Assert.assertTrue(this.officeClient.fetchContactDetails(employee.getIdentifier()).isEmpty());
+
+    this.officeClient.deleteEmployee(employee.getIdentifier());
+    this.eventRecorder.wait(EventConstants.OPERATION_DELETE_EMPLOYEE, employee.getIdentifier());
+  }
+
+  @Configuration
+  @ComponentScan(
+      basePackages = "io.mifos.office.listener"
+  )
+  @EnableFeignClients(basePackages = {"io.mifos.office.api.v1.client"})
+  @RibbonClient(name = APP_NAME)
+  @EnableEventRecording(maxWait = 5000L)
+  @Import({OfficeRestConfiguration.class})
+  public static class TestConfiguration {
+    public TestConfiguration() {
+      super();
+    }
+
+    @Bean
+    public Logger logger() {
+      return LoggerFactory.getLogger("office-test-logger");
+    }
+  }
+}
diff --git a/component-test/src/main/java/io/mifos/office/TestOffice.java b/component-test/src/main/java/io/mifos/office/TestOffice.java
new file mode 100644
index 0000000..9a1bd26
--- /dev/null
+++ b/component-test/src/main/java/io/mifos/office/TestOffice.java
@@ -0,0 +1,302 @@
+/*
+ * 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.office;
+
+import io.mifos.anubis.test.v1.TenantApplicationSecurityEnvironmentTestRule;
+import io.mifos.core.api.context.AutoUserContext;
+import io.mifos.core.test.env.TestEnvironment;
+import io.mifos.core.test.fixture.TenantDataStoreContextTestRule;
+import io.mifos.core.test.fixture.cassandra.CassandraInitializer;
+import io.mifos.core.test.fixture.mariadb.MariaDBInitializer;
+import io.mifos.core.test.listener.EnableEventRecording;
+import io.mifos.core.test.listener.EventRecorder;
+import io.mifos.office.api.v1.EventConstants;
+import io.mifos.office.api.v1.client.AlreadyExistsException;
+import io.mifos.office.api.v1.client.BadRequestException;
+import io.mifos.office.api.v1.client.OfficeClient;
+import io.mifos.office.api.v1.client.NotFoundException;
+import io.mifos.office.api.v1.domain.Address;
+import io.mifos.office.api.v1.domain.Office;
+import io.mifos.office.api.v1.domain.OfficePage;
+import io.mifos.office.rest.config.OfficeRestConfiguration;
+import io.mifos.office.util.AddressFactory;
+import io.mifos.office.util.OfficeFactory;
+import org.apache.commons.lang3.RandomStringUtils;
+import org.junit.*;
+import org.junit.rules.RuleChain;
+import org.junit.rules.TestRule;
+import org.junit.runner.RunWith;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.cloud.netflix.feign.EnableFeignClients;
+import org.springframework.cloud.netflix.ribbon.RibbonClient;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.ComponentScan;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Import;
+import org.springframework.test.context.junit4.SpringRunner;
+
+@RunWith(SpringRunner.class)
+@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT)
+public class TestOffice {
+  private static final String APP_NAME = "office-v1";
+  private static final String TEST_USER = "thutmosis";
+
+  private final static TestEnvironment testEnvironment = new TestEnvironment(APP_NAME);
+  private final static CassandraInitializer cassandraInitializer = new CassandraInitializer();
+  private final static MariaDBInitializer mariaDBInitializer = new MariaDBInitializer();
+  private final static TenantDataStoreContextTestRule tenantDataStoreContext = TenantDataStoreContextTestRule.forRandomTenantName(cassandraInitializer, mariaDBInitializer);
+
+  @ClassRule
+  public static TestRule orderClassRules = RuleChain
+          .outerRule(testEnvironment)
+          .around(cassandraInitializer)
+          .around(mariaDBInitializer)
+          .around(tenantDataStoreContext);
+
+  @Rule
+  public final TenantApplicationSecurityEnvironmentTestRule tenantApplicationSecurityEnvironment
+          = new TenantApplicationSecurityEnvironmentTestRule(testEnvironment, this::waitForInitialize);
+
+  @Autowired
+  private OfficeClient officeClient;
+
+  @Autowired
+  private EventRecorder eventRecorder;
+
+  private AutoUserContext userContext;
+
+  @Before
+  public void prepareTest() {
+    userContext = tenantApplicationSecurityEnvironment.createAutoUserContext(TestOffice.TEST_USER);
+  }
+
+  @After
+  public void cleanupTest() {
+    userContext.close();
+  }
+
+  public boolean waitForInitialize() {
+    try {
+      return this.eventRecorder.wait(EventConstants.INITIALIZE, EventConstants.INITIALIZE);
+    } catch (final InterruptedException e) {
+      throw new IllegalStateException(e);
+    }
+  }
+
+  @Test
+  public void shouldCreateOffice() throws Exception {
+    final Office office = OfficeFactory.createRandomOffice();
+    this.officeClient.createOffice(office);
+    this.eventRecorder.wait(EventConstants.OPERATION_POST_OFFICE, office.getIdentifier());
+
+    final Office savedOffice = this.officeClient.findOfficeByIdentifier(office.getIdentifier());
+    Assert.assertNotNull(savedOffice);
+    Assert.assertEquals(office.getIdentifier(), savedOffice.getIdentifier());
+    Assert.assertEquals(office.getName(), savedOffice.getName());
+    Assert.assertEquals(office.getDescription(), savedOffice.getDescription());
+
+    this.officeClient.deleteOffice(office.getIdentifier());
+    this.eventRecorder.wait(EventConstants.OPERATION_DELETE_OFFICE, office.getIdentifier());
+  }
+
+  @Test
+  public void shouldNotCreateOfficeDuplicate() throws Exception {
+    final Office office = OfficeFactory.createRandomOffice();
+    this.officeClient.createOffice(office);
+    this.eventRecorder.wait(EventConstants.OPERATION_POST_OFFICE, office.getIdentifier());
+    try {
+      this.officeClient.createOffice(office);
+      Assert.fail();
+    } catch (final AlreadyExistsException ex) {
+      // do nothing, expected
+    }
+
+    this.officeClient.deleteOffice(office.getIdentifier());
+    this.eventRecorder.wait(EventConstants.OPERATION_DELETE_OFFICE, office.getIdentifier());
+  }
+
+  @Test
+  public void shouldUpdateOffice() throws Exception {
+    final Office office = OfficeFactory.createRandomOffice();
+    this.officeClient.createOffice(office);
+    this.eventRecorder.wait(EventConstants.OPERATION_POST_OFFICE, office.getIdentifier());
+
+    final String modifiedOfficeName = RandomStringUtils.randomAlphanumeric(32);
+    office.setName(modifiedOfficeName);
+
+    this.officeClient.updateOffice(office.getIdentifier(), office);
+    this.eventRecorder.wait(EventConstants.OPERATION_PUT_OFFICE, office.getIdentifier());
+
+    final Office changedOffice = this.officeClient.findOfficeByIdentifier(office.getIdentifier());
+    Assert.assertEquals(modifiedOfficeName, changedOffice.getName());
+
+    this.officeClient.deleteOffice(office.getIdentifier());
+    this.eventRecorder.wait(EventConstants.OPERATION_DELETE_OFFICE, office.getIdentifier());
+  }
+
+  @Test
+  public void shouldNotUpdateOfficeIdentifierMismatch() throws Exception {
+    final Office office = OfficeFactory.createRandomOffice();
+    this.officeClient.createOffice(office);
+    this.eventRecorder.wait(EventConstants.OPERATION_POST_OFFICE, office.getIdentifier());
+
+    final String originalIdentifier = office.getIdentifier();
+    office.setIdentifier(RandomStringUtils.randomAlphanumeric(32));
+
+    try {
+      this.officeClient.updateOffice(originalIdentifier, office);
+      Assert.fail();
+    } catch (final BadRequestException ex) {
+      // do nothing, expected
+    }
+    this.officeClient.deleteOffice(originalIdentifier);
+    this.eventRecorder.wait(EventConstants.OPERATION_DELETE_OFFICE, office.getIdentifier());
+  }
+
+  @Test
+  public void shouldAddBranch() throws Exception {
+    final Office office = OfficeFactory.createRandomOffice();
+    this.officeClient.createOffice(office);
+    this.eventRecorder.wait(EventConstants.OPERATION_POST_OFFICE, office.getIdentifier());
+
+    final Office branch = OfficeFactory.createRandomOffice();
+    this.officeClient.addBranch(office.getIdentifier(), branch);
+    this.eventRecorder.wait(EventConstants.OPERATION_POST_OFFICE, branch.getIdentifier());
+
+    final OfficePage officePage = this.officeClient.getBranches(office.getIdentifier(), 0, 10, null, null);
+    Assert.assertEquals(Long.valueOf(1L), officePage.getTotalElements());
+
+    final Office savedBranch = officePage.getOffices().get(0);
+    Assert.assertEquals(branch.getIdentifier(), savedBranch.getIdentifier());
+
+    this.officeClient.deleteOffice(branch.getIdentifier());
+    this.eventRecorder.wait(EventConstants.OPERATION_DELETE_OFFICE, branch.getIdentifier());
+    this.officeClient.deleteOffice(office.getIdentifier());
+    this.eventRecorder.wait(EventConstants.OPERATION_DELETE_OFFICE, office.getIdentifier());
+  }
+
+  @Test
+  public void shouldNotAddBranchParentNotFound() throws Exception {
+    try {
+      final Office branch = OfficeFactory.createRandomOffice();
+      this.officeClient.addBranch(RandomStringUtils.randomAlphanumeric(32), branch);
+      Assert.fail();
+    } catch (final NotFoundException ex) {
+      // do nothing, expected
+    }
+  }
+
+  @Test
+  public void shouldSetAddressOfOffice() throws Exception {
+    final Office office = OfficeFactory.createRandomOffice();
+    this.officeClient.createOffice(office);
+    this.eventRecorder.wait(EventConstants.OPERATION_POST_OFFICE, office.getIdentifier());
+
+    final Address address = AddressFactory.createRandomAddress();
+    this.officeClient.setAddressForOffice(office.getIdentifier(), address);
+    this.eventRecorder.wait(EventConstants.OPERATION_PUT_ADDRESS, office.getIdentifier());
+
+    final Address savedAddress = this.officeClient.getAddressOfOffice(office.getIdentifier());
+    Assert.assertNotNull(savedAddress);
+    Assert.assertEquals(address.getStreet(), savedAddress.getStreet());
+    Assert.assertEquals(address.getCity(), savedAddress.getCity());
+    Assert.assertEquals(address.getRegion(), savedAddress.getRegion());
+    Assert.assertEquals(address.getPostalCode(), savedAddress.getPostalCode());
+    Assert.assertEquals(address.getCountryCode(), savedAddress.getCountryCode());
+    Assert.assertEquals(address.getCountry(), savedAddress.getCountry());
+
+    this.officeClient.deleteOffice(office.getIdentifier());
+    this.eventRecorder.wait(EventConstants.OPERATION_DELETE_OFFICE, office.getIdentifier());
+  }
+
+  @Test
+  public void shouldGetAddressOfOffice() throws Exception {
+    final Office office = OfficeFactory.createRandomOffice();
+    final Address address = AddressFactory.createRandomAddress();
+    office.setAddress(address);
+    this.officeClient.createOffice(office);
+    this.eventRecorder.wait(EventConstants.OPERATION_POST_OFFICE, office.getIdentifier());
+
+    final Address savedAddress = this.officeClient.getAddressOfOffice(office.getIdentifier());
+    Assert.assertNotNull(savedAddress);
+    Assert.assertEquals(address.getStreet(), savedAddress.getStreet());
+    Assert.assertEquals(address.getCity(), savedAddress.getCity());
+    Assert.assertEquals(address.getRegion(), savedAddress.getRegion());
+    Assert.assertEquals(address.getPostalCode(), savedAddress.getPostalCode());
+    Assert.assertEquals(address.getCountryCode(), savedAddress.getCountryCode());
+    Assert.assertEquals(address.getCountry(), savedAddress.getCountry());
+
+    this.officeClient.deleteOffice(office.getIdentifier());
+    this.eventRecorder.wait(EventConstants.OPERATION_DELETE_OFFICE, office.getIdentifier());
+  }
+
+  @Test
+  public void shouldDeleteAddressOfOffice() throws Exception {
+    final Office office = OfficeFactory.createRandomOffice();
+    final Address address = AddressFactory.createRandomAddress();
+    office.setAddress(address);
+    this.officeClient.createOffice(office);
+    this.eventRecorder.wait(EventConstants.OPERATION_POST_OFFICE, office.getIdentifier());
+
+    final Address savedAddress = this.officeClient.getAddressOfOffice(office.getIdentifier());
+    Assert.assertNotNull(savedAddress);
+
+    this.officeClient.deleteAddressOfOffice(office.getIdentifier());
+    this.eventRecorder.wait(EventConstants.OPERATION_DELETE_ADDRESS, office.getIdentifier());
+
+    Assert.assertNull(this.officeClient.getAddressOfOffice(office.getIdentifier()));
+
+    this.officeClient.deleteOffice(office.getIdentifier());
+    this.eventRecorder.wait(EventConstants.OPERATION_DELETE_OFFICE, office.getIdentifier());
+  }
+
+  @Test
+  public void shouldReturnParentOfBranch() throws Exception {
+    final Office parent = OfficeFactory.createRandomOffice();
+    this.officeClient.createOffice(parent);
+    this.eventRecorder.wait(EventConstants.OPERATION_POST_OFFICE, parent.getIdentifier());
+
+    final Office branch = OfficeFactory.createRandomOffice();
+    this.officeClient.addBranch(parent.getIdentifier(), branch);
+    this.eventRecorder.wait(EventConstants.OPERATION_POST_OFFICE, branch.getIdentifier());
+
+    final Office savedBranch = this.officeClient.findOfficeByIdentifier(branch.getIdentifier());
+
+    Assert.assertEquals(parent.getIdentifier(), savedBranch.getParentIdentifier());
+  }
+
+  @Configuration
+  @ComponentScan(
+      basePackages = "io.mifos.office.listener"
+  )
+  @EnableFeignClients(basePackages = {"io.mifos.office.api.v1.client"})
+  @RibbonClient(name = APP_NAME)
+  @EnableEventRecording(maxWait = 5000L)
+  @Import({OfficeRestConfiguration.class})
+  public static class TestConfiguration {
+    public TestConfiguration() {
+      super();
+    }
+
+    @Bean
+    public Logger logger() {
+      return LoggerFactory.getLogger("office-test-logger");
+    }
+  }
+}
diff --git a/component-test/src/main/java/io/mifos/office/listener/EmployeeEventListener.java b/component-test/src/main/java/io/mifos/office/listener/EmployeeEventListener.java
new file mode 100644
index 0000000..918d4c5
--- /dev/null
+++ b/component-test/src/main/java/io/mifos/office/listener/EmployeeEventListener.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.office.listener;
+
+import io.mifos.core.lang.config.TenantHeaderFilter;
+import io.mifos.core.test.listener.EventRecorder;
+import io.mifos.office.api.v1.EventConstants;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.jms.annotation.JmsListener;
+import org.springframework.messaging.handler.annotation.Header;
+import org.springframework.stereotype.Component;
+
+@Component
+public class EmployeeEventListener {
+
+  private final EventRecorder eventRecorder;
+
+  @Autowired
+  public EmployeeEventListener(final EventRecorder eventRecorder) {
+    super();
+    this.eventRecorder = eventRecorder;
+  }
+
+  @JmsListener(
+      subscription = EventConstants.DESTINATION,
+      destination = EventConstants.DESTINATION,
+      selector = EventConstants.SELECTOR_POST_EMPLOYEE
+  )
+  public void onCreateEmployee(@Header(TenantHeaderFilter.TENANT_HEADER) final String tenant,
+                               final String eventPayload) throws Exception {
+    this.eventRecorder.event(tenant, EventConstants.OPERATION_POST_EMPLOYEE, eventPayload, String.class);
+  }
+
+  @JmsListener(
+      subscription = EventConstants.DESTINATION,
+      destination = EventConstants.DESTINATION,
+      selector = EventConstants.SELECTOR_PUT_EMPLOYEE
+  )
+  public void onUpdateEmployee(@Header(TenantHeaderFilter.TENANT_HEADER) final String tenant,
+                               final String eventPayload) throws Exception {
+    this.eventRecorder.event(tenant, EventConstants.OPERATION_PUT_EMPLOYEE, eventPayload, String.class);
+  }
+
+  @JmsListener(
+      subscription = EventConstants.DESTINATION,
+      destination = EventConstants.DESTINATION,
+      selector = EventConstants.SELECTOR_DELETE_EMPLOYEE
+  )
+  public void onDeleteEmployee(@Header(TenantHeaderFilter.TENANT_HEADER) final String tenant,
+                               final String eventPayload) throws Exception {
+    this.eventRecorder.event(tenant, EventConstants.OPERATION_DELETE_EMPLOYEE, eventPayload, String.class);
+  }
+
+  @JmsListener(
+      subscription = EventConstants.DESTINATION,
+      destination = EventConstants.DESTINATION,
+      selector = EventConstants.SELECTOR_PUT_CONTACT_DETAIL
+  )
+  public void onSetContactDetail(@Header(TenantHeaderFilter.TENANT_HEADER) final String tenant,
+                                 final String eventPayload) throws Exception {
+    this.eventRecorder.event(tenant, EventConstants.OPERATION_PUT_CONTACT_DETAIL, eventPayload, String.class);
+  }
+
+  @JmsListener(
+      subscription = EventConstants.DESTINATION,
+      destination = EventConstants.DESTINATION,
+      selector = EventConstants.SELECTOR_DELETE_CONTACT_DETAIL
+  )
+  public void onDeleteContactDetail(@Header(TenantHeaderFilter.TENANT_HEADER) final String tenant,
+                                    final String eventPayload) throws Exception {
+    this.eventRecorder.event(tenant, EventConstants.OPERATION_DELETE_CONTACT_DETAIL, eventPayload, String.class);
+  }
+}
diff --git a/component-test/src/main/java/io/mifos/office/listener/MigrationEventListener.java b/component-test/src/main/java/io/mifos/office/listener/MigrationEventListener.java
new file mode 100644
index 0000000..4005e21
--- /dev/null
+++ b/component-test/src/main/java/io/mifos/office/listener/MigrationEventListener.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2017 The Mifos Initiative
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.mifos.office.listener;
+
+import io.mifos.core.lang.config.TenantHeaderFilter;
+import io.mifos.core.test.listener.EventRecorder;
+import io.mifos.office.api.v1.EventConstants;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.jms.annotation.JmsListener;
+import org.springframework.messaging.handler.annotation.Header;
+import org.springframework.stereotype.Component;
+
+@SuppressWarnings("unused")
+@Component
+public class MigrationEventListener {
+
+  private final EventRecorder eventRecorder;
+
+  @Autowired
+  public MigrationEventListener(final EventRecorder eventRecorder) {
+    super();
+    this.eventRecorder = eventRecorder;
+  }
+
+  @JmsListener(
+      subscription = EventConstants.DESTINATION,
+      destination = EventConstants.DESTINATION,
+      selector = EventConstants.SELECTOR_INITIALIZE
+  )
+  public void onInitialized(@Header(TenantHeaderFilter.TENANT_HEADER) final String tenant,
+                            final String payload) {
+    this.eventRecorder.event(tenant, EventConstants.INITIALIZE, payload, String.class);
+  }
+}
diff --git a/component-test/src/main/java/io/mifos/office/listener/OfficeEventListener.java b/component-test/src/main/java/io/mifos/office/listener/OfficeEventListener.java
new file mode 100644
index 0000000..ff22b43
--- /dev/null
+++ b/component-test/src/main/java/io/mifos/office/listener/OfficeEventListener.java
@@ -0,0 +1,97 @@
+/*
+ * 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.office.listener;
+
+import com.google.gson.Gson;
+import io.mifos.core.command.util.CommandConstants;
+import io.mifos.core.lang.config.TenantHeaderFilter;
+import io.mifos.core.test.listener.EventRecorder;
+import io.mifos.office.api.v1.EventConstants;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.jms.annotation.JmsListener;
+import org.springframework.messaging.handler.annotation.Header;
+import org.springframework.stereotype.Component;
+
+@Component
+public class OfficeEventListener {
+
+  private final Gson gson;
+  private final EventRecorder eventRecorder;
+
+  @Autowired
+  public OfficeEventListener(@Qualifier(CommandConstants.SERIALIZER) final Gson gson,
+                             final EventRecorder eventRecorder) {
+    super();
+    this.gson = gson;
+    this.eventRecorder = eventRecorder;
+  }
+
+  @JmsListener(
+      subscription = EventConstants.DESTINATION,
+      destination = EventConstants.DESTINATION,
+      selector = EventConstants.SELECTOR_POST_OFFICE
+  )
+  public void onCreateOffice(@Header(TenantHeaderFilter.TENANT_HEADER) final String tenant,
+                             final String payload)
+      throws Exception {
+    this.eventRecorder.event(tenant, EventConstants.OPERATION_POST_OFFICE, payload, String.class);
+  }
+
+  @JmsListener(
+      subscription = EventConstants.DESTINATION,
+      destination = EventConstants.DESTINATION,
+      selector = EventConstants.SELECTOR_DELETE_OFFICE
+  )
+  public void onDeleteOffice(@Header(TenantHeaderFilter.TENANT_HEADER) final String tenant,
+                             final String payload)
+      throws Exception {
+    this.eventRecorder.event(tenant, EventConstants.OPERATION_DELETE_OFFICE, payload, String.class);
+  }
+
+  @JmsListener(
+      subscription = EventConstants.DESTINATION,
+      destination = EventConstants.DESTINATION,
+      selector = EventConstants.SELECTOR_PUT_OFFICE
+  )
+  public void onUpdateOffice(@Header(TenantHeaderFilter.TENANT_HEADER) final String tenant,
+                             final String payload)
+      throws Exception {
+    this.eventRecorder.event(tenant, EventConstants.OPERATION_PUT_OFFICE, payload, String.class);
+  }
+
+  @JmsListener(
+      subscription = EventConstants.DESTINATION,
+      destination = EventConstants.DESTINATION,
+      selector = EventConstants.SELECTOR_PUT_ADDRESS
+  )
+  public void onSetAddress(@Header(TenantHeaderFilter.TENANT_HEADER) final String tenant,
+                           final String payload)
+      throws Exception {
+    this.eventRecorder.event(tenant, EventConstants.OPERATION_PUT_ADDRESS, payload, String.class);
+  }
+
+  @JmsListener(
+      subscription = EventConstants.DESTINATION,
+      destination = EventConstants.DESTINATION,
+      selector = EventConstants.SELECTOR_DELETE_ADDRESS
+  )
+  public void onDeleteAddress(@Header(TenantHeaderFilter.TENANT_HEADER) final String tenant,
+                              final String payload)
+      throws Exception {
+    this.eventRecorder.event(tenant, EventConstants.OPERATION_DELETE_ADDRESS, payload, String.class);
+  }
+}
diff --git a/component-test/src/main/java/io/mifos/office/util/AddressFactory.java b/component-test/src/main/java/io/mifos/office/util/AddressFactory.java
new file mode 100644
index 0000000..b1824d2
--- /dev/null
+++ b/component-test/src/main/java/io/mifos/office/util/AddressFactory.java
@@ -0,0 +1,37 @@
+/*
+ * 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.office.util;
+
+import io.mifos.office.api.v1.domain.Address;
+import org.apache.commons.lang3.RandomStringUtils;
+
+public class AddressFactory {
+
+  private AddressFactory() {
+    super();
+  }
+
+  public static Address createRandomAddress() {
+    final Address address = new Address();
+    address.setStreet(RandomStringUtils.randomAlphanumeric(16));
+    address.setCity(RandomStringUtils.randomAlphanumeric(16));
+    address.setRegion(RandomStringUtils.randomAlphanumeric(16));
+    address.setPostalCode(RandomStringUtils.randomAlphabetic(6));
+    address.setCountryCode(RandomStringUtils.randomAlphanumeric(2));
+    address.setCountry(RandomStringUtils.randomAlphanumeric(16));
+    return address;
+  }
+}
diff --git a/component-test/src/main/java/io/mifos/office/util/EmployeeFactory.java b/component-test/src/main/java/io/mifos/office/util/EmployeeFactory.java
new file mode 100644
index 0000000..79eb469
--- /dev/null
+++ b/component-test/src/main/java/io/mifos/office/util/EmployeeFactory.java
@@ -0,0 +1,35 @@
+/*
+ * 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.office.util;
+
+import io.mifos.office.api.v1.domain.Employee;
+import org.apache.commons.lang3.RandomStringUtils;
+
+public class EmployeeFactory {
+
+  private EmployeeFactory() {
+    super();
+  }
+
+  public static Employee createRandomEmployee() {
+    final Employee employee = new Employee();
+    employee.setIdentifier(RandomStringUtils.randomAlphanumeric(32));
+    employee.setGivenName(RandomStringUtils.randomAlphanumeric(256));
+    employee.setMiddleName(RandomStringUtils.randomAlphanumeric(256));
+    employee.setSurname(RandomStringUtils.randomAlphanumeric(256));
+    return employee;
+  }
+}
diff --git a/component-test/src/main/java/io/mifos/office/util/OfficeFactory.java b/component-test/src/main/java/io/mifos/office/util/OfficeFactory.java
new file mode 100644
index 0000000..065a206
--- /dev/null
+++ b/component-test/src/main/java/io/mifos/office/util/OfficeFactory.java
@@ -0,0 +1,34 @@
+/*
+ * 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.office.util;
+
+import io.mifos.office.api.v1.domain.Office;
+import org.apache.commons.lang3.RandomStringUtils;
+
+public class OfficeFactory {
+
+  private OfficeFactory() {
+    super();
+  }
+
+  public static Office createRandomOffice() {
+    final Office office = new Office();
+    office.setIdentifier(RandomStringUtils.randomAlphanumeric(32));
+    office.setName(RandomStringUtils.randomAlphanumeric(256));
+    office.setDescription(RandomStringUtils.randomAlphanumeric(2048));
+    return office;
+  }
+}
diff --git a/component-test/src/main/resources/logback.xml b/component-test/src/main/resources/logback.xml
new file mode 100644
index 0000000..cf70b1a
--- /dev/null
+++ b/component-test/src/main/resources/logback.xml
@@ -0,0 +1,40 @@
+<!--
+
+    Copyright 2017 The Mifos Initiative
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+
+-->
+<configuration>
+    <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
+        <file>office.log</file>
+        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
+            <fileNamePattern>office.%d{yyyy-MM-dd}.log</fileNamePattern>
+            <maxHistory>30</maxHistory>
+            <totalSizeCap>3GB</totalSizeCap>
+        </rollingPolicy>
+        <encoder>
+            <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
+        </encoder>
+    </appender>
+    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
+        <encoder>
+            <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
+        </encoder>
+    </appender>
+
+    <root level="INFO">
+        <appender-ref ref="FILE"/>
+        <appender-ref ref="STDOUT"/>
+    </root>
+</configuration>
\ No newline at end of file
diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 0000000..8272508
Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000..6b22de8
--- /dev/null
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,6 @@
+#Thu Mar 16 09:46:09 CET 2017
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-3.4.1-bin.zip
diff --git a/gradlew b/gradlew
new file mode 100755
index 0000000..4453cce
--- /dev/null
+++ b/gradlew
@@ -0,0 +1,172 @@
+#!/usr/bin/env sh
+
+##############################################################################
+##
+##  Gradle start up script for UN*X
+##
+##############################################################################
+
+# Attempt to set APP_HOME
+# Resolve links: $0 may be a link
+PRG="$0"
+# Need this for relative symlinks.
+while [ -h "$PRG" ] ; do
+    ls=`ls -ld "$PRG"`
+    link=`expr "$ls" : '.*-> \(.*\)$'`
+    if expr "$link" : '/.*' > /dev/null; then
+        PRG="$link"
+    else
+        PRG=`dirname "$PRG"`"/$link"
+    fi
+done
+SAVED="`pwd`"
+cd "`dirname \"$PRG\"`/" >/dev/null
+APP_HOME="`pwd -P`"
+cd "$SAVED" >/dev/null
+
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$0"`
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS=""
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD="maximum"
+
+warn ( ) {
+    echo "$*"
+}
+
+die ( ) {
+    echo
+    echo "$*"
+    echo
+    exit 1
+}
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+nonstop=false
+case "`uname`" in
+  CYGWIN* )
+    cygwin=true
+    ;;
+  Darwin* )
+    darwin=true
+    ;;
+  MINGW* )
+    msys=true
+    ;;
+  NONSTOP* )
+    nonstop=true
+    ;;
+esac
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+    if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+        # IBM's JDK on AIX uses strange locations for the executables
+        JAVACMD="$JAVA_HOME/jre/sh/java"
+    else
+        JAVACMD="$JAVA_HOME/bin/java"
+    fi
+    if [ ! -x "$JAVACMD" ] ; then
+        die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+    fi
+else
+    JAVACMD="java"
+    which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
+    MAX_FD_LIMIT=`ulimit -H -n`
+    if [ $? -eq 0 ] ; then
+        if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
+            MAX_FD="$MAX_FD_LIMIT"
+        fi
+        ulimit -n $MAX_FD
+        if [ $? -ne 0 ] ; then
+            warn "Could not set maximum file descriptor limit: $MAX_FD"
+        fi
+    else
+        warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
+    fi
+fi
+
+# For Darwin, add options to specify how the application appears in the dock
+if $darwin; then
+    GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
+fi
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin ; then
+    APP_HOME=`cygpath --path --mixed "$APP_HOME"`
+    CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+    JAVACMD=`cygpath --unix "$JAVACMD"`
+
+    # We build the pattern for arguments to be converted via cygpath
+    ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
+    SEP=""
+    for dir in $ROOTDIRSRAW ; do
+        ROOTDIRS="$ROOTDIRS$SEP$dir"
+        SEP="|"
+    done
+    OURCYGPATTERN="(^($ROOTDIRS))"
+    # Add a user-defined pattern to the cygpath arguments
+    if [ "$GRADLE_CYGPATTERN" != "" ] ; then
+        OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
+    fi
+    # Now convert the arguments - kludge to limit ourselves to /bin/sh
+    i=0
+    for arg in "$@" ; do
+        CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
+        CHECK2=`echo "$arg"|egrep -c "^-"`                                 ### Determine if an option
+
+        if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then                    ### Added a condition
+            eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
+        else
+            eval `echo args$i`="\"$arg\""
+        fi
+        i=$((i+1))
+    done
+    case $i in
+        (0) set -- ;;
+        (1) set -- "$args0" ;;
+        (2) set -- "$args0" "$args1" ;;
+        (3) set -- "$args0" "$args1" "$args2" ;;
+        (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+        (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+        (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+        (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+        (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+        (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+    esac
+fi
+
+# Escape application args
+save ( ) {
+    for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
+    echo " "
+}
+APP_ARGS=$(save "$@")
+
+# Collect all arguments for the java command, following the shell quoting and substitution rules
+eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
+
+# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
+if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
+  cd "$(dirname "$0")"
+fi
+
+exec "$JAVACMD" "$@"
diff --git a/gradlew.bat b/gradlew.bat
new file mode 100644
index 0000000..e95643d
--- /dev/null
+++ b/gradlew.bat
@@ -0,0 +1,84 @@
+@if "%DEBUG%" == "" @echo off
+@rem ##########################################################################
+@rem
+@rem  Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+set DIRNAME=%~dp0
+if "%DIRNAME%" == "" set DIRNAME=.
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS=
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if "%ERRORLEVEL%" == "0" goto init
+
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto init
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:init
+@rem Get command-line arguments, handling Windows variants
+
+if not "%OS%" == "Windows_NT" goto win9xME_args
+
+:win9xME_args
+@rem Slurp the command line arguments.
+set CMD_LINE_ARGS=
+set _SKIP=2
+
+:win9xME_args_slurp
+if "x%~1" == "x" goto execute
+
+set CMD_LINE_ARGS=%*
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
+
+:end
+@rem End local scope for the variables with windows NT shell
+if "%ERRORLEVEL%"=="0" goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+if  not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
+exit /b 1
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/service/build.gradle b/service/build.gradle
new file mode 100644
index 0000000..6dd2def
--- /dev/null
+++ b/service/build.gradle
@@ -0,0 +1,63 @@
+buildscript {
+    ext {
+        springBootVersion = '1.4.1.RELEASE'
+    }
+
+    repositories {
+        jcenter()
+    }
+
+    dependencies {
+        classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
+    }
+}
+
+plugins {
+    id "com.github.hierynomus.license" version "0.13.1"
+}
+
+apply from: '../shared.gradle'
+
+apply plugin: 'spring-boot'
+
+springBoot {
+    executable = true
+    classifier = 'boot'
+}
+
+dependencies {
+    compile(
+            [group: 'org.springframework.cloud', name: 'spring-cloud-starter-config'],
+            [group: 'org.springframework.cloud', name: 'spring-cloud-starter-eureka'],
+            [group: 'org.springframework.boot', name: 'spring-boot-starter-jetty'],
+            [group: 'org.hibernate', name: 'hibernate-validator', version: versions.validator],
+            [group: 'io.mifos.office', name: 'api', version: project.version],
+            [group: 'io.mifos.anubis', name: 'library', version: versions.frameworkanubis],
+            [group: 'com.google.code.gson', name: 'gson'],
+            [group: 'io.mifos.core', name: 'lang', version: versions.frameworklang],
+            [group: 'io.mifos.core', name: 'async', version: versions.frameworkasync],
+            [group: 'io.mifos.core', name: 'cassandra', version: versions.frameworkcassandra],
+            [group: 'io.mifos.core', name: 'mariadb', version: versions.frameworkmariadb],
+            [group: 'io.mifos.core', name: 'command', version: versions.frameworkcommand],
+    )
+}
+
+publishToMavenLocal.dependsOn bootRepackage
+
+publishing {
+    publications {
+        service(MavenPublication) {
+            from components.java
+            groupId project.group
+            artifactId project.name
+            version project.version
+        }
+        bootService(MavenPublication) {
+            // "boot" jar
+            artifact("$buildDir/libs/$project.name-$version-boot.jar")
+            groupId project.group
+            artifactId("$project.name-boot")
+            version project.version
+        }
+    }
+}
diff --git a/service/settings.gradle b/service/settings.gradle
new file mode 100644
index 0000000..081feb4
--- /dev/null
+++ b/service/settings.gradle
@@ -0,0 +1 @@
+rootProject.name = 'service'
\ No newline at end of file
diff --git a/service/src/main/java/io/mifos/office/OfficeApplication.java b/service/src/main/java/io/mifos/office/OfficeApplication.java
new file mode 100644
index 0000000..d9c5988
--- /dev/null
+++ b/service/src/main/java/io/mifos/office/OfficeApplication.java
@@ -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.
+ */
+package io.mifos.office;
+
+import io.mifos.office.rest.config.OfficeRestConfiguration;
+import org.springframework.boot.SpringApplication;
+
+public class OfficeApplication {
+
+  public OfficeApplication() {
+    super();
+  }
+
+  public static void main(String[] args) {
+    SpringApplication.run(OfficeRestConfiguration.class, args);
+  }
+}
diff --git a/service/src/main/java/io/mifos/office/ServiceConstants.java b/service/src/main/java/io/mifos/office/ServiceConstants.java
new file mode 100644
index 0000000..8f62474
--- /dev/null
+++ b/service/src/main/java/io/mifos/office/ServiceConstants.java
@@ -0,0 +1,26 @@
+/*
+ * 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.office;
+
+public interface ServiceConstants {
+
+  String SERVICE_LOGGER_NAME = "office-service-logger";
+  String REST_LOGGER_NAME = "office-rest-logger";
+
+  String JSON_SERIALIZER_NAME = "office-json-serializer";
+
+  String SHEUT_THE_UNKNOWN_USER = "sheut";
+}
diff --git a/service/src/main/java/io/mifos/office/internal/command/AddBranchCommand.java b/service/src/main/java/io/mifos/office/internal/command/AddBranchCommand.java
new file mode 100644
index 0000000..ce6a43f
--- /dev/null
+++ b/service/src/main/java/io/mifos/office/internal/command/AddBranchCommand.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.office.internal.command;
+
+import io.mifos.office.api.v1.domain.Office;
+
+public class AddBranchCommand {
+
+  private final String parentIdentifier;
+  private final Office branch;
+
+  public AddBranchCommand(final String parentIdentifier, final Office branch) {
+    super();
+    this.parentIdentifier = parentIdentifier;
+    this.branch = branch;
+  }
+
+  public String parentIdentifier() {
+    return parentIdentifier;
+  }
+
+  public Office branch() {
+    return branch;
+  }
+}
diff --git a/service/src/main/java/io/mifos/office/internal/command/CreateEmployeeCommand.java b/service/src/main/java/io/mifos/office/internal/command/CreateEmployeeCommand.java
new file mode 100644
index 0000000..485c493
--- /dev/null
+++ b/service/src/main/java/io/mifos/office/internal/command/CreateEmployeeCommand.java
@@ -0,0 +1,32 @@
+/*
+ * 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.office.internal.command;
+
+import io.mifos.office.api.v1.domain.Employee;
+
+public class CreateEmployeeCommand {
+
+  private final Employee employee;
+
+  public CreateEmployeeCommand(final Employee employee) {
+    super();
+    this.employee = employee;
+  }
+
+  public Employee employee() {
+    return employee;
+  }
+}
diff --git a/service/src/main/java/io/mifos/office/internal/command/CreateOfficeCommand.java b/service/src/main/java/io/mifos/office/internal/command/CreateOfficeCommand.java
new file mode 100644
index 0000000..07b859a
--- /dev/null
+++ b/service/src/main/java/io/mifos/office/internal/command/CreateOfficeCommand.java
@@ -0,0 +1,32 @@
+/*
+ * 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.office.internal.command;
+
+import io.mifos.office.api.v1.domain.Office;
+
+public class CreateOfficeCommand {
+
+  private final Office office;
+
+  public CreateOfficeCommand(final Office office) {
+    super();
+    this.office = office;
+  }
+
+  public Office office() {
+    return this.office;
+  }
+}
diff --git a/service/src/main/java/io/mifos/office/internal/command/DeleteAddressOfOfficeCommand.java b/service/src/main/java/io/mifos/office/internal/command/DeleteAddressOfOfficeCommand.java
new file mode 100644
index 0000000..380ffc4
--- /dev/null
+++ b/service/src/main/java/io/mifos/office/internal/command/DeleteAddressOfOfficeCommand.java
@@ -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.
+ */
+package io.mifos.office.internal.command;
+
+public class DeleteAddressOfOfficeCommand {
+
+  private final String identifier;
+
+  public DeleteAddressOfOfficeCommand(final String identifier) {
+    super();
+    this.identifier = identifier;
+  }
+
+  public String identifier() {
+    return identifier;
+  }
+}
diff --git a/service/src/main/java/io/mifos/office/internal/command/DeleteContactDetailCommand.java b/service/src/main/java/io/mifos/office/internal/command/DeleteContactDetailCommand.java
new file mode 100644
index 0000000..52795f4
--- /dev/null
+++ b/service/src/main/java/io/mifos/office/internal/command/DeleteContactDetailCommand.java
@@ -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.
+ */
+package io.mifos.office.internal.command;
+
+public class DeleteContactDetailCommand {
+
+  private final String identifier;
+
+  public DeleteContactDetailCommand(final String identifier) {
+    super();
+    this.identifier = identifier;
+  }
+
+  public String identifier() {
+    return identifier;
+  }
+}
diff --git a/service/src/main/java/io/mifos/office/internal/command/DeleteEmployeeCommand.java b/service/src/main/java/io/mifos/office/internal/command/DeleteEmployeeCommand.java
new file mode 100644
index 0000000..c1b48c7
--- /dev/null
+++ b/service/src/main/java/io/mifos/office/internal/command/DeleteEmployeeCommand.java
@@ -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.
+ */
+package io.mifos.office.internal.command;
+
+public class DeleteEmployeeCommand {
+
+  private final String code;
+
+  public DeleteEmployeeCommand(final String code) {
+    super();
+    this.code = code;
+  }
+
+  public String code() {
+    return code;
+  }
+}
diff --git a/service/src/main/java/io/mifos/office/internal/command/DeleteOfficeCommand.java b/service/src/main/java/io/mifos/office/internal/command/DeleteOfficeCommand.java
new file mode 100644
index 0000000..cceb0c2
--- /dev/null
+++ b/service/src/main/java/io/mifos/office/internal/command/DeleteOfficeCommand.java
@@ -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.
+ */
+package io.mifos.office.internal.command;
+
+public class DeleteOfficeCommand {
+
+  private final String identifier;
+
+  public DeleteOfficeCommand(final String identifier) {
+    super();
+    this.identifier = identifier;
+  }
+
+  public String identifier() {
+    return identifier;
+  }
+}
diff --git a/service/src/main/java/io/mifos/office/internal/command/InitializeServiceCommand.java b/service/src/main/java/io/mifos/office/internal/command/InitializeServiceCommand.java
new file mode 100644
index 0000000..d542c13
--- /dev/null
+++ b/service/src/main/java/io/mifos/office/internal/command/InitializeServiceCommand.java
@@ -0,0 +1,23 @@
+/*
+ * 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.office.internal.command;
+
+public class InitializeServiceCommand {
+
+  public InitializeServiceCommand() {
+    super();
+  }
+}
diff --git a/service/src/main/java/io/mifos/office/internal/command/SetAddressForOfficeCommand.java b/service/src/main/java/io/mifos/office/internal/command/SetAddressForOfficeCommand.java
new file mode 100644
index 0000000..9e0ba82
--- /dev/null
+++ b/service/src/main/java/io/mifos/office/internal/command/SetAddressForOfficeCommand.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.office.internal.command;
+
+import io.mifos.office.api.v1.domain.Address;
+
+public class SetAddressForOfficeCommand {
+
+  private final String identifier;
+  private final Address address;
+
+  public SetAddressForOfficeCommand(final String identifier, final Address address) {
+    super();
+    this.identifier = identifier;
+    this.address = address;
+  }
+
+  public String identifier() {
+    return identifier;
+  }
+
+  public Address address() {
+    return address;
+  }
+}
diff --git a/service/src/main/java/io/mifos/office/internal/command/SetContactDetailsCommand.java b/service/src/main/java/io/mifos/office/internal/command/SetContactDetailsCommand.java
new file mode 100644
index 0000000..c41e188
--- /dev/null
+++ b/service/src/main/java/io/mifos/office/internal/command/SetContactDetailsCommand.java
@@ -0,0 +1,40 @@
+/*
+ * 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.office.internal.command;
+
+import io.mifos.office.api.v1.domain.ContactDetail;
+
+import java.util.List;
+
+public class SetContactDetailsCommand {
+
+  private final String identifier;
+  private final List<ContactDetail> contactDetails;
+
+  public SetContactDetailsCommand(final String identifier, final List<ContactDetail> contactDetails) {
+    super();
+    this.identifier = identifier;
+    this.contactDetails = contactDetails;
+  }
+
+  public String identifier() {
+    return identifier;
+  }
+
+  public List<ContactDetail> contactDetails() {
+    return contactDetails;
+  }
+}
diff --git a/service/src/main/java/io/mifos/office/internal/command/UpdateEmployeeCommand.java b/service/src/main/java/io/mifos/office/internal/command/UpdateEmployeeCommand.java
new file mode 100644
index 0000000..4b256c7
--- /dev/null
+++ b/service/src/main/java/io/mifos/office/internal/command/UpdateEmployeeCommand.java
@@ -0,0 +1,32 @@
+/*
+ * 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.office.internal.command;
+
+import io.mifos.office.api.v1.domain.Employee;
+
+public class UpdateEmployeeCommand {
+
+  private final Employee employee;
+
+  public UpdateEmployeeCommand(final Employee employee) {
+    super();
+    this.employee = employee;
+  }
+
+  public Employee employee() {
+    return employee;
+  }
+}
diff --git a/service/src/main/java/io/mifos/office/internal/command/UpdateOfficeCommand.java b/service/src/main/java/io/mifos/office/internal/command/UpdateOfficeCommand.java
new file mode 100644
index 0000000..5373ec0
--- /dev/null
+++ b/service/src/main/java/io/mifos/office/internal/command/UpdateOfficeCommand.java
@@ -0,0 +1,32 @@
+/*
+ * 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.office.internal.command;
+
+import io.mifos.office.api.v1.domain.Office;
+
+public class UpdateOfficeCommand {
+
+  private Office office;
+
+  public UpdateOfficeCommand(final Office office) {
+    super();
+    this.office = office;
+  }
+
+  public Office office() {
+    return office;
+  }
+}
diff --git a/service/src/main/java/io/mifos/office/internal/command/handler/EmployeeAggregate.java b/service/src/main/java/io/mifos/office/internal/command/handler/EmployeeAggregate.java
new file mode 100644
index 0000000..f2394f1
--- /dev/null
+++ b/service/src/main/java/io/mifos/office/internal/command/handler/EmployeeAggregate.java
@@ -0,0 +1,214 @@
+/*
+ * 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.office.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.office.api.v1.EventConstants;
+import io.mifos.office.api.v1.domain.ContactDetail;
+import io.mifos.office.api.v1.domain.Employee;
+import io.mifos.office.internal.command.CreateEmployeeCommand;
+import io.mifos.office.internal.command.DeleteEmployeeCommand;
+import io.mifos.office.internal.command.SetContactDetailsCommand;
+import io.mifos.office.internal.command.UpdateEmployeeCommand;
+import io.mifos.office.internal.mapper.ContactDetailMapper;
+import io.mifos.office.internal.mapper.EmployeeMapper;
+import io.mifos.office.internal.command.DeleteContactDetailCommand;
+import io.mifos.office.internal.repository.ContactDetailEntity;
+import io.mifos.office.internal.repository.ContactDetailRepository;
+import io.mifos.office.internal.repository.EmployeeEntity;
+import io.mifos.office.internal.repository.EmployeeRepository;
+import io.mifos.office.internal.repository.OfficeEntity;
+import io.mifos.office.internal.repository.OfficeRepository;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.util.List;
+import java.util.Objects;
+import java.util.Optional;
+
+@SuppressWarnings({
+    "unused"
+})
+@Aggregate
+public class EmployeeAggregate {
+
+  private final EmployeeRepository employeeRepository;
+  private final ContactDetailRepository contactDetailRepository;
+  private final OfficeRepository officeRepository;
+
+  @Autowired
+  public EmployeeAggregate(final EmployeeRepository employeeRepository,
+                           final ContactDetailRepository contactDetailRepository,
+                           final OfficeRepository officeRepository) {
+    super();
+    this.employeeRepository = employeeRepository;
+    this.contactDetailRepository = contactDetailRepository;
+    this.officeRepository = officeRepository;
+  }
+
+  @Transactional
+  @CommandHandler
+  @EventEmitter(selectorName = EventConstants.OPERATION_HEADER, selectorValue = EventConstants.OPERATION_POST_EMPLOYEE)
+  public String createEmployee(final CreateEmployeeCommand createEmployeeCommand)
+      throws ServiceException {
+    final Employee employee = createEmployeeCommand.employee();
+
+    if (this.employeeRepository.existsByIdentifier(employee.getIdentifier())) {
+      throw ServiceException.conflict("Employee {0} already exists.", employee.getIdentifier());
+    }
+
+    final EmployeeEntity employeeEntity = EmployeeMapper.map(employee);
+
+    if (employee.getAssignedOffice() != null) {
+      final Optional<OfficeEntity> officeEntity = this.officeRepository.findByIdentifier(employee.getAssignedOffice());
+      if (officeEntity.isPresent()) {
+        employeeEntity.setAssignedOffice(officeEntity.get());
+      } else {
+        throw ServiceException.notFound("Assigned office {0} not found.", employee.getAssignedOffice());
+      }
+    }
+    employeeEntity.setCreatedBy(UserContextHolder.checkedGetUser());
+    employeeEntity.setCreatedOn(Utils.utcNow());
+    final EmployeeEntity savedEmployeeEntity = this.employeeRepository.save(employeeEntity);
+
+    if (employee.getContactDetails() != null) {
+      this.saveContactDetail(savedEmployeeEntity, employee.getContactDetails());
+    }
+
+    return employee.getIdentifier();
+  }
+
+  @Transactional
+  @CommandHandler
+  @EventEmitter(selectorName = EventConstants.OPERATION_HEADER, selectorValue = EventConstants.OPERATION_DELETE_EMPLOYEE)
+  public String deleteEmployee(final DeleteEmployeeCommand deleteEmployeeCommand) {
+
+    final EmployeeEntity employeeEntityToDelete = this.employeeRepository.findByIdentifier(deleteEmployeeCommand.code());
+    if (employeeEntityToDelete != null) {
+      this.deleteContactDetails(employeeEntityToDelete);
+      this.employeeRepository.delete(employeeEntityToDelete);
+    }
+    return deleteEmployeeCommand.code();
+  }
+
+  @Transactional
+  @CommandHandler
+  @EventEmitter(selectorName = EventConstants.OPERATION_HEADER, selectorValue = EventConstants.OPERATION_PUT_EMPLOYEE)
+  public String updateEmployee(final UpdateEmployeeCommand updateEmployeeCommand) {
+    final Employee employee = updateEmployeeCommand.employee();
+
+    final EmployeeEntity employeeEntity = this.employeeRepository.findByIdentifier(employee.getIdentifier());
+
+    if (employee.getGivenName() != null) {
+      employeeEntity.setGivenName(employee.getGivenName());
+    }
+
+    if (employee.getMiddleName() != null) {
+      employeeEntity.setMiddleName(employee.getMiddleName());
+    }
+
+    if (employee.getSurname() != null) {
+      employeeEntity.setSurname(employee.getSurname());
+    }
+
+    final OfficeEntity assignedOffice = employeeEntity.getAssignedOffice();
+    final String currentIdentifier = assignedOffice != null ? assignedOffice.getIdentifier() : null;
+
+    if (!Objects.equals(employee.getAssignedOffice(), currentIdentifier)) {
+      final Optional<OfficeEntity> officeEntity = this.officeRepository.findByIdentifier(employee.getAssignedOffice());
+      if (officeEntity.isPresent()) {
+        employeeEntity.setAssignedOffice(officeEntity.get());
+      } else {
+        throw ServiceException.notFound("Assigned office {0} not found.", employee.getAssignedOffice());
+      }
+    }
+
+    employeeEntity.setLastModifiedBy(UserContextHolder.checkedGetUser());
+    employeeEntity.setLastModifiedOn(Utils.utcNow());
+    this.employeeRepository.save(employeeEntity);
+
+    return updateEmployeeCommand.employee().getIdentifier();
+  }
+
+  @Transactional
+  @CommandHandler
+  @EventEmitter(selectorName = EventConstants.OPERATION_HEADER, selectorValue = EventConstants.OPERATION_PUT_CONTACT_DETAIL)
+  public String setContactDetail(final SetContactDetailsCommand setContactDetailsCommand)
+      throws ServiceException {
+
+    final EmployeeEntity employeeEntity = this.employeeRepository.findByIdentifier(setContactDetailsCommand.identifier());
+    if (employeeEntity == null) {
+      throw ServiceException.notFound("Employee {0} not found.", setContactDetailsCommand.identifier());
+    }
+
+    this.deleteContactDetails(employeeEntity);
+
+    final List<ContactDetail> contactDetails = setContactDetailsCommand.contactDetails();
+
+    if (contactDetails != null && !contactDetails.isEmpty()) {
+      this.saveContactDetail(employeeEntity, contactDetails);
+    }
+
+    employeeEntity.setLastModifiedBy(UserContextHolder.checkedGetUser());
+    employeeEntity.setLastModifiedOn(Utils.utcNow());
+    this.employeeRepository.save(employeeEntity);
+
+    return setContactDetailsCommand.identifier();
+  }
+
+  @Transactional
+  @CommandHandler
+  @EventEmitter(selectorName = EventConstants.OPERATION_HEADER, selectorValue = EventConstants.OPERATION_DELETE_CONTACT_DETAIL)
+  public String deleteContactDetail(final DeleteContactDetailCommand deleteContactDetailCommand)
+      throws ServiceException {
+
+    final EmployeeEntity employeeEntity = this.employeeRepository.findByIdentifier(deleteContactDetailCommand.identifier());
+    if (employeeEntity == null) {
+      throw ServiceException.notFound("Employee {0} not found.", deleteContactDetailCommand.identifier());
+    }
+
+    if (this.deleteContactDetails(employeeEntity)) {
+      employeeEntity.setLastModifiedBy(UserContextHolder.checkedGetUser());
+      employeeEntity.setLastModifiedOn(Utils.utcNow());
+      this.employeeRepository.save(employeeEntity);
+    }
+
+    return deleteContactDetailCommand.identifier();
+  }
+
+  private void saveContactDetail(final EmployeeEntity employeeEntity, List<ContactDetail> contactDetails) {
+    if (contactDetails != null && !contactDetails.isEmpty()) {
+      contactDetails.forEach(contactDetail -> {
+        final ContactDetailEntity contactDetailEntity = ContactDetailMapper.map(contactDetail);
+        contactDetailEntity.setEmployee(employeeEntity);
+        this.contactDetailRepository.save(contactDetailEntity);
+      });
+    }
+  }
+
+  private boolean deleteContactDetails(final EmployeeEntity employeeEntity) {
+    final List<ContactDetailEntity> contactDetailEntities = this.contactDetailRepository.findByEmployeeOrderByPreferenceLevelAsc(employeeEntity);
+    if (contactDetailEntities != null && !contactDetailEntities.isEmpty()) {
+      contactDetailEntities.forEach(this.contactDetailRepository::delete);
+      return true;
+    }
+    return false;
+  }
+}
diff --git a/service/src/main/java/io/mifos/office/internal/command/handler/MigrationAggregate.java b/service/src/main/java/io/mifos/office/internal/command/handler/MigrationAggregate.java
new file mode 100644
index 0000000..8bdb0e4
--- /dev/null
+++ b/service/src/main/java/io/mifos/office/internal/command/handler/MigrationAggregate.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.office.internal.command.handler;
+
+import io.mifos.core.command.annotation.Aggregate;
+import io.mifos.core.command.annotation.CommandHandler;
+import io.mifos.core.command.annotation.EventEmitter;
+import io.mifos.core.mariadb.domain.FlywayFactoryBean;
+import io.mifos.office.ServiceConstants;
+import io.mifos.office.api.v1.EventConstants;
+import io.mifos.office.internal.command.InitializeServiceCommand;
+import org.slf4j.Logger;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Qualifier;
+
+import javax.sql.DataSource;
+
+@SuppressWarnings({
+    "unused"
+})
+@Aggregate
+public class MigrationAggregate {
+
+  private final Logger logger;
+  private final DataSource dataSource;
+  private final FlywayFactoryBean flywayFactoryBean;
+
+  @Autowired
+  public MigrationAggregate(@Qualifier(ServiceConstants.SERVICE_LOGGER_NAME) final Logger logger,
+                            final DataSource dataSource,
+                            final FlywayFactoryBean flywayFactoryBean) {
+    super();
+    this.logger = logger;
+    this.dataSource = dataSource;
+    this.flywayFactoryBean = flywayFactoryBean;
+  }
+
+  @CommandHandler
+  @EventEmitter(selectorName = EventConstants.OPERATION_HEADER, selectorValue = EventConstants.INITIALIZE)
+  public String initialize(final InitializeServiceCommand initializeServiceCommand) {
+    this.logger.debug("Start service migration.");
+    this.flywayFactoryBean.create(this.dataSource).migrate();
+    return EventConstants.INITIALIZE;
+  }
+}
diff --git a/service/src/main/java/io/mifos/office/internal/command/handler/OfficeAggregate.java b/service/src/main/java/io/mifos/office/internal/command/handler/OfficeAggregate.java
new file mode 100644
index 0000000..0930558
--- /dev/null
+++ b/service/src/main/java/io/mifos/office/internal/command/handler/OfficeAggregate.java
@@ -0,0 +1,223 @@
+/*
+ * 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.office.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.office.ServiceConstants;
+import io.mifos.office.api.v1.EventConstants;
+import io.mifos.office.api.v1.domain.Office;
+import io.mifos.office.internal.command.DeleteAddressOfOfficeCommand;
+import io.mifos.office.internal.mapper.AddressMapper;
+import io.mifos.office.internal.mapper.OfficeMapper;
+import io.mifos.office.internal.repository.AddressEntity;
+import io.mifos.office.internal.repository.AddressRepository;
+import io.mifos.office.internal.repository.OfficeEntity;
+import io.mifos.office.internal.repository.OfficeRepository;
+import io.mifos.office.internal.command.AddBranchCommand;
+import io.mifos.office.internal.command.CreateOfficeCommand;
+import io.mifos.office.internal.command.DeleteOfficeCommand;
+import io.mifos.office.internal.command.SetAddressForOfficeCommand;
+import io.mifos.office.internal.command.UpdateOfficeCommand;
+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.Date;
+import java.util.Optional;
+
+@SuppressWarnings({
+    "unused"
+})
+@Aggregate
+public class OfficeAggregate {
+
+  private final Logger logger;
+  private final OfficeRepository officeRepository;
+  private final AddressRepository addressRepository;
+
+  @Autowired
+  public OfficeAggregate(@Qualifier(ServiceConstants.SERVICE_LOGGER_NAME) final Logger logger,
+                         final OfficeRepository officeRepository,
+                         final AddressRepository addressRepository) {
+    super();
+    this.logger = logger;
+    this.officeRepository = officeRepository;
+    this.addressRepository = addressRepository;
+  }
+
+  @Transactional
+  @CommandHandler
+  @EventEmitter(selectorName = EventConstants.OPERATION_HEADER, selectorValue = EventConstants.OPERATION_POST_OFFICE)
+  public String createOffice(final CreateOfficeCommand createOfficeCommand) throws ServiceException {
+    this.createOffice(createOfficeCommand.office(), null);
+    return createOfficeCommand.office().getIdentifier();
+  }
+
+  @Transactional
+  @CommandHandler
+  @EventEmitter(selectorName = EventConstants.OPERATION_HEADER, selectorValue = EventConstants.OPERATION_PUT_OFFICE)
+  public String updateOffice(final UpdateOfficeCommand updateOfficeCommand) throws ServiceException {
+    final Office office = updateOfficeCommand.office();
+
+    final Optional<OfficeEntity> optionalOfficeEntity = this.officeRepository.findByIdentifier(office.getIdentifier());
+
+    if (optionalOfficeEntity.isPresent()) {
+      final OfficeEntity officeEntity = optionalOfficeEntity.get();
+      if (office.getName() != null) {
+        officeEntity.setName(office.getName());
+      }
+
+      if (office.getDescription() != null) {
+        officeEntity.setDescription(office.getDescription());
+      }
+
+      officeEntity.setCreatedBy(UserContextHolder.checkedGetUser());
+      officeEntity.setCreatedOn(Utils.utcNow());
+
+      this.officeRepository.save(officeEntity);
+
+      if (office.getAddress() != null) {
+        this.setAddress(new SetAddressForOfficeCommand(office.getIdentifier(), office.getAddress()));
+      }
+      return office.getIdentifier();
+    } else {
+      throw ServiceException.notFound("Office {0} not found.", office.getIdentifier());
+    }
+  }
+
+  @Transactional
+  @CommandHandler
+  @EventEmitter(selectorName = EventConstants.OPERATION_HEADER, selectorValue = EventConstants.OPERATION_POST_OFFICE)
+  public String addBranch(final AddBranchCommand addBranchCommand) {
+    final Office parentOffice = new Office();
+    parentOffice.setIdentifier(addBranchCommand.parentIdentifier());
+
+    final Office branch = addBranchCommand.branch();
+
+    this.createOffice(branch, parentOffice);
+
+    return branch.getIdentifier();
+  }
+
+  @Transactional
+  @CommandHandler
+  @EventEmitter(selectorName = EventConstants.OPERATION_HEADER, selectorValue = EventConstants.OPERATION_DELETE_OFFICE)
+  public String deleteOffice(final DeleteOfficeCommand deleteOfficeCommand) {
+    final Optional<OfficeEntity> optionalOfficeEntity = this.officeRepository.findByIdentifier(deleteOfficeCommand.identifier());
+
+    if (optionalOfficeEntity.isPresent()) {
+      final OfficeEntity officeEntityToDelete = optionalOfficeEntity.get();
+      final Optional<AddressEntity> optionalAddressEntity = this.addressRepository.findByOffice(officeEntityToDelete);
+      if (optionalAddressEntity.isPresent()) {
+        this.addressRepository.delete(optionalAddressEntity.get());
+      }
+
+      this.officeRepository.delete(officeEntityToDelete);
+    }
+
+    return deleteOfficeCommand.identifier();
+  }
+
+  @SuppressWarnings("WeakerAccess")
+  @Transactional
+  @CommandHandler
+  @EventEmitter(selectorName = EventConstants.OPERATION_HEADER, selectorValue = EventConstants.OPERATION_PUT_ADDRESS)
+  public String setAddress(final SetAddressForOfficeCommand setAddressForOfficeCommand) {
+    final Optional<OfficeEntity> optionalOfficeEntity = this.officeRepository.findByIdentifier(setAddressForOfficeCommand.identifier());
+
+    if (optionalOfficeEntity.isPresent()) {
+      final OfficeEntity officeEntity = optionalOfficeEntity.get();
+      final Optional<AddressEntity> optionalAddressEntity = this.addressRepository.findByOffice(officeEntity);
+      if (optionalAddressEntity.isPresent()) {
+        this.addressRepository.delete(optionalAddressEntity.get());
+      }
+
+      final AddressEntity addressEntity = AddressMapper.map(setAddressForOfficeCommand.address());
+      addressEntity.setOffice(officeEntity);
+      this.addressRepository.save(addressEntity);
+
+      officeEntity.setLastModifiedBy(UserContextHolder.checkedGetUser());
+      officeEntity.setLastModifiedOn(Utils.utcNow());
+      this.officeRepository.save(officeEntity);
+
+      return setAddressForOfficeCommand.identifier();
+    } else {
+      throw ServiceException.notFound("Office {0} not found.", setAddressForOfficeCommand.identifier());
+    }
+  }
+
+  @Transactional
+  @CommandHandler
+  @EventEmitter(selectorName = EventConstants.OPERATION_HEADER, selectorValue = EventConstants.OPERATION_DELETE_ADDRESS)
+  public String deleteAddress(final DeleteAddressOfOfficeCommand deleteAddressOfOfficeCommand) {
+    final Optional<OfficeEntity> optionalOfficeEntity = this.officeRepository.findByIdentifier(deleteAddressOfOfficeCommand.identifier());
+    if (optionalOfficeEntity.isPresent()) {
+      final OfficeEntity officeEntity = optionalOfficeEntity.get();
+      if (officeEntity != null) {
+        final Optional<AddressEntity> optionalAddressEntity = this.addressRepository.findByOffice(officeEntity);
+        if (optionalAddressEntity.isPresent()) {
+          this.addressRepository.delete(optionalAddressEntity.get());
+
+          officeEntity.setLastModifiedBy(UserContextHolder.checkedGetUser());
+          officeEntity.setLastModifiedOn(Utils.utcNow());
+          this.officeRepository.save(officeEntity);
+          return deleteAddressOfOfficeCommand.identifier();
+        }
+      } else {
+        this.logger.info("Office {} not found.", deleteAddressOfOfficeCommand.identifier());
+      }
+    }
+    return null;
+  }
+
+  private void createOffice(final Office office, final Office parentOffice) {
+    if (this.officeRepository.existsByIdentifier(office.getIdentifier())) {
+      this.logger.info("Office {} already exists.", office.getIdentifier());
+      throw ServiceException.conflict("Office {0} already exists.", office.getIdentifier());
+    }
+
+    final String modificationUser = UserContextHolder.checkedGetUser();
+    final Date modificationDate = Utils.utcNow();
+
+    final OfficeEntity officeEntity = OfficeMapper.map(office);
+    if (parentOffice != null) {
+      final Optional<OfficeEntity> optionalParentOfficeEntity = this.officeRepository.findByIdentifier(parentOffice.getIdentifier());
+      if (optionalParentOfficeEntity.isPresent()) {
+        final OfficeEntity parentOfficeEntity = optionalParentOfficeEntity.get();
+        officeEntity.setParentOfficeId(parentOfficeEntity.getId());
+        parentOfficeEntity.setLastModifiedBy(modificationUser);
+        parentOfficeEntity.setLastModifiedOn(modificationDate);
+        this.officeRepository.save(parentOfficeEntity);
+      }
+    }
+
+    officeEntity.setCreatedBy(modificationUser);
+    officeEntity.setCreatedOn(modificationDate);
+
+    final OfficeEntity savedOfficeEntity = this.officeRepository.save(officeEntity);
+
+    if (office.getAddress() != null) {
+      final AddressEntity addressEntity = AddressMapper.map(office.getAddress());
+      addressEntity.setOffice(savedOfficeEntity);
+      this.addressRepository.save(addressEntity);
+    }
+  }
+}
diff --git a/service/src/main/java/io/mifos/office/internal/command/handler/Utils.java b/service/src/main/java/io/mifos/office/internal/command/handler/Utils.java
new file mode 100644
index 0000000..9ad10cf
--- /dev/null
+++ b/service/src/main/java/io/mifos/office/internal/command/handler/Utils.java
@@ -0,0 +1,29 @@
+/*
+ * 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.office.internal.command.handler;
+
+import java.time.Clock;
+import java.time.Instant;
+import java.util.Date;
+
+/**
+ * @author Myrle Krantz
+ */
+interface Utils {
+  static Date utcNow() {
+    return Date.from(Instant.now(Clock.systemUTC()));
+  }
+}
diff --git a/service/src/main/java/io/mifos/office/internal/config/OfficeServiceConfiguration.java b/service/src/main/java/io/mifos/office/internal/config/OfficeServiceConfiguration.java
new file mode 100644
index 0000000..e28e9f3
--- /dev/null
+++ b/service/src/main/java/io/mifos/office/internal/config/OfficeServiceConfiguration.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.office.internal.config;
+
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+import io.mifos.core.mariadb.config.EnableMariaDB;
+import io.mifos.office.ServiceConstants;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.ComponentScan;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
+
+@Configuration
+@EnableMariaDB
+@ComponentScan(
+    basePackages = {
+        "io.mifos.office.internal.command.handler",
+        "io.mifos.office.internal.repository",
+        "io.mifos.office.internal.service"
+    }
+)
+@EnableJpaRepositories(
+    basePackages = {
+        "io.mifos.office.internal.repository"
+    }
+)
+public class OfficeServiceConfiguration {
+
+  public OfficeServiceConfiguration() {
+    super();
+  }
+
+  @Bean(name = ServiceConstants.SERVICE_LOGGER_NAME)
+  public Logger logger() {
+    return LoggerFactory.getLogger(ServiceConstants.SERVICE_LOGGER_NAME);
+  }
+
+  @Bean(name = ServiceConstants.JSON_SERIALIZER_NAME)
+  public Gson gson() {
+    return new GsonBuilder().create();
+  }
+}
diff --git a/service/src/main/java/io/mifos/office/internal/mapper/AddressMapper.java b/service/src/main/java/io/mifos/office/internal/mapper/AddressMapper.java
new file mode 100644
index 0000000..725e5bd
--- /dev/null
+++ b/service/src/main/java/io/mifos/office/internal/mapper/AddressMapper.java
@@ -0,0 +1,48 @@
+/*
+ * 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.office.internal.mapper;
+
+import io.mifos.office.api.v1.domain.Address;
+import io.mifos.office.internal.repository.AddressEntity;
+
+public final class AddressMapper {
+
+  private AddressMapper() {
+    super();
+  }
+
+  public static AddressEntity map(final Address address) {
+    final AddressEntity addressEntity = new AddressEntity();
+    addressEntity.setStreet(address.getStreet());
+    addressEntity.setCity(address.getCity());
+    addressEntity.setRegion(address.getRegion());
+    addressEntity.setPostalCode(address.getPostalCode());
+    addressEntity.setCountry(address.getCountry());
+    addressEntity.setCountryCode(address.getCountryCode());
+    return addressEntity;
+  }
+
+  public static Address map(final AddressEntity addressEntity) {
+    final Address address = new Address();
+    address.setStreet(addressEntity.getStreet());
+    address.setCity(addressEntity.getCity());
+    address.setRegion(addressEntity.getRegion());
+    address.setPostalCode(addressEntity.getPostalCode());
+    address.setCountry(addressEntity.getCountry());
+    address.setCountryCode(addressEntity.getCountryCode());
+    return address;
+  }
+}
diff --git a/service/src/main/java/io/mifos/office/internal/mapper/ContactDetailMapper.java b/service/src/main/java/io/mifos/office/internal/mapper/ContactDetailMapper.java
new file mode 100644
index 0000000..df70d77
--- /dev/null
+++ b/service/src/main/java/io/mifos/office/internal/mapper/ContactDetailMapper.java
@@ -0,0 +1,44 @@
+/*
+ * 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.office.internal.mapper;
+
+import io.mifos.office.api.v1.domain.ContactDetail;
+import io.mifos.office.internal.repository.ContactDetailEntity;
+
+public class ContactDetailMapper {
+
+  private ContactDetailMapper() {
+    super();
+  }
+
+  public static ContactDetailEntity map(final ContactDetail contactDetail) {
+    final ContactDetailEntity contactDetailEntity = new ContactDetailEntity();
+    contactDetailEntity.setType(contactDetail.getType());
+    contactDetailEntity.setGroup(contactDetail.getGroup());
+    contactDetailEntity.setValue(contactDetail.getValue());
+    contactDetailEntity.setPreferenceLevel(contactDetail.getPreferenceLevel());
+    return contactDetailEntity;
+  }
+
+  public static ContactDetail map(final ContactDetailEntity contactDetailEntity) {
+    final ContactDetail contactDetail = new ContactDetail();
+    contactDetail.setType(contactDetailEntity.getType());
+    contactDetail.setGroup(contactDetailEntity.getGroup());
+    contactDetail.setValue(contactDetailEntity.getValue());
+    contactDetail.setPreferenceLevel(contactDetailEntity.getPreferenceLevel());
+    return contactDetail;
+  }
+}
diff --git a/service/src/main/java/io/mifos/office/internal/mapper/EmployeeMapper.java b/service/src/main/java/io/mifos/office/internal/mapper/EmployeeMapper.java
new file mode 100644
index 0000000..70cd4b7
--- /dev/null
+++ b/service/src/main/java/io/mifos/office/internal/mapper/EmployeeMapper.java
@@ -0,0 +1,46 @@
+/*
+ * 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.office.internal.mapper;
+
+import io.mifos.office.api.v1.domain.Employee;
+import io.mifos.office.internal.repository.EmployeeEntity;
+
+public class EmployeeMapper {
+
+  private EmployeeMapper() {
+    super();
+  }
+
+  public static EmployeeEntity map(final Employee employee) {
+    final EmployeeEntity employeeEntity = new EmployeeEntity();
+    employeeEntity.setIdentifier(employee.getIdentifier());
+    employeeEntity.setGivenName(employee.getGivenName());
+    employeeEntity.setMiddleName(employee.getMiddleName());
+    employeeEntity.setSurname(employee.getSurname());
+    return employeeEntity;
+  }
+
+  public static Employee map(final EmployeeEntity employeeEntity) {
+    final Employee employee = new Employee();
+    employee.setIdentifier(employeeEntity.getIdentifier());
+    employee.setGivenName(employeeEntity.getGivenName());
+    employee.setMiddleName(employeeEntity.getMiddleName());
+    employee.setSurname(employeeEntity.getSurname());
+    if (employeeEntity.getAssignedOffice() != null)
+      employee.setAssignedOffice(employeeEntity.getAssignedOffice().getIdentifier());
+    return employee;
+  }
+}
diff --git a/service/src/main/java/io/mifos/office/internal/mapper/OfficeMapper.java b/service/src/main/java/io/mifos/office/internal/mapper/OfficeMapper.java
new file mode 100644
index 0000000..526ed2c
--- /dev/null
+++ b/service/src/main/java/io/mifos/office/internal/mapper/OfficeMapper.java
@@ -0,0 +1,42 @@
+/*
+ * 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.office.internal.mapper;
+
+import io.mifos.office.api.v1.domain.Office;
+import io.mifos.office.internal.repository.OfficeEntity;
+
+public final class OfficeMapper {
+
+  private OfficeMapper() {
+    super();
+  }
+
+  public static OfficeEntity map(final Office office) {
+    final OfficeEntity officeEntity = new OfficeEntity();
+    officeEntity.setIdentifier(office.getIdentifier());
+    officeEntity.setName(office.getName());
+    officeEntity.setDescription(office.getDescription());
+    return officeEntity;
+  }
+
+  public static Office map(final OfficeEntity officeEntity) {
+    final Office office = new Office();
+    office.setIdentifier(officeEntity.getIdentifier());
+    office.setName(officeEntity.getName());
+    office.setDescription(officeEntity.getDescription());
+    return office;
+  }
+}
diff --git a/service/src/main/java/io/mifos/office/internal/repository/AddressEntity.java b/service/src/main/java/io/mifos/office/internal/repository/AddressEntity.java
new file mode 100644
index 0000000..fedc5e3
--- /dev/null
+++ b/service/src/main/java/io/mifos/office/internal/repository/AddressEntity.java
@@ -0,0 +1,119 @@
+/*
+ * 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.office.internal.repository;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.FetchType;
+import javax.persistence.GeneratedValue;
+import javax.persistence.GenerationType;
+import javax.persistence.Id;
+import javax.persistence.JoinColumn;
+import javax.persistence.OneToOne;
+import javax.persistence.Table;
+
+@Entity
+@Table(name = "horus_addresses")
+public class AddressEntity {
+
+  @Id
+  @GeneratedValue(strategy = GenerationType.IDENTITY)
+  @Column
+  private Long id;
+  @Column(name = "street")
+  private String street;
+  @Column(name = "city")
+  private String city;
+  @Column(name = "region")
+  private String region;
+  @Column(name = "postal_code")
+  private String postalCode;
+  @Column(name = "country_code")
+  private String countryCode;
+  @Column(name = "country")
+  private String country;
+  @OneToOne(fetch = FetchType.LAZY, optional = false)
+  @JoinColumn(name = "office_id")
+  private OfficeEntity office;
+
+  public AddressEntity() {
+    super();
+  }
+
+  public Long getId() {
+    return id;
+  }
+
+  public void setId(Long id) {
+    this.id = id;
+  }
+
+  public String getStreet() {
+    return street;
+  }
+
+  public void setStreet(String street) {
+    this.street = street;
+  }
+
+  public String getCity() {
+    return city;
+  }
+
+  public void setCity(String city) {
+    this.city = city;
+  }
+
+  public String getRegion() {
+    return region;
+  }
+
+  public void setRegion(String region) {
+    this.region = region;
+  }
+
+  public String getPostalCode() {
+    return postalCode;
+  }
+
+  public void setPostalCode(String postalCode) {
+    this.postalCode = postalCode;
+  }
+
+  public String getCountryCode() {
+    return countryCode;
+  }
+
+  public void setCountryCode(String countryCode) {
+    this.countryCode = countryCode;
+  }
+
+  public String getCountry() {
+    return country;
+  }
+
+  public void setCountry(String country) {
+    this.country = country;
+  }
+
+  public OfficeEntity getOffice() {
+    return office;
+  }
+
+  public void setOffice(OfficeEntity office) {
+    this.office = office;
+  }
+}
diff --git a/service/src/main/java/io/mifos/office/internal/repository/AddressRepository.java b/service/src/main/java/io/mifos/office/internal/repository/AddressRepository.java
new file mode 100644
index 0000000..f931521
--- /dev/null
+++ b/service/src/main/java/io/mifos/office/internal/repository/AddressRepository.java
@@ -0,0 +1,27 @@
+/*
+ * 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.office.internal.repository;
+
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.stereotype.Repository;
+
+import java.util.Optional;
+
+@Repository
+public interface AddressRepository extends JpaRepository<AddressEntity, Long> {
+
+  Optional<AddressEntity> findByOffice(final OfficeEntity officeEntity);
+}
diff --git a/service/src/main/java/io/mifos/office/internal/repository/ContactDetailEntity.java b/service/src/main/java/io/mifos/office/internal/repository/ContactDetailEntity.java
new file mode 100644
index 0000000..fd6d331
--- /dev/null
+++ b/service/src/main/java/io/mifos/office/internal/repository/ContactDetailEntity.java
@@ -0,0 +1,99 @@
+/*
+ * 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.office.internal.repository;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.FetchType;
+import javax.persistence.GeneratedValue;
+import javax.persistence.GenerationType;
+import javax.persistence.Id;
+import javax.persistence.JoinColumn;
+import javax.persistence.OneToOne;
+import javax.persistence.Table;
+
+@Entity
+@Table(name = "horus_contact_details")
+public class ContactDetailEntity {
+
+  @Id
+  @GeneratedValue(strategy = GenerationType.IDENTITY)
+  @Column(name = "id")
+  private Long id;
+  @OneToOne(fetch = FetchType.LAZY, optional = true)
+  @JoinColumn(name = "employee_id")
+  private EmployeeEntity employee;
+  @Column(name = "a_type")
+  private String type;
+  @Column(name = "a_group")
+  private String group;
+  @Column(name = "a_value")
+  private String value;
+  @Column(name = "preference_level")
+  private Integer preferenceLevel;
+
+  public ContactDetailEntity() {
+    super();
+  }
+
+  public Long getId() {
+    return id;
+  }
+
+  public void setId(Long id) {
+    this.id = id;
+  }
+
+  public EmployeeEntity getEmployee() {
+    return employee;
+  }
+
+  public void setEmployee(final EmployeeEntity employee) {
+    this.employee = employee;
+  }
+
+  public String getType() {
+    return this.type;
+  }
+
+  public void setType(final String type) {
+    this.type = type;
+  }
+
+  public String getGroup() {
+    return this.group;
+  }
+
+  public void setGroup(final String group) {
+    this.group = group;
+  }
+
+  public String getValue() {
+    return this.value;
+  }
+
+  public void setValue(final String value) {
+    this.value = value;
+  }
+
+  public Integer getPreferenceLevel() {
+    return this.preferenceLevel;
+  }
+
+  public void setPreferenceLevel(final Integer preferenceLevel) {
+    this.preferenceLevel = preferenceLevel;
+  }
+}
diff --git a/service/src/main/java/io/mifos/office/internal/repository/ContactDetailRepository.java b/service/src/main/java/io/mifos/office/internal/repository/ContactDetailRepository.java
new file mode 100644
index 0000000..72d457c
--- /dev/null
+++ b/service/src/main/java/io/mifos/office/internal/repository/ContactDetailRepository.java
@@ -0,0 +1,27 @@
+/*
+ * 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.office.internal.repository;
+
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.stereotype.Repository;
+
+import java.util.List;
+
+@Repository
+public interface ContactDetailRepository extends JpaRepository<ContactDetailEntity, Long> {
+
+  List<ContactDetailEntity> findByEmployeeOrderByPreferenceLevelAsc(final EmployeeEntity employeeEntity);
+}
diff --git a/service/src/main/java/io/mifos/office/internal/repository/EmployeeEntity.java b/service/src/main/java/io/mifos/office/internal/repository/EmployeeEntity.java
new file mode 100644
index 0000000..04b43af
--- /dev/null
+++ b/service/src/main/java/io/mifos/office/internal/repository/EmployeeEntity.java
@@ -0,0 +1,160 @@
+/*
+ * 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.office.internal.repository;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.FetchType;
+import javax.persistence.GeneratedValue;
+import javax.persistence.GenerationType;
+import javax.persistence.Id;
+import javax.persistence.JoinColumn;
+import javax.persistence.OneToOne;
+import javax.persistence.Table;
+import javax.persistence.Temporal;
+import javax.persistence.TemporalType;
+import java.util.Date;
+
+@Entity
+@Table(name = "horus_employees")
+public class EmployeeEntity {
+
+  @Id
+  @GeneratedValue(strategy = GenerationType.IDENTITY)
+  @Column(name = "id")
+  private Long id;
+  @Column(name = "identifier")
+  private String identifier;
+  @Column(name = "given_name")
+  private String givenName;
+  @Column(name = "middle_name")
+  private String middleName;
+  @Column(name = "surname")
+  private String surname;
+  @OneToOne(fetch = FetchType.LAZY, optional = true)
+  @JoinColumn(name = "assigned_office_id")
+  private OfficeEntity assignedOffice;
+  @Column(name = "created_by")
+  private String createdBy;
+  @Column(name = "created_on")
+  @Temporal(TemporalType.TIMESTAMP)
+  private Date createdOn;
+  @Column(name = "last_modified_by")
+  private String lastModifiedBy;
+  @Column(name = "last_modified_on")
+  @Temporal(TemporalType.TIMESTAMP)
+  private Date lastModifiedOn;
+
+  public EmployeeEntity() {
+    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 getGivenName() {
+    return givenName;
+  }
+
+  public void setGivenName(String givenName) {
+    this.givenName = givenName;
+  }
+
+  public String getMiddleName() {
+    return middleName;
+  }
+
+  public void setMiddleName(String middleName) {
+    this.middleName = middleName;
+  }
+
+  public String getSurname() {
+    return surname;
+  }
+
+  public void setSurname(String surname) {
+    this.surname = surname;
+  }
+
+  public OfficeEntity getAssignedOffice() {
+    return assignedOffice;
+  }
+
+  public void setAssignedOffice(OfficeEntity assignedOffice) {
+    this.assignedOffice = assignedOffice;
+  }
+
+  public String getCreatedBy() {
+    return createdBy;
+  }
+
+  public void setCreatedBy(String createdBy) {
+    this.createdBy = createdBy;
+  }
+
+  public Date getCreatedOn() {
+    return createdOn;
+  }
+
+  public void setCreatedOn(Date createdOn) {
+    this.createdOn = createdOn;
+  }
+
+  public String getLastModifiedBy() {
+    return lastModifiedBy;
+  }
+
+  public void setLastModifiedBy(String lastModifiedBy) {
+    this.lastModifiedBy = lastModifiedBy;
+  }
+
+  public Date getLastModifiedOn() {
+    return lastModifiedOn;
+  }
+
+  public void setLastModifiedOn(Date lastModifiedOn) {
+    this.lastModifiedOn = lastModifiedOn;
+  }
+
+  @Override
+  public boolean equals(Object o) {
+    if (this == o) return true;
+    if (o == null || getClass() != o.getClass()) return false;
+
+    EmployeeEntity that = (EmployeeEntity) o;
+
+    return identifier.equals(that.identifier);
+
+  }
+
+  @Override
+  public int hashCode() {
+    return identifier.hashCode();
+  }
+}
diff --git a/service/src/main/java/io/mifos/office/internal/repository/EmployeeRepository.java b/service/src/main/java/io/mifos/office/internal/repository/EmployeeRepository.java
new file mode 100644
index 0000000..1b9d687
--- /dev/null
+++ b/service/src/main/java/io/mifos/office/internal/repository/EmployeeRepository.java
@@ -0,0 +1,36 @@
+/*
+ * 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.office.internal.repository;
+
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.Pageable;
+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;
+
+@Repository
+public interface EmployeeRepository extends JpaRepository<EmployeeEntity, Long> {
+
+  EmployeeEntity findByIdentifier(final String identifier);
+
+  @Query("SELECT CASE WHEN COUNT(e) > 0 THEN 'true' ELSE 'false' END FROM EmployeeEntity e WHERE e.identifier = :identifier")
+  Boolean existsByIdentifier(@Param("identifier") final String identifier);
+
+  Page<EmployeeEntity> findByAssignedOffice(final OfficeEntity assignedOffice, final Pageable pageable);
+
+  Page<EmployeeEntity> findByIdentifierContaining(String term, Pageable pageRequest);
+}
diff --git a/service/src/main/java/io/mifos/office/internal/repository/OfficeEntity.java b/service/src/main/java/io/mifos/office/internal/repository/OfficeEntity.java
new file mode 100644
index 0000000..24a4c42
--- /dev/null
+++ b/service/src/main/java/io/mifos/office/internal/repository/OfficeEntity.java
@@ -0,0 +1,146 @@
+/*
+ * 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.office.internal.repository;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.GeneratedValue;
+import javax.persistence.GenerationType;
+import javax.persistence.Id;
+import javax.persistence.Table;
+import javax.persistence.Temporal;
+import javax.persistence.TemporalType;
+import java.util.Date;
+
+@Entity
+@Table(name = "horus_offices")
+public class OfficeEntity {
+
+  @Id
+  @GeneratedValue(strategy = GenerationType.IDENTITY)
+  @Column(name = "id")
+  private Long id;
+  @Column(name = "parent_office_id")
+  private Long parentOfficeId;
+  @Column(name = "identifier")
+  private String identifier;
+  @Column(name = "a_name")
+  private String name;
+  @Column(name = "description")
+  private String description;
+  @Column(name = "created_by")
+  private String createdBy;
+  @Column(name = "created_on")
+  @Temporal(TemporalType.TIMESTAMP)
+  private Date createdOn;
+  @Column(name = "last_modified_by")
+  private String lastModifiedBy;
+  @Column(name = "last_modified_on")
+  @Temporal(TemporalType.TIMESTAMP)
+  private Date lastModifiedOn;
+
+  public OfficeEntity() {
+    super();
+  }
+
+  public Long getId() {
+    return id;
+  }
+
+  public void setId(Long id) {
+    this.id = id;
+  }
+
+  public Long getParentOfficeId() {
+    return parentOfficeId;
+  }
+
+  public void setParentOfficeId(Long parentOfficeId) {
+    this.parentOfficeId = parentOfficeId;
+  }
+
+  public String getIdentifier() {
+    return identifier;
+  }
+
+  public void setIdentifier(String identifier) {
+    this.identifier = identifier;
+  }
+
+  public String getName() {
+    return name;
+  }
+
+  public void setName(String name) {
+    this.name = name;
+  }
+
+  public String getDescription() {
+    return description;
+  }
+
+  public void setDescription(String description) {
+    this.description = description;
+  }
+
+  public String getCreatedBy() {
+    return createdBy;
+  }
+
+  public void setCreatedBy(String createdBy) {
+    this.createdBy = createdBy;
+  }
+
+  public Date getCreatedOn() {
+    return createdOn;
+  }
+
+  public void setCreatedOn(Date createdOn) {
+    this.createdOn = createdOn;
+  }
+
+  public String getLastModifiedBy() {
+    return lastModifiedBy;
+  }
+
+  public void setLastModifiedBy(String lastModifiedBy) {
+    this.lastModifiedBy = lastModifiedBy;
+  }
+
+  public Date getLastModifiedOn() {
+    return lastModifiedOn;
+  }
+
+  public void setLastModifiedOn(Date lastModifiedOn) {
+    this.lastModifiedOn = lastModifiedOn;
+  }
+
+  @Override
+  public boolean equals(Object o) {
+    if (this == o) return true;
+    if (o == null || getClass() != o.getClass()) return false;
+
+    final OfficeEntity that = (OfficeEntity) o;
+
+    return identifier.equals(that.identifier);
+
+  }
+
+  @Override
+  public int hashCode() {
+    return identifier.hashCode();
+  }
+}
diff --git a/service/src/main/java/io/mifos/office/internal/repository/OfficeRepository.java b/service/src/main/java/io/mifos/office/internal/repository/OfficeRepository.java
new file mode 100644
index 0000000..891fcf0
--- /dev/null
+++ b/service/src/main/java/io/mifos/office/internal/repository/OfficeRepository.java
@@ -0,0 +1,43 @@
+/*
+ * 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.office.internal.repository;
+
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.Pageable;
+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;
+
+@Repository
+public interface OfficeRepository extends JpaRepository<OfficeEntity, Long> {
+
+  Optional<OfficeEntity> findByIdentifier(final String identifier);
+
+  @Query("SELECT CASE WHEN COUNT(o) > 0 THEN 'true' ELSE 'false' END FROM OfficeEntity o WHERE o.identifier = :identifier")
+  Boolean existsByIdentifier(@Param("identifier") final String identifier);
+
+  @Query("SELECT CASE WHEN COUNT(o) > 0 THEN 'true' ELSE 'false' END FROM OfficeEntity o WHERE o.parentOfficeId = :parentOfficeId")
+  Boolean existsByParentOfficeId(@Param("parentOfficeId") final Long parentOfficeId);
+
+  Page<OfficeEntity> findByParentOfficeIdIsNull(final Pageable pageable);
+
+  Page<OfficeEntity> findByParentOfficeId(final Long parentOfficeId, final Pageable pageable);
+
+  Page<OfficeEntity> findByIdentifierContainingOrNameContaining(final String identifier, final String name, final Pageable pageable);
+}
diff --git a/service/src/main/java/io/mifos/office/internal/service/EmployeeService.java b/service/src/main/java/io/mifos/office/internal/service/EmployeeService.java
new file mode 100644
index 0000000..e4414c0
--- /dev/null
+++ b/service/src/main/java/io/mifos/office/internal/service/EmployeeService.java
@@ -0,0 +1,125 @@
+/*
+ * 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.office.internal.service;
+
+import io.mifos.core.lang.ServiceException;
+import io.mifos.office.ServiceConstants;
+import io.mifos.office.api.v1.domain.ContactDetail;
+import io.mifos.office.api.v1.domain.Employee;
+import io.mifos.office.api.v1.domain.EmployeePage;
+import io.mifos.office.internal.mapper.ContactDetailMapper;
+import io.mifos.office.internal.mapper.EmployeeMapper;
+import io.mifos.office.internal.repository.ContactDetailEntity;
+import io.mifos.office.internal.repository.ContactDetailRepository;
+import io.mifos.office.internal.repository.EmployeeEntity;
+import io.mifos.office.internal.repository.EmployeeRepository;
+import io.mifos.office.internal.repository.OfficeEntity;
+import io.mifos.office.internal.repository.OfficeRepository;
+import org.slf4j.Logger;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.Pageable;
+import org.springframework.stereotype.Service;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Optional;
+import java.util.stream.Collectors;
+
+@Service
+public class EmployeeService {
+
+  private final Logger logger;
+  private final EmployeeRepository employeeRepository;
+  private final ContactDetailRepository contactDetailRepository;
+  private final OfficeRepository officeRepository;
+
+  @Autowired
+  public EmployeeService(@Qualifier(ServiceConstants.REST_LOGGER_NAME) final Logger logger,
+                         final EmployeeRepository employeeRepository,
+                         final ContactDetailRepository contactDetailRepository,
+                         final OfficeRepository officeRepository) {
+    super();
+    this.logger = logger;
+    this.employeeRepository = employeeRepository;
+    this.contactDetailRepository = contactDetailRepository;
+    this.officeRepository = officeRepository;
+  }
+
+  public Boolean employeeExists(final String code) {
+    return this.employeeRepository.existsByIdentifier(code);
+  }
+
+  public Optional<Employee> findByCode(final String code) {
+    final EmployeeEntity employeeEntity = this.employeeRepository.findByIdentifier(code);
+    if (employeeEntity != null) {
+      final Employee employee = EmployeeMapper.map(employeeEntity);
+      employee.setContactDetails(this.findContactDetailsByEmployee(employeeEntity.getIdentifier()));
+      return Optional.of(employee);
+    } else {
+      return Optional.empty();
+    }
+  }
+
+  public EmployeePage findEmployees(final String term, final String officeIdentifier, final Pageable pageRequest) {
+
+    final Page<EmployeeEntity> employeeEntityPage;
+    if (term != null) {
+      employeeEntityPage = this.employeeRepository.findByIdentifierContaining(term, pageRequest);
+    } else if (officeIdentifier != null) {
+      final OfficeEntity officeEntity = this.officeRepository.findByIdentifier(officeIdentifier)
+          .orElseThrow(() -> ServiceException.notFound("Office {0} not found.", officeIdentifier));
+      employeeEntityPage = this.employeeRepository.findByAssignedOffice(officeEntity, pageRequest);
+    } else {
+      employeeEntityPage = this.employeeRepository.findAll(pageRequest);
+    }
+
+    final EmployeePage employeePage = new EmployeePage();
+    employeePage.setTotalPages(employeeEntityPage.getTotalPages());
+    employeePage.setTotalElements(employeeEntityPage.getTotalElements());
+
+    final List<Employee> employees = new ArrayList<>();
+    employeePage.setEmployees(employees);
+    employeeEntityPage.forEach(employeeEntity -> {
+      final Employee employee = EmployeeMapper.map(employeeEntity);
+      employees.add(employee);
+
+      employee.setContactDetails(this.findContactDetailsByEmployee(employeeEntity.getIdentifier()));
+
+    });
+
+    return employeePage;
+  }
+
+
+  public List<ContactDetail> findContactDetailsByEmployee(final String identifier) {
+    final EmployeeEntity employeeEntity = this.employeeRepository.findByIdentifier(identifier);
+    if (employeeEntity == null) {
+      throw ServiceException.notFound("Employee {0} not found.", identifier);
+    }
+
+    final List<ContactDetailEntity> contactDetailEntities = this.contactDetailRepository.findByEmployeeOrderByPreferenceLevelAsc(employeeEntity);
+    if (contactDetailEntities != null && !contactDetailEntities.isEmpty()) {
+      return contactDetailEntities
+          .stream()
+          .map(ContactDetailMapper::map)
+          .collect(Collectors.toList());
+    }
+    return Collections.emptyList();
+  }
+}
diff --git a/service/src/main/java/io/mifos/office/internal/service/OfficeService.java b/service/src/main/java/io/mifos/office/internal/service/OfficeService.java
new file mode 100644
index 0000000..b2ce26b
--- /dev/null
+++ b/service/src/main/java/io/mifos/office/internal/service/OfficeService.java
@@ -0,0 +1,150 @@
+/*
+ * 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.office.internal.service;
+
+import io.mifos.core.lang.ServiceException;
+import io.mifos.office.ServiceConstants;
+import io.mifos.office.api.v1.domain.Address;
+import io.mifos.office.api.v1.domain.Office;
+import io.mifos.office.api.v1.domain.OfficePage;
+import io.mifos.office.internal.mapper.AddressMapper;
+import io.mifos.office.internal.mapper.OfficeMapper;
+import io.mifos.office.internal.repository.AddressEntity;
+import io.mifos.office.internal.repository.AddressRepository;
+import io.mifos.office.internal.repository.OfficeEntity;
+import io.mifos.office.internal.repository.OfficeRepository;
+import org.slf4j.Logger;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.Pageable;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Optional;
+
+@Service
+public class OfficeService {
+
+  private final Logger logger;
+  private final OfficeRepository officeRepository;
+  private final AddressRepository addressRepository;
+
+  @Autowired
+  public OfficeService(@Qualifier(ServiceConstants.SERVICE_LOGGER_NAME) final Logger logger,
+                       final OfficeRepository officeRepository,
+                       final AddressRepository addressRepository) {
+    super();
+    this.logger = logger;
+    this.officeRepository = officeRepository;
+    this.addressRepository = addressRepository;
+  }
+
+  public boolean officeExists(final String identifier) {
+    return this.officeRepository.existsByIdentifier(identifier);
+  }
+
+  public boolean branchExists(final String identifier) {
+    final Optional<OfficeEntity> officeEntityOptional = this.officeRepository.findByIdentifier(identifier);
+    if (officeEntityOptional.isPresent()) {
+      return this.officeRepository.existsByParentOfficeId(officeEntityOptional.get().getId());
+    } else {
+      throw ServiceException.notFound("Office {0} not found.", identifier);
+    }
+  }
+
+  @Transactional(readOnly = true)
+  public OfficePage fetchOffices(final String term, final Pageable pageRequest) {
+    final Page<OfficeEntity> officeEntityPage;
+    if (term != null) {
+      officeEntityPage = this.officeRepository.findByIdentifierContainingOrNameContaining(term, term, pageRequest);
+    } else {
+      officeEntityPage = this.officeRepository.findByParentOfficeIdIsNull(pageRequest);
+    }
+
+    final OfficePage officePage = new OfficePage();
+    officePage.setTotalPages(officeEntityPage.getTotalPages());
+    officePage.setTotalElements(officeEntityPage.getTotalElements());
+    officePage.setOffices(this.extractOfficeEntities(officeEntityPage, null));
+
+    return officePage;
+  }
+
+  public Optional<Office> findOfficeByIdentifier(final String identifier) {
+    final Optional<OfficeEntity> officeEntityOptional = this.officeRepository.findByIdentifier(identifier);
+
+    if (officeEntityOptional.isPresent()) {
+      final Optional<Office> officeOptional = officeEntityOptional.map(OfficeMapper::map);
+
+      officeOptional.ifPresent(office -> {
+        final Long parentOfficeId = officeEntityOptional.get().getParentOfficeId();
+        if(parentOfficeId != null) {
+          final OfficeEntity parentEntity = this.officeRepository.getOne(parentOfficeId);
+          office.setParentIdentifier(parentEntity.getIdentifier());
+        }
+
+        final Optional<AddressEntity> addressEntityOptional = this.addressRepository.findByOffice(officeEntityOptional.get());
+        addressEntityOptional.ifPresent(addressEntity -> office.setAddress(AddressMapper.map(addressEntity)));
+      });
+
+      return officeOptional;
+    }
+    return Optional.empty();
+  }
+
+  public Optional<Address> findAddressOfOffice(final String identifier) {
+    final Optional<OfficeEntity> officeEntityOptional = this.officeRepository.findByIdentifier(identifier);
+
+    if (!officeEntityOptional.isPresent()) {
+      throw ServiceException.notFound("Office {0} not found.", identifier);
+    }
+
+    final Optional<AddressEntity> addressEntityOptional = this.addressRepository.findByOffice(officeEntityOptional.get());
+
+    return addressEntityOptional.map(AddressMapper::map);
+  }
+
+  @Transactional(readOnly = true)
+  public OfficePage fetchBranches(final String parentIdentifier, final Pageable pageRequest) {
+    final OfficeEntity parentOfficeEntity = this.officeRepository.findByIdentifier(parentIdentifier)
+        .orElseThrow(() -> ServiceException.notFound("Parent office {0} not found!", parentIdentifier));
+
+    final Page<OfficeEntity> officeEntityPage = this.officeRepository.findByParentOfficeId(parentOfficeEntity.getId(), pageRequest);
+    final OfficePage officePage = new OfficePage();
+    officePage.setTotalPages(officeEntityPage.getTotalPages());
+    officePage.setTotalElements(officeEntityPage.getTotalElements());
+    officePage.setOffices(this.extractOfficeEntities(officeEntityPage, parentIdentifier));
+
+    return officePage;
+  }
+
+  public List<Office> extractOfficeEntities(final Page<OfficeEntity> officeEntityPage, final String parentIdentifier) {
+    final List<Office> offices = new ArrayList<>(officeEntityPage.getSize());
+    officeEntityPage.forEach(officeEntity -> {
+      final Office office = OfficeMapper.map(officeEntity);
+      if (parentIdentifier != null) {
+        office.setParentIdentifier(parentIdentifier);
+      }
+      offices.add(office);
+
+      final Optional<AddressEntity> addressEntityOptional = this.addressRepository.findByOffice(officeEntity);
+      addressEntityOptional.ifPresent(addressEntity -> office.setAddress(AddressMapper.map(addressEntity)));
+    });
+    return offices;
+  }
+}
diff --git a/service/src/main/java/io/mifos/office/rest/config/OfficeRestConfiguration.java b/service/src/main/java/io/mifos/office/rest/config/OfficeRestConfiguration.java
new file mode 100644
index 0000000..5de5ebc
--- /dev/null
+++ b/service/src/main/java/io/mifos/office/rest/config/OfficeRestConfiguration.java
@@ -0,0 +1,61 @@
+/*
+ * 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.office.rest.config;
+
+import io.mifos.anubis.config.EnableAnubis;
+import io.mifos.core.async.config.EnableAsync;
+import io.mifos.core.cassandra.config.EnableCassandra;
+import io.mifos.core.command.config.EnableCommandProcessing;
+import io.mifos.core.lang.config.EnableServiceException;
+import io.mifos.core.lang.config.EnableTenantContext;
+import io.mifos.office.ServiceConstants;
+import io.mifos.office.internal.config.OfficeServiceConfiguration;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
+import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.ComponentScan;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Import;
+
+@Configuration
+@EnableAutoConfiguration
+@EnableDiscoveryClient
+@EnableAsync
+@EnableTenantContext
+@EnableCassandra
+@EnableCommandProcessing
+@EnableServiceException
+@EnableAnubis
+@ComponentScan({
+    "io.mifos.office.rest.controller"
+})
+@Import({
+    OfficeServiceConfiguration.class
+})
+public class OfficeRestConfiguration {
+
+  public OfficeRestConfiguration() {
+    super();
+  }
+
+  @Bean(name = ServiceConstants.REST_LOGGER_NAME)
+  public Logger logger() {
+    return LoggerFactory.getLogger(ServiceConstants.REST_LOGGER_NAME);
+  }
+
+}
diff --git a/service/src/main/java/io/mifos/office/rest/controller/OfficeRestController.java b/service/src/main/java/io/mifos/office/rest/controller/OfficeRestController.java
new file mode 100644
index 0000000..ac60901
--- /dev/null
+++ b/service/src/main/java/io/mifos/office/rest/controller/OfficeRestController.java
@@ -0,0 +1,449 @@
+/*
+ * 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.office.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.office.api.v1.PermittableGroupIds;
+import io.mifos.office.api.v1.domain.*;
+import io.mifos.office.internal.command.DeleteAddressOfOfficeCommand;
+import io.mifos.office.internal.service.EmployeeService;
+import io.mifos.office.internal.service.OfficeService;
+import io.mifos.office.internal.command.AddBranchCommand;
+import io.mifos.office.internal.command.CreateEmployeeCommand;
+import io.mifos.office.internal.command.CreateOfficeCommand;
+import io.mifos.office.internal.command.DeleteContactDetailCommand;
+import io.mifos.office.internal.command.DeleteEmployeeCommand;
+import io.mifos.office.internal.command.DeleteOfficeCommand;
+import io.mifos.office.internal.command.InitializeServiceCommand;
+import io.mifos.office.internal.command.SetAddressForOfficeCommand;
+import io.mifos.office.internal.command.SetContactDetailsCommand;
+import io.mifos.office.internal.command.UpdateEmployeeCommand;
+import io.mifos.office.internal.command.UpdateOfficeCommand;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.data.domain.PageRequest;
+import org.springframework.data.domain.Pageable;
+import org.springframework.data.domain.Sort;
+import org.springframework.http.MediaType;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.*;
+
+import javax.validation.Valid;
+import java.util.List;
+import java.util.Optional;
+
+@RestController
+@RequestMapping("/")
+public class OfficeRestController {
+
+  private final CommandGateway commandGateway;
+  private final OfficeService officeService;
+  private final EmployeeService employeeService;
+
+  @Autowired
+  public OfficeRestController(final CommandGateway commandGateway,
+                              final OfficeService officeService,
+                              final EmployeeService employeeService) {
+    super();
+    this.commandGateway = commandGateway;
+    this.officeService = officeService;
+    this.employeeService = employeeService;
+  }
+
+  @Permittable(value = AcceptedTokenType.SYSTEM)
+  @RequestMapping(
+      value = "/initialize",
+      method = RequestMethod.POST,
+      consumes = MediaType.ALL_VALUE,
+      produces = MediaType.APPLICATION_JSON_VALUE
+  )
+  public
+  @ResponseBody
+  ResponseEntity<Void> initialize() {
+    this.commandGateway.process(new InitializeServiceCommand());
+    return ResponseEntity.accepted().build();
+  }
+
+  @Permittable(value = AcceptedTokenType.TENANT, groupId = PermittableGroupIds.OFFICE_MANAGEMENT)
+  @RequestMapping(
+      value = "/offices",
+      method = RequestMethod.POST,
+      consumes = MediaType.APPLICATION_JSON_VALUE,
+      produces = MediaType.APPLICATION_JSON_VALUE
+  )
+  public
+  @ResponseBody
+  ResponseEntity<Void> createOffice(@RequestBody @Valid final Office office) throws InterruptedException {
+    if (office == null) {
+      throw ServiceException.badRequest("An office must be given.");
+    }
+
+    if (this.officeService.officeExists(office.getIdentifier())) {
+      throw ServiceException.conflict("Office {0} already exists.", office.getIdentifier());
+    }
+    this.commandGateway.process(new CreateOfficeCommand(office));
+    return ResponseEntity.accepted().build();
+  }
+
+  @Permittable(value = AcceptedTokenType.TENANT, groupId = PermittableGroupIds.OFFICE_MANAGEMENT)
+  @RequestMapping(
+      value = "/offices",
+      method = RequestMethod.GET,
+      consumes = MediaType.ALL_VALUE,
+      produces = MediaType.APPLICATION_JSON_VALUE
+  )
+  public
+  @ResponseBody
+  ResponseEntity<OfficePage> fetchOffices(@RequestParam(value = "term", required = false) final String term,
+                                          @RequestParam(value = "pageIndex", required = false) final Integer pageIndex,
+                                          @RequestParam(value = "size", required = false) final Integer size,
+                                          @RequestParam(value = "sortColumn", required = false) final String sortColumn,
+                                          @RequestParam(value = "sortDirection", required = false) final String sortDirection) {
+    return ResponseEntity.ok(this.officeService.fetchOffices(term, this.createPageRequest(pageIndex, size, sortColumn, sortDirection)));
+  }
+
+  @Permittable(value = AcceptedTokenType.TENANT, groupId = PermittableGroupIds.OFFICE_MANAGEMENT)
+  @RequestMapping(
+      value = "/offices/{identifier}",
+      method = RequestMethod.GET,
+      consumes = MediaType.ALL_VALUE,
+      produces = MediaType.APPLICATION_JSON_VALUE
+  )
+  public
+  @ResponseBody
+  ResponseEntity<Office> findOffice(@PathVariable("identifier") final String identifier) {
+    final Optional<Office> office = this.officeService.findOfficeByIdentifier(identifier);
+    if (office.isPresent()) {
+      return ResponseEntity.ok(office.get());
+    } else {
+      throw ServiceException.notFound("Office with identifier {0} not found.", identifier);
+    }
+  }
+
+  @Permittable(value = AcceptedTokenType.TENANT, groupId = PermittableGroupIds.OFFICE_MANAGEMENT)
+  @RequestMapping(
+      value = "/offices/{identifier}",
+      method = RequestMethod.PUT,
+      consumes = MediaType.APPLICATION_JSON_VALUE,
+      produces = MediaType.APPLICATION_JSON_VALUE
+  )
+  public
+  @ResponseBody
+  ResponseEntity<Void> updateOffice(@PathVariable("identifier") final String identifier,
+                                    @RequestBody @Valid final Office office) throws InterruptedException {
+    if (!this.officeService.officeExists(identifier)) {
+      throw ServiceException.notFound("Office {0} not found.", identifier);
+    }
+
+    if (office.getIdentifier() != null && !identifier.equals(office.getIdentifier())) {
+      throw ServiceException.badRequest("Office identifier must match resource identifier");
+    }
+
+    this.commandGateway.process(new UpdateOfficeCommand(office));
+    return ResponseEntity.accepted().build();
+  }
+
+  @Permittable(value = AcceptedTokenType.TENANT, groupId = PermittableGroupIds.OFFICE_MANAGEMENT)
+  @RequestMapping(
+      value = "/offices/{identifier}",
+      method = RequestMethod.POST,
+      consumes = MediaType.APPLICATION_JSON_VALUE,
+      produces = MediaType.APPLICATION_JSON_VALUE
+  )
+  public
+  @ResponseBody
+  ResponseEntity<Void> addBranch(@PathVariable("identifier") final String identifier,
+                                 @RequestBody @Valid final Office office) {
+    if (!this.officeService.officeExists(identifier)) {
+      throw ServiceException.notFound("Parent office {0} not found.", identifier);
+    }
+
+    this.commandGateway.process(new AddBranchCommand(identifier, office));
+    return ResponseEntity.accepted().build();
+  }
+
+  @Permittable(value = AcceptedTokenType.TENANT, groupId = PermittableGroupIds.OFFICE_MANAGEMENT)
+  @RequestMapping(
+      value = "/offices/{identifier}/branches",
+      method = RequestMethod.GET,
+      produces = MediaType.APPLICATION_JSON_VALUE,
+      consumes = MediaType.APPLICATION_JSON_VALUE
+  )
+  public
+  @ResponseBody
+  ResponseEntity<OfficePage> getBranches(@PathVariable("identifier") final String identifier,
+                                         @RequestParam(value = "pageIndex", required = false) final Integer pageIndex,
+                                         @RequestParam(value = "size", required = false) final Integer size,
+                                         @RequestParam(value = "sortColumn", required = false) final String sortColumn,
+                                         @RequestParam(value = "sortDirection", required = false) final String sortDirection) {
+    if (!this.officeService.officeExists(identifier)) {
+      throw ServiceException.notFound("Parent office {0} not found.", identifier);
+    }
+    return ResponseEntity.ok(this.officeService.fetchBranches(identifier, this.createPageRequest(pageIndex, size, sortColumn, sortDirection)));
+  }
+
+  @Permittable(value = AcceptedTokenType.TENANT, groupId = PermittableGroupIds.OFFICE_MANAGEMENT)
+  @RequestMapping(
+      value = "/offices/{identifier}",
+      method = RequestMethod.DELETE,
+      consumes = MediaType.ALL_VALUE,
+      produces = MediaType.APPLICATION_JSON_VALUE
+  )
+  public
+  @ResponseBody
+  ResponseEntity<Void> deleteOffice(@PathVariable("identifier") final String identifier)
+      throws InterruptedException {
+    if (this.officeService.branchExists(identifier)) {
+      throw ServiceException.conflict("Office {0} has children.", identifier);
+    }
+
+    if (this.officeService.officeExists(identifier)) {
+      this.commandGateway.process(new DeleteOfficeCommand(identifier));
+    }
+
+    return ResponseEntity.accepted().build();
+  }
+
+  @Permittable(value = AcceptedTokenType.TENANT, groupId = PermittableGroupIds.OFFICE_MANAGEMENT)
+  @RequestMapping(
+      value = "/offices/{identifier}/address",
+      method = RequestMethod.PUT,
+      consumes = MediaType.APPLICATION_JSON_VALUE,
+      produces = MediaType.APPLICATION_JSON_VALUE
+  )
+  public
+  @ResponseBody
+  ResponseEntity<Void> setAddressForOffice(@PathVariable("identifier") final String identifier,
+                                           @RequestBody @Valid final Address address) {
+    if (!this.officeService.officeExists(identifier)) {
+      throw ServiceException.notFound("Office {0} not found.", identifier);
+    }
+
+    this.commandGateway.process(new SetAddressForOfficeCommand(identifier, address));
+    return ResponseEntity.accepted().build();
+  }
+
+  @Permittable(value = AcceptedTokenType.TENANT, groupId = PermittableGroupIds.OFFICE_MANAGEMENT)
+  @RequestMapping(
+      value = "/offices/{identifier}/address",
+      method = RequestMethod.GET,
+      consumes = MediaType.ALL_VALUE,
+      produces = MediaType.APPLICATION_JSON_VALUE
+  )
+  public
+  @ResponseBody
+  ResponseEntity getAddressOfOffice(@PathVariable("identifier") final String identifier) {
+    final Optional<Address> addressOfOffice = this.officeService.findAddressOfOffice(identifier);
+    if (addressOfOffice.isPresent()) {
+      return ResponseEntity.ok(addressOfOffice.get());
+    } else {
+      return ResponseEntity.noContent().build();
+    }
+  }
+
+  @Permittable(value = AcceptedTokenType.TENANT, groupId = PermittableGroupIds.OFFICE_MANAGEMENT)
+  @RequestMapping(
+      value = "/offices/{identifier}/address",
+      method = RequestMethod.DELETE,
+      consumes = MediaType.ALL_VALUE,
+      produces = MediaType.APPLICATION_JSON_VALUE
+  )
+  public
+  @ResponseBody
+  ResponseEntity<Void> deleteAddressOfOffice(@PathVariable("identifier") final String identifier) {
+    if (!this.officeService.officeExists(identifier)) {
+      throw ServiceException.notFound("Parent office {0} not found.", identifier);
+    }
+
+    this.commandGateway.process(new DeleteAddressOfOfficeCommand(identifier));
+    return ResponseEntity.accepted().build();
+  }
+
+  @Permittable(value = AcceptedTokenType.TENANT, groupId = PermittableGroupIds.EMPLOYEE_MANAGEMENT)
+  @RequestMapping(
+      value = "/employees",
+      method = RequestMethod.POST,
+      consumes = MediaType.APPLICATION_JSON_VALUE,
+      produces = MediaType.APPLICATION_JSON_VALUE
+  )
+  public
+  @ResponseBody
+  ResponseEntity<Void> createEmployee(@RequestBody @Valid final Employee employee) throws InterruptedException {
+    if (employee.getAssignedOffice() != null && !this.officeService.officeExists(employee.getAssignedOffice())) {
+      throw ServiceException.notFound("Office {0} to assign not found.", employee.getAssignedOffice());
+    }
+
+    if (this.employeeService.employeeExists(employee.getIdentifier())) {
+      throw ServiceException.conflict("Employee {0} already exists.", employee.getIdentifier());
+    }
+
+    this.commandGateway.process(new CreateEmployeeCommand(employee));
+    return ResponseEntity.accepted().build();
+  }
+
+  @Permittable(value = AcceptedTokenType.TENANT, groupId = PermittableGroupIds.EMPLOYEE_MANAGEMENT)
+  @RequestMapping(
+      value = "/employees",
+      method = RequestMethod.GET,
+      consumes = MediaType.ALL_VALUE,
+      produces = MediaType.APPLICATION_JSON_VALUE
+  )
+  public
+  @ResponseBody
+  ResponseEntity<EmployeePage> fetchEmployees(@RequestParam(value = "term", required = false) final String term,
+                                              @RequestParam(value = "office", required = false) final String officeIdentifier,
+                                              @RequestParam(value = "pageIndex", required = false) final Integer pageIndex,
+                                              @RequestParam(value = "size", required = false) final Integer size,
+                                              @RequestParam(value = "sortColumn", required = false) final String sortColumn,
+                                              @RequestParam(value = "sortDirection", required = false) final String sortDirection) {
+    if (officeIdentifier != null && !this.officeService.officeExists(officeIdentifier)) {
+      throw ServiceException.notFound("Office {0} not found.", officeIdentifier);
+    }
+    return ResponseEntity.ok(this.employeeService.findEmployees(term, officeIdentifier, this.createPageRequest(pageIndex, size, sortColumn, sortDirection)));
+  }
+
+  @Permittable(value = AcceptedTokenType.TENANT, groupId = PermittableGroupIds.EMPLOYEE_MANAGEMENT)
+  @Permittable(value = AcceptedTokenType.TENANT, groupId = PermittableGroupIds.SELF_MANAGEMENT, permittedEndpoint = "/employees/{useridentifier}")
+  @RequestMapping(
+      value = "/employees/{useridentifier}",
+      method = RequestMethod.GET,
+      consumes = MediaType.ALL_VALUE,
+      produces = MediaType.APPLICATION_JSON_VALUE
+  )
+  public
+  @ResponseBody
+  ResponseEntity<Employee> findEmployee(@PathVariable("useridentifier") final String identifier) {
+    final Optional<Employee> employee = this.employeeService.findByCode(identifier);
+    if (employee.isPresent()) {
+      return ResponseEntity.ok(employee.get());
+    } else {
+      throw ServiceException.notFound("Employee with identifier {0} not found.", identifier);
+    }
+  }
+
+  @Permittable(value = AcceptedTokenType.TENANT, groupId = PermittableGroupIds.EMPLOYEE_MANAGEMENT)
+  @Permittable(value = AcceptedTokenType.TENANT, groupId = PermittableGroupIds.SELF_MANAGEMENT, permittedEndpoint = "/employees/{useridentifier}")
+  @RequestMapping(
+      value = "/employees/{useridentifier}",
+      method = RequestMethod.PUT,
+      consumes = MediaType.APPLICATION_JSON_VALUE,
+      produces = MediaType.APPLICATION_JSON_VALUE
+  )
+  public
+  @ResponseBody
+  ResponseEntity<Void> updateEmployee(@PathVariable("useridentifier") final String identifier,
+                                      @RequestBody @Valid final Employee employee) {
+    if (!this.employeeService.employeeExists(identifier)) {
+      throw ServiceException.notFound("Employee {0} not found.", identifier);
+    }
+
+    if (employee.getIdentifier() != null && !identifier.equals(employee.getIdentifier())) {
+      throw ServiceException.badRequest("Employee code must match resource identifier");
+    }
+
+    if (employee.getAssignedOffice() != null && !this.officeService.officeExists(employee.getAssignedOffice())) {
+      throw ServiceException.notFound("Office {0} to assign not found.", employee.getAssignedOffice());
+    }
+
+    this.commandGateway.process(new UpdateEmployeeCommand(employee));
+
+    return ResponseEntity.accepted().build();
+  }
+
+  @Permittable(value = AcceptedTokenType.TENANT, groupId = PermittableGroupIds.EMPLOYEE_MANAGEMENT)
+  @RequestMapping(
+      value = "/employees/{useridentifier}",
+      method = RequestMethod.DELETE,
+      consumes = MediaType.ALL_VALUE,
+      produces = MediaType.APPLICATION_JSON_VALUE
+  )
+  public
+  @ResponseBody
+  ResponseEntity<Void> deleteEmployee(@PathVariable("useridentifier") final String identifier) {
+    if (this.employeeService.employeeExists(identifier)) {
+      this.commandGateway.process(new DeleteEmployeeCommand(identifier));
+    }
+    return ResponseEntity.accepted().build();
+  }
+
+  @Permittable(value = AcceptedTokenType.TENANT, groupId = PermittableGroupIds.EMPLOYEE_MANAGEMENT)
+  @Permittable(value = AcceptedTokenType.TENANT, groupId = PermittableGroupIds.SELF_MANAGEMENT, permittedEndpoint = "/employees/{useridentifier}/contacts")
+  @RequestMapping(
+      value = "/employees/{useridentifier}/contacts",
+      method = RequestMethod.PUT,
+      consumes = MediaType.ALL_VALUE,
+      produces = MediaType.APPLICATION_JSON_VALUE
+  )
+  public
+  @ResponseBody
+  ResponseEntity<Void> setContactDetails(@PathVariable("useridentifier") final String identifier,
+                                         @RequestBody @Valid final List<ContactDetail> contactDetails) {
+    if (!this.employeeService.employeeExists(identifier)) {
+      throw ServiceException.notFound("Employee {0} not found.", identifier);
+    }
+
+    this.commandGateway.process(new SetContactDetailsCommand(identifier, contactDetails));
+
+    return ResponseEntity.accepted().build();
+  }
+
+  @Permittable(value = AcceptedTokenType.TENANT, groupId = PermittableGroupIds.EMPLOYEE_MANAGEMENT)
+  @Permittable(value = AcceptedTokenType.TENANT, groupId = PermittableGroupIds.SELF_MANAGEMENT, permittedEndpoint = "/employees/{useridentifier}/contacts")
+  @RequestMapping(
+      value = "/employees/{useridentifier}/contacts",
+      method = RequestMethod.GET,
+      consumes = MediaType.ALL_VALUE,
+      produces = MediaType.APPLICATION_JSON_VALUE
+  )
+  public
+  @ResponseBody
+  ResponseEntity<List<ContactDetail>> fetchContactDetails(@PathVariable("useridentifier") final String identifier) {
+    if (!this.employeeService.employeeExists(identifier)) {
+      throw ServiceException.notFound("Employee {0} not found.", identifier);
+    }
+    return ResponseEntity.ok(this.employeeService.findContactDetailsByEmployee(identifier));
+  }
+
+  @Permittable(value = AcceptedTokenType.TENANT, groupId = PermittableGroupIds.EMPLOYEE_MANAGEMENT)
+  @Permittable(value = AcceptedTokenType.TENANT, groupId = PermittableGroupIds.SELF_MANAGEMENT, permittedEndpoint = "/employees/{useridentifier}/contacts")
+  @RequestMapping(
+      value = "/employees/{useridentifier}/contacts",
+      method = RequestMethod.DELETE,
+      consumes = MediaType.ALL_VALUE,
+      produces = MediaType.APPLICATION_JSON_VALUE
+  )
+  public
+  @ResponseBody
+  ResponseEntity<Void> deleteContactDetails(@PathVariable("useridentifier") final String identifier) {
+    if (!this.employeeService.employeeExists(identifier)) {
+      throw ServiceException.notFound("Employee {0} not found.", identifier);
+    }
+
+    this.commandGateway.process(new DeleteContactDetailCommand(identifier));
+
+    return ResponseEntity.accepted().build();
+  }
+
+  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;
+    final String sortColumnToUse = sortColumn != null ? sortColumn : "identifier";
+    final Sort.Direction direction = sortDirection != null ? Sort.Direction.valueOf(sortDirection.toUpperCase()) : Sort.Direction.ASC;
+    return new PageRequest(pageIndexToUse, sizeToUse, direction, sortColumnToUse);
+  }
+}
diff --git a/service/src/main/resources/application.yml b/service/src/main/resources/application.yml
new file mode 100644
index 0000000..c693aa9
--- /dev/null
+++ b/service/src/main/resources/application.yml
@@ -0,0 +1,66 @@
+#
+# Copyright 2017 The Mifos Initiative
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#    http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+spring:
+  cloud:
+    discovery:
+      enabled: false
+    config:
+      enabled: false
+
+eureka:
+  client:
+    serviceUrl:
+      defaultZone: http://localhost:8761/eureka/
+
+server:
+  port: 8081
+  contextPath: /office/v1/*
+
+cassandra:
+  clusterName: staging_cluster
+  contactPoints: 127.0.0.1:9042,127.0.0.2:9042,127.0.0.3:9042
+  keyspace: seshat
+  cl:
+    read: LOCAL_QUORUM
+    write: LOCAL_QUORUM
+    delete: LOCAL_QUORUM
+
+mariadb:
+  driverClass: org.mariadb.jdbc.Driver
+  database: seshat
+  host: localhost
+  port: 3306
+  user: root
+  password: mysql
+
+bonecp:
+  idleMaxAgeInMinutes: 240
+  idleConnectionTestPeriodInMinutes: 60
+  maxConnectionsPerPartition: 10
+  minConnectionsPerPartition: 1
+  partitionCount: 2
+  acquireIncrement: 5
+  statementsCacheSize: 100
+
+async:
+  corePoolSize: 32
+  maxPoolSize: 16384
+  queueCapacity: 0
+  threadName: async-processor-
+
+flyway:
+  enabled: false
\ No newline at end of file
diff --git a/service/src/main/resources/bootstrap.yml b/service/src/main/resources/bootstrap.yml
new file mode 100644
index 0000000..ddd815e
--- /dev/null
+++ b/service/src/main/resources/bootstrap.yml
@@ -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.
+#
+
+spring:
+    application:
+        name: office-v1
diff --git a/service/src/main/resources/db/migrations/mariadb/V1__initial_setup.sql b/service/src/main/resources/db/migrations/mariadb/V1__initial_setup.sql
new file mode 100644
index 0000000..3805815
--- /dev/null
+++ b/service/src/main/resources/db/migrations/mariadb/V1__initial_setup.sql
@@ -0,0 +1,84 @@
+--
+-- 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 horus_offices (
+  id               BIGINT        NOT NULL AUTO_INCREMENT,
+  parent_office_id BIGINT        NULL,
+  identifier       VARCHAR(32)   NOT NULL,
+  a_name           VARCHAR(256)  NOT NULL,
+  description      VARCHAR(2048) 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 office_pk
+  PRIMARY KEY (id),
+  CONSTRAINT office_identifier_uq
+  UNIQUE (identifier),
+  CONSTRAINT office_parent_fk
+  FOREIGN KEY (parent_office_id) REFERENCES horus_offices (id)
+    ON UPDATE RESTRICT
+);
+
+CREATE TABLE horus_addresses (
+  id           BIGINT       NOT NULL AUTO_INCREMENT,
+  office_id    BIGINT       NOT NULL,
+  street       VARCHAR(256) NOT NULL,
+  city         VARCHAR(256) NOT NULL,
+  region       VARCHAR(256) NULL,
+  postal_code  VARCHAR(32)  NOT NULL,
+  country_code VARCHAR(2)   NOT NULL,
+  country      VARCHAR(256) NOT NULL,
+  CONSTRAINT address_pk
+  PRIMARY KEY (id),
+  CONSTRAINT address_office_fk
+  FOREIGN KEY (office_id) REFERENCES horus_offices (id)
+    ON UPDATE RESTRICT
+);
+
+CREATE TABLE horus_employees (
+  id                 BIGINT       NOT NULL AUTO_INCREMENT,
+  identifier         VARCHAR(32)  NOT NULL,
+  given_name         VARCHAR(256) NULL,
+  middle_name        VARCHAR(256) NULL,
+  surname            VARCHAR(256) NULL,
+  assigned_office_id BIGINT       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 employee_pk
+  PRIMARY KEY (id),
+  CONSTRAINT employee_identifier_uq
+  UNIQUE (identifier),
+  CONSTRAINT employee_office_fk
+  FOREIGN KEY (assigned_office_id) REFERENCES horus_offices (id)
+    ON UPDATE RESTRICT
+);
+
+CREATE TABLE horus_contact_details (
+  id               BIGINT       NOT NULL AUTO_INCREMENT,
+  employee_id      BIGINT       NOT NULL,
+  a_type           VARCHAR(256) NOT NULL,
+  a_group          VARCHAR(256) NOT NULL,
+  a_value          VARCHAR(256) NOT NULL,
+  preference_level TINYINT      NULL,
+  CONSTRAINT contact_detail_pk
+  PRIMARY KEY (id),
+  CONSTRAINT contact_detail_employee_fk
+  FOREIGN KEY (employee_id) REFERENCES horus_employees (id)
+    ON UPDATE RESTRICT
+);
\ No newline at end of file
diff --git a/service/src/main/resources/logback.xml b/service/src/main/resources/logback.xml
new file mode 100644
index 0000000..9d04222
--- /dev/null
+++ b/service/src/main/resources/logback.xml
@@ -0,0 +1,40 @@
+<!--
+
+    Copyright 2017 The Mifos Initiative
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+
+-->
+<configuration>
+    <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
+        <file>office.log</file>
+        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
+            <fileNamePattern>office.%d{yyyy-MM-dd}.log</fileNamePattern>
+            <maxHistory>30</maxHistory>
+            <totalSizeCap>3GB</totalSizeCap>
+        </rollingPolicy>
+        <encoder>
+            <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
+        </encoder>
+    </appender>
+    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
+        <encoder>
+            <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
+        </encoder>
+    </appender>
+
+    <root level="DEBUG">
+        <appender-ref ref="FILE"/>
+        <appender-ref ref="STDOUT"/>
+    </root>
+</configuration>
\ No newline at end of file
diff --git a/settings.gradle b/settings.gradle
new file mode 100644
index 0000000..856a732
--- /dev/null
+++ b/settings.gradle
@@ -0,0 +1,6 @@
+rootProject.name = 'office'
+
+includeBuild 'api'
+includeBuild 'service'
+includeBuild 'component-test'
+
diff --git a/shared.gradle b/shared.gradle
new file mode 100644
index 0000000..caa8c64
--- /dev/null
+++ b/shared.gradle
@@ -0,0 +1,73 @@
+group 'io.mifos.office'
+version '0.1.0-BUILD-SNAPSHOT'
+
+ext.versions = [
+        frameworkapi : '0.1.0-BUILD-SNAPSHOT',
+        frameworklang : '0.1.0-BUILD-SNAPSHOT',
+        frameworkasync : '0.1.0-BUILD-SNAPSHOT',
+        frameworkcassandra : '0.1.0-BUILD-SNAPSHOT',
+        frameworkmariadb : '0.1.0-BUILD-SNAPSHOT',
+        frameworkcommand : '0.1.0-BUILD-SNAPSHOT',
+        frameworktest: '0.1.0-BUILD-SNAPSHOT',
+        frameworkanubis: '0.1.0-BUILD-SNAPSHOT',
+        validator  : '5.3.0.Final'
+]
+
+apply plugin: 'java'
+apply plugin: 'idea'
+apply plugin: 'maven-publish'
+apply plugin: 'io.spring.dependency-management'
+
+tasks.withType(JavaCompile) {
+    sourceCompatibility = JavaVersion.VERSION_1_8
+    targetCompatibility = JavaVersion.VERSION_1_8
+}
+
+repositories {
+    jcenter()
+    mavenLocal()
+}
+
+configurations {
+    compile.exclude group: 'commons-logging', module: 'commons-logging'
+}
+
+dependencyManagement {
+    imports {
+        mavenBom 'io.spring.platform:platform-bom:Athens-RELEASE'
+        mavenBom 'org.springframework.cloud:spring-cloud-dependencies:Camden.SR1'
+    }
+}
+
+// override certain dependency provided by Spring platform using newer releases
+ext['cassandra.version'] = '3.6'
+ext['cassandra-driver.version'] = '3.1.2'
+ext['activemq.version'] = '5.13.2'
+ext['spring-data-releasetrain.version'] = 'Gosling-SR2A'
+
+dependencies {
+    compile(
+            [group: 'com.google.code.findbugs', name: 'jsr305']
+    )
+
+    testCompile(
+            [group: 'org.springframework.boot', name: 'spring-boot-starter-test']
+    )
+}
+
+jar {
+    from sourceSets.main.allSource
+}
+
+license {
+    header rootProject.file('../HEADER')
+    strictCheck true
+    mapping {
+        java = 'SLASHSTAR_STYLE'
+        xml = 'XML_STYLE'
+        yml = 'SCRIPT_STYLE'
+        yaml = 'SCRIPT_STYLE'
+    }
+    ext.year = Calendar.getInstance().get(Calendar.YEAR)
+    ext.name = 'The Mifos Initiative'
+}

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

[fineract-cn-office] 02/14: renamed API class

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-office.git

commit 102fb07f48dc6974e91db0d74048d1956c629046
Author: Markus Geiss <mg...@mifos.org>
AuthorDate: Fri Mar 17 17:58:10 2017 +0100

    renamed API class
---
 ...{OfficeClient.java => OrganizationManager.java} |  2 +-
 .../main/java/io/mifos/office/TestEmployee.java    | 88 +++++++++++-----------
 .../src/main/java/io/mifos/office/TestOffice.java  | 72 +++++++++---------
 3 files changed, 81 insertions(+), 81 deletions(-)

diff --git a/api/src/main/java/io/mifos/office/api/v1/client/OfficeClient.java b/api/src/main/java/io/mifos/office/api/v1/client/OrganizationManager.java
similarity index 99%
rename from api/src/main/java/io/mifos/office/api/v1/client/OfficeClient.java
rename to api/src/main/java/io/mifos/office/api/v1/client/OrganizationManager.java
index 195ccfd..3ef36b2 100644
--- a/api/src/main/java/io/mifos/office/api/v1/client/OfficeClient.java
+++ b/api/src/main/java/io/mifos/office/api/v1/client/OrganizationManager.java
@@ -37,7 +37,7 @@ import java.util.List;
 
 @SuppressWarnings("unused")
 @FeignClient(name="office-v1", path="/office/v1", configuration=CustomFeignClientsConfiguration.class)
-public interface OfficeClient {
+public interface OrganizationManager {
 
   @RequestMapping(
       value = "/offices",
diff --git a/component-test/src/main/java/io/mifos/office/TestEmployee.java b/component-test/src/main/java/io/mifos/office/TestEmployee.java
index 9914c77..a826e39 100644
--- a/component-test/src/main/java/io/mifos/office/TestEmployee.java
+++ b/component-test/src/main/java/io/mifos/office/TestEmployee.java
@@ -26,7 +26,7 @@ import io.mifos.core.test.listener.EventRecorder;
 import io.mifos.office.api.v1.EventConstants;
 import io.mifos.office.api.v1.client.AlreadyExistsException;
 import io.mifos.office.api.v1.client.BadRequestException;
-import io.mifos.office.api.v1.client.OfficeClient;
+import io.mifos.office.api.v1.client.OrganizationManager;
 import io.mifos.office.api.v1.client.NotFoundException;
 import io.mifos.office.api.v1.domain.ContactDetail;
 import io.mifos.office.api.v1.domain.Employee;
@@ -79,7 +79,7 @@ public class TestEmployee {
           = new TenantApplicationSecurityEnvironmentTestRule(testEnvironment, this::waitForInitialize);
 
   @Autowired
-  private OfficeClient officeClient;
+  private OrganizationManager organizationManager;
 
   @Autowired
   private EventRecorder eventRecorder;
@@ -108,12 +108,12 @@ public class TestEmployee {
   public void shouldCreateEmployee() throws Exception {
     final Employee employee = EmployeeFactory.createRandomEmployee();
     {
-      this.officeClient.createEmployee(employee);
+      this.organizationManager.createEmployee(employee);
       final boolean found = this.eventRecorder.wait(EventConstants.OPERATION_POST_EMPLOYEE, employee.getIdentifier());
       Assert.assertTrue(found);
     }
 
-    final Employee savedEmployee = this.officeClient.findEmployee(employee.getIdentifier());
+    final Employee savedEmployee = this.organizationManager.findEmployee(employee.getIdentifier());
 
     Assert.assertNotNull(savedEmployee);
     Assert.assertEquals(employee.getIdentifier(), savedEmployee.getIdentifier());
@@ -121,41 +121,41 @@ public class TestEmployee {
     Assert.assertEquals(employee.getMiddleName(), savedEmployee.getMiddleName());
     Assert.assertEquals(employee.getSurname(), savedEmployee.getSurname());
 
-    this.officeClient.deleteEmployee(employee.getIdentifier());
+    this.organizationManager.deleteEmployee(employee.getIdentifier());
     this.eventRecorder.wait(EventConstants.OPERATION_DELETE_EMPLOYEE, employee.getIdentifier());
   }
 
   @Test
   public void shouldNotCreateEmployeeAlreadyExists() throws Exception {
     final Employee employee = EmployeeFactory.createRandomEmployee();
-    this.officeClient.createEmployee(employee);
+    this.organizationManager.createEmployee(employee);
 
     this.eventRecorder.wait(EventConstants.OPERATION_POST_EMPLOYEE, employee.getIdentifier());
 
     try {
-      this.officeClient.createEmployee(employee);
+      this.organizationManager.createEmployee(employee);
       Assert.fail();
     } catch (final AlreadyExistsException ex) {
       // do nothing, expected
     }
 
-    this.officeClient.deleteEmployee(employee.getIdentifier());
+    this.organizationManager.deleteEmployee(employee.getIdentifier());
     this.eventRecorder.wait(EventConstants.OPERATION_DELETE_EMPLOYEE, employee.getIdentifier());
   }
 
   @Test
   public void shouldFindEmployeesByOffice() throws Exception {
     final Office office = OfficeFactory.createRandomOffice();
-    this.officeClient.createOffice(office);
+    this.organizationManager.createOffice(office);
     this.eventRecorder.wait(EventConstants.OPERATION_POST_OFFICE, office.getIdentifier());
 
     final Employee employee = EmployeeFactory.createRandomEmployee();
     employee.setAssignedOffice(office.getIdentifier());
-    this.officeClient.createEmployee(employee);
+    this.organizationManager.createEmployee(employee);
 
     this.eventRecorder.wait(EventConstants.OPERATION_POST_EMPLOYEE, employee.getIdentifier());
 
-    final EmployeePage employeePage = this.officeClient.fetchEmployees(null, office.getIdentifier(), 0, 20, null, null);
+    final EmployeePage employeePage = this.organizationManager.fetchEmployees(null, office.getIdentifier(), 0, 20, null, null);
     Assert.assertEquals(Long.valueOf(1L), employeePage.getTotalElements());
     final Employee savedEmployee = employeePage.getEmployees().get(0);
     Assert.assertEquals(employee.getIdentifier(), savedEmployee.getIdentifier());
@@ -163,45 +163,45 @@ public class TestEmployee {
     Assert.assertEquals(employee.getMiddleName(), savedEmployee.getMiddleName());
     Assert.assertEquals(employee.getSurname(), savedEmployee.getSurname());
 
-    this.officeClient.deleteEmployee(employee.getIdentifier());
+    this.organizationManager.deleteEmployee(employee.getIdentifier());
     this.eventRecorder.wait(EventConstants.OPERATION_DELETE_EMPLOYEE, employee.getIdentifier());
-    this.officeClient.deleteOffice(office.getIdentifier());
+    this.organizationManager.deleteOffice(office.getIdentifier());
     this.eventRecorder.wait(EventConstants.OPERATION_DELETE_OFFICE, office.getIdentifier());
   }
 
   @Test
   public void shouldFindAllEmployees() throws Exception {
     final Employee firstEmployee = EmployeeFactory.createRandomEmployee();
-    this.officeClient.createEmployee(firstEmployee);
+    this.organizationManager.createEmployee(firstEmployee);
     this.eventRecorder.wait(EventConstants.OPERATION_POST_EMPLOYEE, firstEmployee.getIdentifier());
     final Employee secondEmployee = EmployeeFactory.createRandomEmployee();
-    this.officeClient.createEmployee(secondEmployee);
+    this.organizationManager.createEmployee(secondEmployee);
     this.eventRecorder.wait(EventConstants.OPERATION_POST_EMPLOYEE, secondEmployee.getIdentifier());
 
-    final EmployeePage employeePage = this.officeClient.fetchEmployees(null, null, 0, 20, null, null);
+    final EmployeePage employeePage = this.organizationManager.fetchEmployees(null, null, 0, 20, null, null);
     Assert.assertEquals(Long.valueOf(2L), employeePage.getTotalElements());
 
-    this.officeClient.deleteEmployee(firstEmployee.getIdentifier());
+    this.organizationManager.deleteEmployee(firstEmployee.getIdentifier());
     this.eventRecorder.wait(EventConstants.OPERATION_DELETE_EMPLOYEE, firstEmployee.getIdentifier());
-    this.officeClient.deleteEmployee(secondEmployee.getIdentifier());
+    this.organizationManager.deleteEmployee(secondEmployee.getIdentifier());
     this.eventRecorder.wait(EventConstants.OPERATION_DELETE_EMPLOYEE, secondEmployee.getIdentifier());
   }
 
   @Test
   public void shouldDeleteEmployee() throws Exception {
     final Employee employee = EmployeeFactory.createRandomEmployee();
-    this.officeClient.createEmployee(employee);
+    this.organizationManager.createEmployee(employee);
     this.eventRecorder.wait(EventConstants.OPERATION_POST_EMPLOYEE, employee.getIdentifier());
-    Assert.assertNotNull(this.officeClient.findEmployee(employee.getIdentifier()));
+    Assert.assertNotNull(this.organizationManager.findEmployee(employee.getIdentifier()));
 
     {
-      this.officeClient.deleteEmployee(employee.getIdentifier());
+      this.organizationManager.deleteEmployee(employee.getIdentifier());
       final boolean found = this.eventRecorder.wait(EventConstants.OPERATION_DELETE_EMPLOYEE, employee.getIdentifier());
       Assert.assertTrue(found);
     }
 
     try {
-      this.officeClient.findEmployee(employee.getIdentifier());
+      this.organizationManager.findEmployee(employee.getIdentifier());
       Assert.fail();
     } catch (final NotFoundException ex) {
       // do nothing, expected
@@ -211,11 +211,11 @@ public class TestEmployee {
   @Test
   public void shouldUpdateEmployee() throws Exception {
     final Employee employee = EmployeeFactory.createRandomEmployee();
-    this.officeClient.createEmployee(employee);
+    this.organizationManager.createEmployee(employee);
     this.eventRecorder.wait(EventConstants.OPERATION_POST_EMPLOYEE, employee.getIdentifier());
 
     final Office office = OfficeFactory.createRandomOffice();
-    this.officeClient.createOffice(office);
+    this.organizationManager.createOffice(office);
     this.eventRecorder.wait(EventConstants.OPERATION_POST_OFFICE, office.getIdentifier());
 
     final Employee updatedEmployee = EmployeeFactory.createRandomEmployee();
@@ -223,12 +223,12 @@ public class TestEmployee {
     updatedEmployee.setAssignedOffice(office.getIdentifier());
 
     {
-      this.officeClient.updateEmployee(employee.getIdentifier(), updatedEmployee);
+      this.organizationManager.updateEmployee(employee.getIdentifier(), updatedEmployee);
       final boolean found = this.eventRecorder.wait(EventConstants.OPERATION_PUT_EMPLOYEE, employee.getIdentifier());
       Assert.assertTrue(found);
     }
 
-    final Employee savedEmployee = this.officeClient.findEmployee(employee.getIdentifier());
+    final Employee savedEmployee = this.organizationManager.findEmployee(employee.getIdentifier());
     Assert.assertNotNull(savedEmployee);
     Assert.assertEquals(updatedEmployee.getIdentifier(), savedEmployee.getIdentifier());
     Assert.assertEquals(updatedEmployee.getGivenName(), savedEmployee.getGivenName());
@@ -237,7 +237,7 @@ public class TestEmployee {
     Assert.assertEquals(updatedEmployee.getAssignedOffice(), savedEmployee.getAssignedOffice());
 
     {
-      this.officeClient.deleteEmployee(employee.getIdentifier());
+      this.organizationManager.deleteEmployee(employee.getIdentifier());
       final boolean found = this.eventRecorder.wait(EventConstants.OPERATION_DELETE_EMPLOYEE, employee.getIdentifier());
       Assert.assertTrue(found);
     }
@@ -246,44 +246,44 @@ public class TestEmployee {
   @Test
   public void shouldNotUpdateEmployeeCodeMismatch() throws Exception {
     final Employee employee = EmployeeFactory.createRandomEmployee();
-    this.officeClient.createEmployee(employee);
+    this.organizationManager.createEmployee(employee);
     this.eventRecorder.wait(EventConstants.OPERATION_POST_EMPLOYEE, employee.getIdentifier());
 
     final String originalCode = employee.getIdentifier();
     employee.setIdentifier(RandomStringUtils.randomAlphanumeric(8));
 
     try {
-      this.officeClient.updateEmployee(originalCode, employee);
+      this.organizationManager.updateEmployee(originalCode, employee);
       Assert.fail();
     } catch (final BadRequestException ex) {
       // do nothing, expected
     }
 
-    this.officeClient.deleteEmployee(originalCode);
+    this.organizationManager.deleteEmployee(originalCode);
     this.eventRecorder.wait(EventConstants.OPERATION_DELETE_EMPLOYEE, originalCode);
   }
 
   @Test
   public void shouldNotUpdateEmployeeNotFound() throws Exception {
     final Employee employee = EmployeeFactory.createRandomEmployee();
-    this.officeClient.createEmployee(employee);
+    this.organizationManager.createEmployee(employee);
     this.eventRecorder.wait(EventConstants.OPERATION_POST_EMPLOYEE, employee.getIdentifier());
 
     try {
-      this.officeClient.updateEmployee(RandomStringUtils.randomAlphanumeric(8), employee);
+      this.organizationManager.updateEmployee(RandomStringUtils.randomAlphanumeric(8), employee);
       Assert.fail();
     } catch (final NotFoundException ex) {
       // do nothing, expected
     }
 
-    this.officeClient.deleteEmployee(employee.getIdentifier());
+    this.organizationManager.deleteEmployee(employee.getIdentifier());
     this.eventRecorder.wait(EventConstants.OPERATION_DELETE_EMPLOYEE, employee.getIdentifier());
   }
 
   @Test
   public void shouldSetContactDetailOfEmployee() throws Exception {
     final Employee employee = EmployeeFactory.createRandomEmployee();
-    this.officeClient.createEmployee(employee);
+    this.organizationManager.createEmployee(employee);
     this.eventRecorder.wait(EventConstants.OPERATION_POST_EMPLOYEE, employee.getIdentifier());
 
     final ContactDetail email = new ContactDetail();
@@ -299,12 +299,12 @@ public class TestEmployee {
     phone.setPreferenceLevel(2);
 
     {
-      this.officeClient.setContactDetails(employee.getIdentifier(), Arrays.asList(email, phone));
+      this.organizationManager.setContactDetails(employee.getIdentifier(), Arrays.asList(email, phone));
       final boolean found = this.eventRecorder.wait(EventConstants.OPERATION_PUT_CONTACT_DETAIL, employee.getIdentifier());
       Assert.assertTrue(found);
     }
 
-    final List<ContactDetail> savedContactDetails = this.officeClient.fetchContactDetails(employee.getIdentifier());
+    final List<ContactDetail> savedContactDetails = this.organizationManager.fetchContactDetails(employee.getIdentifier());
     Assert.assertNotNull(savedContactDetails);
     Assert.assertEquals(2, savedContactDetails.size());
 
@@ -321,7 +321,7 @@ public class TestEmployee {
     Assert.assertEquals(phone.getPreferenceLevel(), savedPhone.getPreferenceLevel());
 
     {
-      this.officeClient.deleteEmployee(employee.getIdentifier());
+      this.organizationManager.deleteEmployee(employee.getIdentifier());
       final boolean found = this.eventRecorder.wait(EventConstants.OPERATION_DELETE_EMPLOYEE, employee.getIdentifier());
       Assert.assertTrue(found);
     }
@@ -335,7 +335,7 @@ public class TestEmployee {
     contactDetail.setValue("employee@example.com");
 
     try {
-      this.officeClient.setContactDetails(RandomStringUtils.randomAlphanumeric(8), Collections.singletonList(contactDetail));
+      this.organizationManager.setContactDetails(RandomStringUtils.randomAlphanumeric(8), Collections.singletonList(contactDetail));
       Assert.fail();
     } catch (final NotFoundException ex) {
       // do nothing, expected
@@ -345,7 +345,7 @@ public class TestEmployee {
   @Test
   public void shouldDeleteContactDetailOfEmployee() throws Exception {
     final Employee employee = EmployeeFactory.createRandomEmployee();
-    this.officeClient.createEmployee(employee);
+    this.organizationManager.createEmployee(employee);
     this.eventRecorder.wait(EventConstants.OPERATION_POST_EMPLOYEE, employee.getIdentifier());
 
     final ContactDetail email = new ContactDetail();
@@ -361,22 +361,22 @@ public class TestEmployee {
     phone.setPreferenceLevel(2);
 
     {
-      this.officeClient.setContactDetails(employee.getIdentifier(), Arrays.asList(email, phone));
+      this.organizationManager.setContactDetails(employee.getIdentifier(), Arrays.asList(email, phone));
       final boolean found = this.eventRecorder.wait(EventConstants.OPERATION_PUT_CONTACT_DETAIL, employee.getIdentifier());
       Assert.assertTrue(found);
     }
 
-    Assert.assertNotNull(this.officeClient.fetchContactDetails(employee.getIdentifier()));
+    Assert.assertNotNull(this.organizationManager.fetchContactDetails(employee.getIdentifier()));
 
     {
-      this.officeClient.deleteContactDetails(employee.getIdentifier());
+      this.organizationManager.deleteContactDetails(employee.getIdentifier());
       final boolean found = this.eventRecorder.wait(EventConstants.OPERATION_DELETE_CONTACT_DETAIL, employee.getIdentifier());
       Assert.assertTrue(found);
     }
 
-    Assert.assertTrue(this.officeClient.fetchContactDetails(employee.getIdentifier()).isEmpty());
+    Assert.assertTrue(this.organizationManager.fetchContactDetails(employee.getIdentifier()).isEmpty());
 
-    this.officeClient.deleteEmployee(employee.getIdentifier());
+    this.organizationManager.deleteEmployee(employee.getIdentifier());
     this.eventRecorder.wait(EventConstants.OPERATION_DELETE_EMPLOYEE, employee.getIdentifier());
   }
 
diff --git a/component-test/src/main/java/io/mifos/office/TestOffice.java b/component-test/src/main/java/io/mifos/office/TestOffice.java
index 9a1bd26..c28d48d 100644
--- a/component-test/src/main/java/io/mifos/office/TestOffice.java
+++ b/component-test/src/main/java/io/mifos/office/TestOffice.java
@@ -26,7 +26,7 @@ import io.mifos.core.test.listener.EventRecorder;
 import io.mifos.office.api.v1.EventConstants;
 import io.mifos.office.api.v1.client.AlreadyExistsException;
 import io.mifos.office.api.v1.client.BadRequestException;
-import io.mifos.office.api.v1.client.OfficeClient;
+import io.mifos.office.api.v1.client.OrganizationManager;
 import io.mifos.office.api.v1.client.NotFoundException;
 import io.mifos.office.api.v1.domain.Address;
 import io.mifos.office.api.v1.domain.Office;
@@ -74,7 +74,7 @@ public class TestOffice {
           = new TenantApplicationSecurityEnvironmentTestRule(testEnvironment, this::waitForInitialize);
 
   @Autowired
-  private OfficeClient officeClient;
+  private OrganizationManager organizationManager;
 
   @Autowired
   private EventRecorder eventRecorder;
@@ -102,92 +102,92 @@ public class TestOffice {
   @Test
   public void shouldCreateOffice() throws Exception {
     final Office office = OfficeFactory.createRandomOffice();
-    this.officeClient.createOffice(office);
+    this.organizationManager.createOffice(office);
     this.eventRecorder.wait(EventConstants.OPERATION_POST_OFFICE, office.getIdentifier());
 
-    final Office savedOffice = this.officeClient.findOfficeByIdentifier(office.getIdentifier());
+    final Office savedOffice = this.organizationManager.findOfficeByIdentifier(office.getIdentifier());
     Assert.assertNotNull(savedOffice);
     Assert.assertEquals(office.getIdentifier(), savedOffice.getIdentifier());
     Assert.assertEquals(office.getName(), savedOffice.getName());
     Assert.assertEquals(office.getDescription(), savedOffice.getDescription());
 
-    this.officeClient.deleteOffice(office.getIdentifier());
+    this.organizationManager.deleteOffice(office.getIdentifier());
     this.eventRecorder.wait(EventConstants.OPERATION_DELETE_OFFICE, office.getIdentifier());
   }
 
   @Test
   public void shouldNotCreateOfficeDuplicate() throws Exception {
     final Office office = OfficeFactory.createRandomOffice();
-    this.officeClient.createOffice(office);
+    this.organizationManager.createOffice(office);
     this.eventRecorder.wait(EventConstants.OPERATION_POST_OFFICE, office.getIdentifier());
     try {
-      this.officeClient.createOffice(office);
+      this.organizationManager.createOffice(office);
       Assert.fail();
     } catch (final AlreadyExistsException ex) {
       // do nothing, expected
     }
 
-    this.officeClient.deleteOffice(office.getIdentifier());
+    this.organizationManager.deleteOffice(office.getIdentifier());
     this.eventRecorder.wait(EventConstants.OPERATION_DELETE_OFFICE, office.getIdentifier());
   }
 
   @Test
   public void shouldUpdateOffice() throws Exception {
     final Office office = OfficeFactory.createRandomOffice();
-    this.officeClient.createOffice(office);
+    this.organizationManager.createOffice(office);
     this.eventRecorder.wait(EventConstants.OPERATION_POST_OFFICE, office.getIdentifier());
 
     final String modifiedOfficeName = RandomStringUtils.randomAlphanumeric(32);
     office.setName(modifiedOfficeName);
 
-    this.officeClient.updateOffice(office.getIdentifier(), office);
+    this.organizationManager.updateOffice(office.getIdentifier(), office);
     this.eventRecorder.wait(EventConstants.OPERATION_PUT_OFFICE, office.getIdentifier());
 
-    final Office changedOffice = this.officeClient.findOfficeByIdentifier(office.getIdentifier());
+    final Office changedOffice = this.organizationManager.findOfficeByIdentifier(office.getIdentifier());
     Assert.assertEquals(modifiedOfficeName, changedOffice.getName());
 
-    this.officeClient.deleteOffice(office.getIdentifier());
+    this.organizationManager.deleteOffice(office.getIdentifier());
     this.eventRecorder.wait(EventConstants.OPERATION_DELETE_OFFICE, office.getIdentifier());
   }
 
   @Test
   public void shouldNotUpdateOfficeIdentifierMismatch() throws Exception {
     final Office office = OfficeFactory.createRandomOffice();
-    this.officeClient.createOffice(office);
+    this.organizationManager.createOffice(office);
     this.eventRecorder.wait(EventConstants.OPERATION_POST_OFFICE, office.getIdentifier());
 
     final String originalIdentifier = office.getIdentifier();
     office.setIdentifier(RandomStringUtils.randomAlphanumeric(32));
 
     try {
-      this.officeClient.updateOffice(originalIdentifier, office);
+      this.organizationManager.updateOffice(originalIdentifier, office);
       Assert.fail();
     } catch (final BadRequestException ex) {
       // do nothing, expected
     }
-    this.officeClient.deleteOffice(originalIdentifier);
+    this.organizationManager.deleteOffice(originalIdentifier);
     this.eventRecorder.wait(EventConstants.OPERATION_DELETE_OFFICE, office.getIdentifier());
   }
 
   @Test
   public void shouldAddBranch() throws Exception {
     final Office office = OfficeFactory.createRandomOffice();
-    this.officeClient.createOffice(office);
+    this.organizationManager.createOffice(office);
     this.eventRecorder.wait(EventConstants.OPERATION_POST_OFFICE, office.getIdentifier());
 
     final Office branch = OfficeFactory.createRandomOffice();
-    this.officeClient.addBranch(office.getIdentifier(), branch);
+    this.organizationManager.addBranch(office.getIdentifier(), branch);
     this.eventRecorder.wait(EventConstants.OPERATION_POST_OFFICE, branch.getIdentifier());
 
-    final OfficePage officePage = this.officeClient.getBranches(office.getIdentifier(), 0, 10, null, null);
+    final OfficePage officePage = this.organizationManager.getBranches(office.getIdentifier(), 0, 10, null, null);
     Assert.assertEquals(Long.valueOf(1L), officePage.getTotalElements());
 
     final Office savedBranch = officePage.getOffices().get(0);
     Assert.assertEquals(branch.getIdentifier(), savedBranch.getIdentifier());
 
-    this.officeClient.deleteOffice(branch.getIdentifier());
+    this.organizationManager.deleteOffice(branch.getIdentifier());
     this.eventRecorder.wait(EventConstants.OPERATION_DELETE_OFFICE, branch.getIdentifier());
-    this.officeClient.deleteOffice(office.getIdentifier());
+    this.organizationManager.deleteOffice(office.getIdentifier());
     this.eventRecorder.wait(EventConstants.OPERATION_DELETE_OFFICE, office.getIdentifier());
   }
 
@@ -195,7 +195,7 @@ public class TestOffice {
   public void shouldNotAddBranchParentNotFound() throws Exception {
     try {
       final Office branch = OfficeFactory.createRandomOffice();
-      this.officeClient.addBranch(RandomStringUtils.randomAlphanumeric(32), branch);
+      this.organizationManager.addBranch(RandomStringUtils.randomAlphanumeric(32), branch);
       Assert.fail();
     } catch (final NotFoundException ex) {
       // do nothing, expected
@@ -205,14 +205,14 @@ public class TestOffice {
   @Test
   public void shouldSetAddressOfOffice() throws Exception {
     final Office office = OfficeFactory.createRandomOffice();
-    this.officeClient.createOffice(office);
+    this.organizationManager.createOffice(office);
     this.eventRecorder.wait(EventConstants.OPERATION_POST_OFFICE, office.getIdentifier());
 
     final Address address = AddressFactory.createRandomAddress();
-    this.officeClient.setAddressForOffice(office.getIdentifier(), address);
+    this.organizationManager.setAddressForOffice(office.getIdentifier(), address);
     this.eventRecorder.wait(EventConstants.OPERATION_PUT_ADDRESS, office.getIdentifier());
 
-    final Address savedAddress = this.officeClient.getAddressOfOffice(office.getIdentifier());
+    final Address savedAddress = this.organizationManager.getAddressOfOffice(office.getIdentifier());
     Assert.assertNotNull(savedAddress);
     Assert.assertEquals(address.getStreet(), savedAddress.getStreet());
     Assert.assertEquals(address.getCity(), savedAddress.getCity());
@@ -221,7 +221,7 @@ public class TestOffice {
     Assert.assertEquals(address.getCountryCode(), savedAddress.getCountryCode());
     Assert.assertEquals(address.getCountry(), savedAddress.getCountry());
 
-    this.officeClient.deleteOffice(office.getIdentifier());
+    this.organizationManager.deleteOffice(office.getIdentifier());
     this.eventRecorder.wait(EventConstants.OPERATION_DELETE_OFFICE, office.getIdentifier());
   }
 
@@ -230,10 +230,10 @@ public class TestOffice {
     final Office office = OfficeFactory.createRandomOffice();
     final Address address = AddressFactory.createRandomAddress();
     office.setAddress(address);
-    this.officeClient.createOffice(office);
+    this.organizationManager.createOffice(office);
     this.eventRecorder.wait(EventConstants.OPERATION_POST_OFFICE, office.getIdentifier());
 
-    final Address savedAddress = this.officeClient.getAddressOfOffice(office.getIdentifier());
+    final Address savedAddress = this.organizationManager.getAddressOfOffice(office.getIdentifier());
     Assert.assertNotNull(savedAddress);
     Assert.assertEquals(address.getStreet(), savedAddress.getStreet());
     Assert.assertEquals(address.getCity(), savedAddress.getCity());
@@ -242,7 +242,7 @@ public class TestOffice {
     Assert.assertEquals(address.getCountryCode(), savedAddress.getCountryCode());
     Assert.assertEquals(address.getCountry(), savedAddress.getCountry());
 
-    this.officeClient.deleteOffice(office.getIdentifier());
+    this.organizationManager.deleteOffice(office.getIdentifier());
     this.eventRecorder.wait(EventConstants.OPERATION_DELETE_OFFICE, office.getIdentifier());
   }
 
@@ -251,32 +251,32 @@ public class TestOffice {
     final Office office = OfficeFactory.createRandomOffice();
     final Address address = AddressFactory.createRandomAddress();
     office.setAddress(address);
-    this.officeClient.createOffice(office);
+    this.organizationManager.createOffice(office);
     this.eventRecorder.wait(EventConstants.OPERATION_POST_OFFICE, office.getIdentifier());
 
-    final Address savedAddress = this.officeClient.getAddressOfOffice(office.getIdentifier());
+    final Address savedAddress = this.organizationManager.getAddressOfOffice(office.getIdentifier());
     Assert.assertNotNull(savedAddress);
 
-    this.officeClient.deleteAddressOfOffice(office.getIdentifier());
+    this.organizationManager.deleteAddressOfOffice(office.getIdentifier());
     this.eventRecorder.wait(EventConstants.OPERATION_DELETE_ADDRESS, office.getIdentifier());
 
-    Assert.assertNull(this.officeClient.getAddressOfOffice(office.getIdentifier()));
+    Assert.assertNull(this.organizationManager.getAddressOfOffice(office.getIdentifier()));
 
-    this.officeClient.deleteOffice(office.getIdentifier());
+    this.organizationManager.deleteOffice(office.getIdentifier());
     this.eventRecorder.wait(EventConstants.OPERATION_DELETE_OFFICE, office.getIdentifier());
   }
 
   @Test
   public void shouldReturnParentOfBranch() throws Exception {
     final Office parent = OfficeFactory.createRandomOffice();
-    this.officeClient.createOffice(parent);
+    this.organizationManager.createOffice(parent);
     this.eventRecorder.wait(EventConstants.OPERATION_POST_OFFICE, parent.getIdentifier());
 
     final Office branch = OfficeFactory.createRandomOffice();
-    this.officeClient.addBranch(parent.getIdentifier(), branch);
+    this.organizationManager.addBranch(parent.getIdentifier(), branch);
     this.eventRecorder.wait(EventConstants.OPERATION_POST_OFFICE, branch.getIdentifier());
 
-    final Office savedBranch = this.officeClient.findOfficeByIdentifier(branch.getIdentifier());
+    final Office savedBranch = this.organizationManager.findOfficeByIdentifier(branch.getIdentifier());
 
     Assert.assertEquals(parent.getIdentifier(), savedBranch.getParentIdentifier());
   }

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