fineract-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From my...@apache.org
Subject [fineract-cn-customer] 04/46: Add identification card upload
Date Mon, 22 Jan 2018 15:47:05 GMT
This is an automated email from the ASF dual-hosted git repository.

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

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

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

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

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

Mime
View raw message