Added the ability to reference customer documents from a loan.
diff --git a/api/src/main/java/io/mifos/individuallending/api/v1/client/CaseDocumentsManager.java b/api/src/main/java/io/mifos/individuallending/api/v1/client/CaseDocumentsManager.java
new file mode 100644
index 0000000..1eb9766
--- /dev/null
+++ b/api/src/main/java/io/mifos/individuallending/api/v1/client/CaseDocumentsManager.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2017 Kuelap, Inc.
+ *
+ * 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.individuallending.api.v1.client;
+
+import io.mifos.core.api.util.CustomFeignClientsConfiguration;
+import io.mifos.individuallending.api.v1.domain.caseinstance.CaseCustomerDocuments;
+import org.springframework.cloud.netflix.feign.FeignClient;
+import org.springframework.http.MediaType;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestMethod;
+
+/**
+ * @author Myrle Krantz
+ */
+@SuppressWarnings("unused")
+@FeignClient(value = "portfolio-v1", path = "/portfolio/v1", configuration = CustomFeignClientsConfiguration.class)
+public interface CaseDocumentsManager {
+  @RequestMapping(
+      value = "/individuallending/products/{productidentifier}/cases/{caseidentifier}/documents",
+      method = RequestMethod.GET,
+      produces = MediaType.ALL_VALUE,
+      consumes = MediaType.APPLICATION_JSON_VALUE)
+  CaseCustomerDocuments getCaseDocuments(
+      @PathVariable("productidentifier") final String productIdentifier,
+      @PathVariable("caseidentifier") final String caseIdentifier);
+
+
+  @RequestMapping(
+      value = "/individuallending/products/{productidentifier}/cases/{caseidentifier}/documents",
+      method = RequestMethod.PUT,
+      produces = MediaType.APPLICATION_JSON_VALUE,
+      consumes = MediaType.APPLICATION_JSON_VALUE
+  )
+  void changeCaseDocuments(
+      @PathVariable("productidentifier") final String productIdentifier,
+      @PathVariable("caseidentifier") final String caseIdentifier,
+      final CaseCustomerDocuments caseInstance);
+}
\ No newline at end of file
diff --git a/api/src/main/java/io/mifos/individuallending/api/v1/domain/caseinstance/CaseCustomerDocuments.java b/api/src/main/java/io/mifos/individuallending/api/v1/domain/caseinstance/CaseCustomerDocuments.java
new file mode 100644
index 0000000..f09bf4e
--- /dev/null
+++ b/api/src/main/java/io/mifos/individuallending/api/v1/domain/caseinstance/CaseCustomerDocuments.java
@@ -0,0 +1,113 @@
+/*
+ * Copyright 2017 Kuelap, Inc.
+ *
+ * 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.individuallending.api.v1.domain.caseinstance;
+
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * @author Myrle Krantz
+ */
+public class CaseCustomerDocuments {
+  private List<Document> documents;
+
+  public static class Document {
+    private String customerId;
+    private String documentId;
+
+    public Document() {
+    }
+
+    public Document(String customerId, String documentId) {
+      this.customerId = customerId;
+      this.documentId = documentId;
+    }
+
+    public String getCustomerId() {
+      return customerId;
+    }
+
+    public void setCustomerId(String customerId) {
+      this.customerId = customerId;
+    }
+
+    public String getDocumentId() {
+      return documentId;
+    }
+
+    public void setDocumentId(String documentId) {
+      this.documentId = documentId;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+      if (this == o) return true;
+      if (o == null || getClass() != o.getClass()) return false;
+      Document document = (Document) o;
+      return Objects.equals(customerId, document.customerId) &&
+          Objects.equals(documentId, document.documentId);
+    }
+
+    @Override
+    public int hashCode() {
+      return Objects.hash(customerId, documentId);
+    }
+
+    @Override
+    public String toString() {
+      return "Document{" +
+          "customerId='" + customerId + '\'' +
+          ", documentId='" + documentId + '\'' +
+          '}';
+    }
+  }
+
+  @SuppressWarnings("unused")
+  public CaseCustomerDocuments() {
+  }
+
+  public CaseCustomerDocuments(List<Document> documents) {
+    this.documents = documents;
+  }
+
+  public List<Document> getDocuments() {
+    return documents;
+  }
+
+  public void setDocuments(List<Document> documents) {
+    this.documents = documents;
+  }
+
+  @Override
+  public boolean equals(Object o) {
+    if (this == o) return true;
+    if (o == null || getClass() != o.getClass()) return false;
+    CaseCustomerDocuments that = (CaseCustomerDocuments) o;
+    return Objects.equals(documents, that.documents);
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hash(documents);
+  }
+
+  @Override
+  public String toString() {
+    return "CaseCustomerDocuments{" +
+        "documents=" + documents +
+        '}';
+  }
+}
diff --git a/api/src/main/java/io/mifos/individuallending/api/v1/events/IndividualLoanEventConstants.java b/api/src/main/java/io/mifos/individuallending/api/v1/events/IndividualLoanEventConstants.java
index 219a016..be28810 100644
--- a/api/src/main/java/io/mifos/individuallending/api/v1/events/IndividualLoanEventConstants.java
+++ b/api/src/main/java/io/mifos/individuallending/api/v1/events/IndividualLoanEventConstants.java
@@ -22,7 +22,9 @@
   String DESTINATION = "portfolio-v1";
   String SELECTOR_NAME = "action";
 
-  String PUT_LOSS_PROVISION_STEPS = "put-loss-provision-steps";
+  String PUT_DOCUMENT = "put-individualloan-documents";
+  String PUT_LOSS_PROVISION_STEPS = "put-individualloan-loss-provision-steps";
+
   String OPEN_INDIVIDUALLOAN_CASE = "open-individualloan-case";
   String DENY_INDIVIDUALLOAN_CASE = "deny-individualloan-case";
   String APPROVE_INDIVIDUALLOAN_CASE = "approve-individualloan-case";
@@ -36,7 +38,9 @@
   String CLOSE_INDIVIDUALLOAN_CASE = "close-individualloan-case";
   String RECOVER_INDIVIDUALLOAN_CASE = "recover-individualloan-case";
 
+  String SELECTOR_PUT_DOCUMENT = SELECTOR_NAME + " = '" + PUT_DOCUMENT + "'";
   String SELECTOR_PUT_LOSS_PROVISION_STEPS = SELECTOR_NAME + " = '" + PUT_LOSS_PROVISION_STEPS + "'";
+
   String SELECTOR_OPEN_INDIVIDUALLOAN_CASE = SELECTOR_NAME + " = '" + OPEN_INDIVIDUALLOAN_CASE + "'";
   String SELECTOR_DENY_INDIVIDUALLOAN_CASE = SELECTOR_NAME + " = '" + DENY_INDIVIDUALLOAN_CASE + "'";
   String SELECTOR_APPROVE_INDIVIDUALLOAN_CASE = SELECTOR_NAME + " = '" + APPROVE_INDIVIDUALLOAN_CASE + "'";
diff --git a/api/src/main/java/io/mifos/portfolio/api/v1/PermittableGroupIds.java b/api/src/main/java/io/mifos/portfolio/api/v1/PermittableGroupIds.java
index 75f8a7f..e65cf20 100644
--- a/api/src/main/java/io/mifos/portfolio/api/v1/PermittableGroupIds.java
+++ b/api/src/main/java/io/mifos/portfolio/api/v1/PermittableGroupIds.java
@@ -24,4 +24,5 @@
   String PRODUCT_LOSS_PROVISIONING_MANAGEMENT = "portfolio__v1__products__lossprv";
   String PRODUCT_MANAGEMENT = "portfolio__v1__products";
   String CASE_MANAGEMENT = "portfolio__v1__case";
+  String CASE_DOCUMENT_MANAGEMENT = "portfolio__v1__case_documents";
 }
diff --git a/component-test/src/main/java/io/mifos/portfolio/AbstractPortfolioTest.java b/component-test/src/main/java/io/mifos/portfolio/AbstractPortfolioTest.java
index 0c6b96c..5337030 100644
--- a/component-test/src/main/java/io/mifos/portfolio/AbstractPortfolioTest.java
+++ b/component-test/src/main/java/io/mifos/portfolio/AbstractPortfolioTest.java
@@ -23,6 +23,7 @@
 import io.mifos.core.test.listener.EnableEventRecording;
 import io.mifos.core.test.listener.EventRecorder;
 import io.mifos.customer.api.v1.client.CustomerManager;
+import io.mifos.individuallending.api.v1.client.CaseDocumentsManager;
 import io.mifos.individuallending.api.v1.client.IndividualLending;
 import io.mifos.individuallending.api.v1.domain.product.AccountDesignators;
 import io.mifos.individuallending.api.v1.domain.workflow.Action;
@@ -115,6 +116,10 @@
   @Autowired
   IndividualLending individualLending;
 
+  @SuppressWarnings("SpringAutowiredFieldsWarningInspection")
+  @Autowired
+  CaseDocumentsManager caseDocumentsManager;
+
   @SuppressWarnings("unused")
   @MockBean
   RhythmAdapter rhythmAdapter;
diff --git a/component-test/src/main/java/io/mifos/portfolio/TestCaseDocuments.java b/component-test/src/main/java/io/mifos/portfolio/TestCaseDocuments.java
new file mode 100644
index 0000000..ce14b75
--- /dev/null
+++ b/component-test/src/main/java/io/mifos/portfolio/TestCaseDocuments.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright 2017 Kuelap, Inc.
+ *
+ * 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.portfolio;
+
+import io.mifos.individuallending.api.v1.domain.caseinstance.CaseCustomerDocuments;
+import io.mifos.individuallending.api.v1.events.IndividualLoanEventConstants;
+import io.mifos.portfolio.api.v1.domain.Case;
+import io.mifos.portfolio.api.v1.domain.Product;
+import io.mifos.portfolio.api.v1.events.CaseEvent;
+import org.junit.Assert;
+import org.junit.Test;
+
+import java.util.Arrays;
+
+/**
+ * @author Myrle Krantz
+ */
+public class TestCaseDocuments extends AbstractPortfolioTest {
+  @Test
+  public void test() throws InterruptedException {
+    //Prepare
+    final Product product = createAndEnableProduct();
+    final Case customerCase = createCase(product.getIdentifier());
+
+
+    //Check that initial state is empty.
+    final CaseCustomerDocuments caseDocuments = caseDocumentsManager.getCaseDocuments(
+        product.getIdentifier(), customerCase.getIdentifier());
+
+    Assert.assertEquals(0, caseDocuments.getDocuments().size());
+
+
+    //Insert some documents.
+    final CaseCustomerDocuments.Document studentLoanDocument
+        = new CaseCustomerDocuments.Document(Fixture.CUSTOMER_IDENTIFIER, "student_loan_papers");
+    final CaseCustomerDocuments.Document houseTitle
+        = new CaseCustomerDocuments.Document(Fixture.CUSTOMER_IDENTIFIER, "house_title");
+    final CaseCustomerDocuments.Document workContract
+        = new CaseCustomerDocuments.Document(Fixture.CUSTOMER_IDENTIFIER, "work_contract");
+
+    caseDocuments.setDocuments(Arrays.asList(studentLoanDocument, houseTitle, workContract));
+
+    caseDocumentsManager.changeCaseDocuments(product.getIdentifier(), customerCase.getIdentifier(), caseDocuments);
+    Assert.assertTrue(this.eventRecorder.wait(IndividualLoanEventConstants.PUT_DOCUMENT,
+        new CaseEvent(product.getIdentifier(), customerCase.getIdentifier())));
+
+
+    //Check that they are as set.
+    {
+      final CaseCustomerDocuments changedCaseDocuments = caseDocumentsManager.getCaseDocuments(product.getIdentifier(), customerCase.getIdentifier());
+      Assert.assertEquals(caseDocuments, changedCaseDocuments);
+    }
+
+    //Re-order the documents
+    caseDocuments.setDocuments(Arrays.asList(houseTitle, studentLoanDocument, workContract));
+
+    caseDocumentsManager.changeCaseDocuments(product.getIdentifier(), customerCase.getIdentifier(), caseDocuments);
+    Assert.assertTrue(this.eventRecorder.wait(IndividualLoanEventConstants.PUT_DOCUMENT,
+        new CaseEvent(product.getIdentifier(), customerCase.getIdentifier())));
+
+    //Check that they are as set.
+    {
+      Thread.sleep(1000);
+      final CaseCustomerDocuments changedCaseDocuments = caseDocumentsManager.getCaseDocuments(product.getIdentifier(), customerCase.getIdentifier());
+      Assert.assertEquals(caseDocuments, changedCaseDocuments);
+    }
+
+    //TODO: mock customer and check that only existing and completed documents are referenced.
+  }
+}
\ No newline at end of file
diff --git a/component-test/src/main/java/io/mifos/portfolio/TestSuite.java b/component-test/src/main/java/io/mifos/portfolio/TestSuite.java
index 0be9417..a189642 100644
--- a/component-test/src/main/java/io/mifos/portfolio/TestSuite.java
+++ b/component-test/src/main/java/io/mifos/portfolio/TestSuite.java
@@ -33,7 +33,8 @@
     TestProducts.class,
     TestTaskDefinitions.class,
     TestTaskInstances.class,
-    TestLossProvisionSteps.class
+    TestLossProvisionSteps.class,
+    TestCaseDocuments.class
 })
 public class TestSuite extends SuiteTestEnvironment {
 }
diff --git a/component-test/src/main/java/io/mifos/portfolio/listener/CaseDocumentsListener.java b/component-test/src/main/java/io/mifos/portfolio/listener/CaseDocumentsListener.java
new file mode 100644
index 0000000..a994b2a
--- /dev/null
+++ b/component-test/src/main/java/io/mifos/portfolio/listener/CaseDocumentsListener.java
@@ -0,0 +1,34 @@
+package io.mifos.portfolio.listener;
+
+import io.mifos.core.lang.config.TenantHeaderFilter;
+import io.mifos.core.test.listener.EventRecorder;
+import io.mifos.individuallending.api.v1.events.IndividualLoanEventConstants;
+import io.mifos.portfolio.api.v1.events.CaseEvent;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.jms.annotation.JmsListener;
+import org.springframework.messaging.handler.annotation.Header;
+import org.springframework.stereotype.Component;
+
+/**
+ * @author Myrle Krantz
+ */
+@SuppressWarnings("unused")
+@Component
+public class CaseDocumentsListener {
+  private final EventRecorder eventRecorder;
+
+  @Autowired
+  public CaseDocumentsListener(final EventRecorder eventRecorder) {
+    this.eventRecorder = eventRecorder;
+  }
+
+  @JmsListener(
+      subscription = IndividualLoanEventConstants.DESTINATION,
+      destination = IndividualLoanEventConstants.DESTINATION,
+      selector = IndividualLoanEventConstants.SELECTOR_PUT_DOCUMENT
+  )
+  public void onCreateCase(@Header(TenantHeaderFilter.TENANT_HEADER) final String tenant,
+                           final String payload) {
+    this.eventRecorder.event(tenant, IndividualLoanEventConstants.PUT_DOCUMENT, payload, CaseEvent.class);
+  }
+}
diff --git a/service/src/main/java/io/mifos/individuallending/internal/command/ChangeCaseDocuments.java b/service/src/main/java/io/mifos/individuallending/internal/command/ChangeCaseDocuments.java
new file mode 100644
index 0000000..2d6478b
--- /dev/null
+++ b/service/src/main/java/io/mifos/individuallending/internal/command/ChangeCaseDocuments.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2017 Kuelap, Inc.
+ *
+ * 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.individuallending.internal.command;
+
+import io.mifos.individuallending.api.v1.domain.caseinstance.CaseCustomerDocuments;
+
+/**
+ * @author Myrle Krantz
+ */
+public class ChangeCaseDocuments {
+  private final String productIdentifier;
+  private final String caseIdentifier;
+  private final CaseCustomerDocuments instance;
+
+  public ChangeCaseDocuments(String productIdentifier, String caseIdentifier, CaseCustomerDocuments instance) {
+    this.productIdentifier = productIdentifier;
+    this.caseIdentifier = caseIdentifier;
+    this.instance = instance;
+  }
+
+  public String getProductIdentifier() {
+    return productIdentifier;
+  }
+
+  public String getCaseIdentifier() {
+    return caseIdentifier;
+  }
+
+  public CaseCustomerDocuments getInstance() {
+    return instance;
+  }
+
+  @Override
+  public String toString() {
+    return "ChangeCaseDocuments{" +
+        "productIdentifier='" + productIdentifier + '\'' +
+        ", caseIdentifier='" + caseIdentifier + '\'' +
+        '}';
+  }
+}
diff --git a/service/src/main/java/io/mifos/individuallending/internal/command/handler/CaseDocumentsCommandHandler.java b/service/src/main/java/io/mifos/individuallending/internal/command/handler/CaseDocumentsCommandHandler.java
new file mode 100644
index 0000000..4a9e3d9
--- /dev/null
+++ b/service/src/main/java/io/mifos/individuallending/internal/command/handler/CaseDocumentsCommandHandler.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright 2017 Kuelap, Inc.
+ *
+ * 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.individuallending.internal.command.handler;
+
+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.individuallending.api.v1.domain.caseinstance.CaseCustomerDocuments;
+import io.mifos.individuallending.api.v1.events.IndividualLoanEventConstants;
+import io.mifos.individuallending.internal.command.ChangeCaseDocuments;
+import io.mifos.individuallending.internal.mapper.CaseCustomerDocumentsMapper;
+import io.mifos.individuallending.internal.repository.CaseCustomerDocumentEntity;
+import io.mifos.individuallending.internal.repository.CaseCustomerDocumentsRepository;
+import io.mifos.individuallending.internal.repository.CaseParametersEntity;
+import io.mifos.individuallending.internal.repository.CaseParametersRepository;
+import io.mifos.portfolio.api.v1.events.CaseEvent;
+import io.mifos.portfolio.service.internal.repository.CaseRepository;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+/**
+ * @author Myrle Krantz
+ */
+@Aggregate
+public class CaseDocumentsCommandHandler {
+  final private CaseRepository caseRepository;
+  final private CaseParametersRepository caseParametersRepository;
+  final private CaseCustomerDocumentsRepository caseCustomerDocumentsRepository;
+
+  public CaseDocumentsCommandHandler(
+      final CaseRepository caseRepository,
+      final CaseParametersRepository caseParametersRepository,
+      final CaseCustomerDocumentsRepository caseCustomerDocumentsRepository) {
+    this.caseRepository = caseRepository;
+    this.caseParametersRepository = caseParametersRepository;
+    this.caseCustomerDocumentsRepository = caseCustomerDocumentsRepository;
+  }
+
+  @Transactional
+  @CommandHandler(logStart = CommandLogLevel.INFO, logFinish = CommandLogLevel.INFO)
+  @EventEmitter(selectorName = IndividualLoanEventConstants.SELECTOR_NAME, selectorValue = IndividualLoanEventConstants.PUT_DOCUMENT)
+  public CaseEvent process(final ChangeCaseDocuments command) {
+    final CaseParametersEntity caseparametersEntity =
+        caseRepository.findByProductIdentifierAndIdentifier(command.getProductIdentifier(), command.getCaseIdentifier())
+            .flatMap(x -> caseParametersRepository.findByCaseId(x.getId()))
+            .orElseThrow(() -> ServiceException.notFound("Case ''{0}.{1}'' not found", command.getProductIdentifier(), command.getCaseIdentifier()));
+
+    final Map<CaseCustomerDocuments.Document, CaseCustomerDocumentEntity> existingCaseCustomerDocuments
+        = caseCustomerDocumentsRepository.findByCaseParametersId(caseparametersEntity.getId())
+        .collect(Collectors.toMap(CaseCustomerDocumentsMapper::map, x -> x));
+
+    final List<CaseCustomerDocumentEntity> newCaseCustomerDocuments = CaseCustomerDocumentsMapper.map(
+        command.getInstance().getDocuments(),
+        caseparametersEntity,
+        existingCaseCustomerDocuments);
+
+    final Set<CaseCustomerDocumentEntity> toDelete = caseCustomerDocumentsRepository.findByCaseParametersId(caseparametersEntity.getId())
+        .filter(x -> !command.getInstance().getDocuments().contains(CaseCustomerDocumentsMapper.map(x)))
+        .collect(Collectors.toSet());
+
+    caseCustomerDocumentsRepository.delete(toDelete);
+    caseCustomerDocumentsRepository.save(newCaseCustomerDocuments);
+
+    return new CaseEvent(command.getProductIdentifier(), command.getCaseIdentifier());
+  }
+}
diff --git a/service/src/main/java/io/mifos/individuallending/internal/mapper/CaseCustomerDocumentsMapper.java b/service/src/main/java/io/mifos/individuallending/internal/mapper/CaseCustomerDocumentsMapper.java
new file mode 100644
index 0000000..c51c1ea
--- /dev/null
+++ b/service/src/main/java/io/mifos/individuallending/internal/mapper/CaseCustomerDocumentsMapper.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright 2017 Kuelap, Inc.
+ *
+ * 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.individuallending.internal.mapper;
+
+import io.mifos.individuallending.api.v1.domain.caseinstance.CaseCustomerDocuments;
+import io.mifos.individuallending.internal.repository.CaseCustomerDocumentEntity;
+import io.mifos.individuallending.internal.repository.CaseParametersEntity;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * @author Myrle Krantz
+ */
+public final class CaseCustomerDocumentsMapper {
+  private CaseCustomerDocumentsMapper() {}
+
+  public static CaseCustomerDocuments.Document map(
+      final CaseCustomerDocumentEntity caseCustomerDocumentEntity) {
+    final CaseCustomerDocuments.Document ret = new CaseCustomerDocuments.Document();
+    ret.setCustomerId(caseCustomerDocumentEntity.getCustomerIdentifier());
+    ret.setDocumentId(caseCustomerDocumentEntity.getDocumentIdentifier());
+    return ret;
+  }
+
+  private static CaseCustomerDocumentEntity map(
+      final CaseCustomerDocuments.Document caseCustomerDocument,
+      final Long caseParametersId,
+      final Integer order) {
+    final CaseCustomerDocumentEntity ret = new CaseCustomerDocumentEntity();
+    ret.setCustomerIdentifier(caseCustomerDocument.getCustomerId());
+    ret.setDocumentIdentifier(caseCustomerDocument.getDocumentId());
+    ret.setCaseParametersId(caseParametersId);
+    ret.setOrder(order);
+    return ret;
+  }
+
+
+  public static List<CaseCustomerDocumentEntity> map(
+      final List<CaseCustomerDocuments.Document> documents,
+      final CaseParametersEntity caseparametersEntity,
+      final Map<CaseCustomerDocuments.Document, CaseCustomerDocumentEntity> existingCaseCustomerDocuments) {
+    final List<CaseCustomerDocumentEntity> ret = new ArrayList<>();
+    for (int i = 0; i < documents.size(); i++) {
+      CaseCustomerDocumentEntity toAdd = map(documents.get(i), caseparametersEntity.getId(), i);
+      final CaseCustomerDocumentEntity existing = existingCaseCustomerDocuments.get(documents.get(i));
+      if (existing != null) {
+        existing.setOrder(toAdd.getOrder());
+        toAdd = existing;
+      }
+      ret.add(toAdd);
+    }
+    return ret;
+  }
+}
diff --git a/service/src/main/java/io/mifos/individuallending/internal/mapper/LossProvisionStepMapper.java b/service/src/main/java/io/mifos/individuallending/internal/mapper/LossProvisionStepMapper.java
index 1b4bbbe..0f09fd1 100644
--- a/service/src/main/java/io/mifos/individuallending/internal/mapper/LossProvisionStepMapper.java
+++ b/service/src/main/java/io/mifos/individuallending/internal/mapper/LossProvisionStepMapper.java
@@ -20,6 +20,9 @@
 
 import java.math.BigDecimal;
 
+/**
+ * @author Myrle Krantz
+ */
 public interface LossProvisionStepMapper {
   static LossProvisionStepEntity map(
       final Long productId,
diff --git a/service/src/main/java/io/mifos/individuallending/internal/repository/CaseCustomerDocumentEntity.java b/service/src/main/java/io/mifos/individuallending/internal/repository/CaseCustomerDocumentEntity.java
new file mode 100644
index 0000000..e1abfaa
--- /dev/null
+++ b/service/src/main/java/io/mifos/individuallending/internal/repository/CaseCustomerDocumentEntity.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright 2017 Kuelap, Inc.
+ *
+ * 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.individuallending.internal.repository;
+
+import javax.persistence.*;
+import java.util.Objects;
+
+/**
+ * @author Myrle Krantz
+ */
+@SuppressWarnings("unused")
+@Entity
+@Table(name = "bastet_il_c_docs")
+public class CaseCustomerDocumentEntity {
+  @Id
+  @GeneratedValue(strategy = GenerationType.IDENTITY)
+  @Column(name = "id")
+  private Long id;
+
+  @Column(name = "case_id")
+  private Long caseParametersId;
+
+  @Column(name = "customer_identifier")
+  private String customerIdentifier;
+
+  @Column(name = "document_identifier")
+  private String documentIdentifier;
+
+  @Column(name = "list_order")
+  private Integer order;
+
+  public Long getId() {
+    return id;
+  }
+
+  public void setId(Long id) {
+    this.id = id;
+  }
+
+  public Long getCaseParametersId() {
+    return caseParametersId;
+  }
+
+  public void setCaseParametersId(Long caseParametersId) {
+    this.caseParametersId = caseParametersId;
+  }
+
+  public String getCustomerIdentifier() {
+    return customerIdentifier;
+  }
+
+  public void setCustomerIdentifier(String customerIdentifier) {
+    this.customerIdentifier = customerIdentifier;
+  }
+
+  public String getDocumentIdentifier() {
+    return documentIdentifier;
+  }
+
+  public void setDocumentIdentifier(String documentIdentifier) {
+    this.documentIdentifier = documentIdentifier;
+  }
+
+  public Integer getOrder() {
+    return order;
+  }
+
+  public void setOrder(Integer order) {
+    this.order = order;
+  }
+
+  @Override
+  public boolean equals(Object o) {
+    if (this == o) return true;
+    if (o == null || getClass() != o.getClass()) return false;
+    CaseCustomerDocumentEntity that = (CaseCustomerDocumentEntity) o;
+    return Objects.equals(caseParametersId, that.caseParametersId) &&
+        Objects.equals(customerIdentifier, that.customerIdentifier) &&
+        Objects.equals(documentIdentifier, that.documentIdentifier) &&
+        Objects.equals(order, that.order);
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hash(caseParametersId, customerIdentifier, documentIdentifier, order);
+  }
+}
\ No newline at end of file
diff --git a/service/src/main/java/io/mifos/individuallending/internal/repository/CaseCustomerDocumentsRepository.java b/service/src/main/java/io/mifos/individuallending/internal/repository/CaseCustomerDocumentsRepository.java
new file mode 100644
index 0000000..308a1b0
--- /dev/null
+++ b/service/src/main/java/io/mifos/individuallending/internal/repository/CaseCustomerDocumentsRepository.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2017 Kuelap, Inc.
+ *
+ * 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.individuallending.internal.repository;
+
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.stereotype.Component;
+import org.springframework.stereotype.Repository;
+
+import java.util.stream.Stream;
+
+/**
+ * @author Myrle Krantz
+ */
+@Component
+@Repository
+public interface CaseCustomerDocumentsRepository extends JpaRepository<CaseCustomerDocumentEntity, Long> {
+  Stream<CaseCustomerDocumentEntity> findByCaseParametersId(Long caseId);
+}
diff --git a/service/src/main/java/io/mifos/individuallending/internal/service/CaseDocumentsService.java b/service/src/main/java/io/mifos/individuallending/internal/service/CaseDocumentsService.java
new file mode 100644
index 0000000..19ccd96
--- /dev/null
+++ b/service/src/main/java/io/mifos/individuallending/internal/service/CaseDocumentsService.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright 2017 Kuelap, Inc.
+ *
+ * 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.individuallending.internal.service;
+
+import io.mifos.core.lang.ServiceException;
+import io.mifos.individuallending.api.v1.domain.caseinstance.CaseCustomerDocuments;
+import io.mifos.individuallending.internal.mapper.CaseCustomerDocumentsMapper;
+import io.mifos.individuallending.internal.repository.CaseCustomerDocumentEntity;
+import io.mifos.individuallending.internal.repository.CaseCustomerDocumentsRepository;
+import io.mifos.individuallending.internal.repository.CaseParametersEntity;
+import io.mifos.individuallending.internal.repository.CaseParametersRepository;
+import io.mifos.portfolio.service.internal.repository.CaseRepository;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.util.Comparator;
+import java.util.stream.Stream;
+
+/**
+ * @author Myrle Krantz
+ */
+@Service
+public class CaseDocumentsService {
+  final private CaseCustomerDocumentsRepository caseCustomerDocumentsRepository;
+  final private CaseRepository caseRepository;
+  final private CaseParametersRepository caseParametersRepository;
+
+  @Autowired
+  public CaseDocumentsService(
+      final CaseCustomerDocumentsRepository caseCustomerDocumentsRepository,
+      final CaseRepository caseRepository,
+      final CaseParametersRepository caseParametersRepository) {
+    this.caseCustomerDocumentsRepository = caseCustomerDocumentsRepository;
+    this.caseRepository = caseRepository;
+    this.caseParametersRepository = caseParametersRepository;
+  }
+
+  public Stream<CaseCustomerDocuments.Document> find(
+      final String productIdentifier,
+      final String caseIdentifier) {
+    final CaseParametersEntity caseparametersEntity =
+        caseRepository.findByProductIdentifierAndIdentifier(productIdentifier, caseIdentifier)
+            .flatMap(x -> caseParametersRepository.findByCaseId(x.getId()))
+            .orElseThrow(() -> ServiceException.notFound("Case ''{0}.{1}'' not found", productIdentifier, caseIdentifier));
+
+    return caseCustomerDocumentsRepository.findByCaseParametersId(caseparametersEntity.getId())
+        .sorted(Comparator.comparing(CaseCustomerDocumentEntity::getOrder))
+        .map(CaseCustomerDocumentsMapper::map);
+  }
+}
\ No newline at end of file
diff --git a/service/src/main/java/io/mifos/individuallending/rest/CaseDocumentsRestController.java b/service/src/main/java/io/mifos/individuallending/rest/CaseDocumentsRestController.java
new file mode 100644
index 0000000..e76e291
--- /dev/null
+++ b/service/src/main/java/io/mifos/individuallending/rest/CaseDocumentsRestController.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright 2017 Kuelap, Inc.
+ *
+ * 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.individuallending.rest;
+
+import io.mifos.anubis.annotation.AcceptedTokenType;
+import io.mifos.anubis.annotation.Permittable;
+import io.mifos.core.command.gateway.CommandGateway;
+import io.mifos.core.lang.ServiceException;
+import io.mifos.individuallending.api.v1.domain.caseinstance.CaseCustomerDocuments;
+import io.mifos.individuallending.internal.command.ChangeCaseDocuments;
+import io.mifos.individuallending.internal.service.CaseDocumentsService;
+import io.mifos.portfolio.api.v1.PermittableGroupIds;
+import io.mifos.portfolio.api.v1.domain.Case;
+import io.mifos.portfolio.service.internal.service.CaseService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.MediaType;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+import java.util.stream.Collectors;
+
+@RestController
+@RequestMapping("/individuallending/products/{productidentifier}/cases/{caseidentifier}/documents")
+public class CaseDocumentsRestController {
+  private final CommandGateway commandGateway;
+  private final CaseService caseService;
+  private final CaseDocumentsService caseDocumentsService;
+
+  @Autowired
+  public CaseDocumentsRestController(
+      final CommandGateway commandGateway,
+      final CaseService caseService,
+      final CaseDocumentsService caseDocumentsService) {
+    this.commandGateway = commandGateway;
+    this.caseService = caseService;
+    this.caseDocumentsService = caseDocumentsService;
+  }
+
+  @Permittable(value = AcceptedTokenType.TENANT, groupId = PermittableGroupIds.CASE_DOCUMENT_MANAGEMENT)
+  @RequestMapping(
+      method = RequestMethod.GET,
+      consumes = MediaType.ALL_VALUE,
+      produces = MediaType.APPLICATION_JSON_VALUE)
+  public @ResponseBody
+  CaseCustomerDocuments
+  getCaseDocuments(
+      @PathVariable("productidentifier") final String productIdentifier,
+      @PathVariable("caseidentifier") final String caseIdentifier) {
+    throwIfCaseDoesntExist(productIdentifier, caseIdentifier);
+
+    final List<CaseCustomerDocuments.Document> ret = caseDocumentsService.find(productIdentifier, caseIdentifier)
+        .collect(Collectors.toList());
+
+    return new CaseCustomerDocuments(ret);
+  }
+
+
+  @Permittable(value = AcceptedTokenType.TENANT, groupId = PermittableGroupIds.CASE_DOCUMENT_MANAGEMENT)
+  @RequestMapping(
+      method = RequestMethod.PUT,
+      consumes = MediaType.APPLICATION_JSON_VALUE,
+      produces = MediaType.APPLICATION_JSON_VALUE
+  )
+  public @ResponseBody
+  ResponseEntity<Void>
+  changeCaseDocuments(
+      @PathVariable("productidentifier") final String productIdentifier,
+      @PathVariable("caseidentifier") final String caseIdentifier,
+      final @RequestBody CaseCustomerDocuments instance) {
+    throwIfCaseDoesntExist(productIdentifier, caseIdentifier);
+
+    commandGateway.process(new ChangeCaseDocuments(productIdentifier, caseIdentifier, instance));
+
+    return ResponseEntity.accepted().build();
+  }
+
+  private void throwIfCaseDoesntExist(final String productIdentifier, final String caseIdentifier) throws ServiceException {
+    //noinspection unused
+    Case x = caseService.findByIdentifier(productIdentifier, caseIdentifier)
+        .orElseThrow(() -> ServiceException.notFound("Case ''{0}.{1}'' does not exist.", productIdentifier, caseIdentifier));
+  }
+}
diff --git a/service/src/main/java/io/mifos/portfolio/service/internal/repository/CaseRepository.java b/service/src/main/java/io/mifos/portfolio/service/internal/repository/CaseRepository.java
index 2d439f6..98865e8 100644
--- a/service/src/main/java/io/mifos/portfolio/service/internal/repository/CaseRepository.java
+++ b/service/src/main/java/io/mifos/portfolio/service/internal/repository/CaseRepository.java
@@ -39,5 +39,4 @@
   boolean existsByProductIdentifier(@Param("productIdentifier") String productIdentifier);
 
   Stream<CaseEntity> findByCurrentStateIn(Collection<String> currentStates);
-
 }
diff --git a/service/src/main/resources/db/migrations/mariadb/V11__case_documents.sql b/service/src/main/resources/db/migrations/mariadb/V11__case_documents.sql
new file mode 100644
index 0000000..de79dd0
--- /dev/null
+++ b/service/src/main/resources/db/migrations/mariadb/V11__case_documents.sql
@@ -0,0 +1,27 @@
+--
+-- Copyright 2017 Kuelap, Inc.
+--
+-- 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 bastet_il_c_docs (
+  id BIGINT NOT NULL AUTO_INCREMENT,
+  case_id                  BIGINT         NOT NULL,
+  customer_identifier      VARCHAR(32)    NOT NULL,
+  document_identifier      VARCHAR(32)    NOT NULL,
+  list_order               INT            NOT NULL,
+
+  CONSTRAINT bastet_il_c_docs_pk PRIMARY KEY (id),
+  CONSTRAINT bastet_il_c_docs_uq UNIQUE (case_id, customer_identifier, document_identifier),
+  CONSTRAINT bastet_il_c_docs_fk FOREIGN KEY (case_id) REFERENCES bastet_il_cases (id)
+);
\ No newline at end of file