Implementing and testing CLOSE.
diff --git a/component-test/src/main/java/io/mifos/portfolio/TestAccountingInteractionInLoanWorkflow.java b/component-test/src/main/java/io/mifos/portfolio/TestAccountingInteractionInLoanWorkflow.java
index 33d2978..785627a 100644
--- a/component-test/src/main/java/io/mifos/portfolio/TestAccountingInteractionInLoanWorkflow.java
+++ b/component-test/src/main/java/io/mifos/portfolio/TestAccountingInteractionInLoanWorkflow.java
@@ -88,6 +88,7 @@
     step5Disburse(BigDecimal.valueOf(2000L));
     step6CalculateInterestAccrual();
     step7PaybackPartialAmount(expectedCurrentBalance);
+    step8Close();
   }
 
 
@@ -102,6 +103,7 @@
     final BigDecimal repayment1 = expectedCurrentBalance.divide(BigDecimal.valueOf(2), BigDecimal.ROUND_HALF_EVEN);
     step7PaybackPartialAmount(repayment1);
     step7PaybackPartialAmount(expectedCurrentBalance);
+    step8Close();
   }
 
   //Create product and set charges to fixed fees.
@@ -308,4 +310,20 @@
     expectedCurrentBalance = expectedCurrentBalance.subtract(amount);
     interestAccrued = BigDecimal.ZERO;
   }
+
+  private void step8Close() throws InterruptedException {
+    logger.info("step8Close");
+
+    AccountingFixture.mockBalance(customerLoanAccountIdentifier, expectedCurrentBalance);
+
+    checkStateTransfer(
+        product.getIdentifier(),
+        customerCase.getIdentifier(),
+        Action.CLOSE,
+        Collections.singletonList(assignEntryToTeller()),
+        IndividualLoanEventConstants.CLOSE_INDIVIDUALLOAN_CASE,
+        Case.State.CLOSED); //Close has to be done explicitly.
+
+    checkNextActionsCorrect(product.getIdentifier(), customerCase.getIdentifier());
+  }
 }
\ No newline at end of file
diff --git a/service/src/main/java/io/mifos/individuallending/internal/command/handler/IndividualLoanCommandHandler.java b/service/src/main/java/io/mifos/individuallending/internal/command/handler/IndividualLoanCommandHandler.java
index b796cc4..36bbe16 100644
--- a/service/src/main/java/io/mifos/individuallending/internal/command/handler/IndividualLoanCommandHandler.java
+++ b/service/src/main/java/io/mifos/individuallending/internal/command/handler/IndividualLoanCommandHandler.java
@@ -219,8 +219,9 @@
 
     checkIfTasksAreOutstanding(dataContextOfAction, Action.DISBURSE);
 
+    final BigDecimal disbursalAmount = Optional.ofNullable(command.getCommand().getPaymentSize()).orElse(BigDecimal.ZERO);
     final CostComponentsForRepaymentPeriod costComponentsForRepaymentPeriod =
-        costComponentService.getCostComponentsForDisburse(dataContextOfAction, command.getCommand().getPaymentSize());
+        costComponentService.getCostComponentsForDisburse(dataContextOfAction, disbursalAmount);
 
     final DesignatorToAccountIdentifierMapper designatorToAccountIdentifierMapper
         = new DesignatorToAccountIdentifierMapper(dataContextOfAction);
@@ -369,6 +370,27 @@
 
     checkIfTasksAreOutstanding(dataContextOfAction, Action.CLOSE);
 
+    final CostComponentsForRepaymentPeriod costComponentsForRepaymentPeriod =
+        costComponentService.getCostComponentsForClose(dataContextOfAction);
+
+    final DesignatorToAccountIdentifierMapper designatorToAccountIdentifierMapper
+        = new DesignatorToAccountIdentifierMapper(dataContextOfAction);
+
+    final List<ChargeInstance> charges =
+        costComponentsForRepaymentPeriod.stream()
+            .map(entry -> mapCostComponentEntryToChargeInstance(
+                Action.DISBURSE,
+                entry,
+                designatorToAccountIdentifierMapper))
+            .filter(Optional::isPresent)
+            .map(Optional::get)
+            .collect(Collectors.toList());
+
+    accountingAdapter.bookCharges(charges,
+        command.getCommand().getNote(),
+        dataContextOfAction.getMessageForCharge(Action.DISBURSE),
+        Action.DISBURSE.getTransactionType());
+
     final CaseEntity customerCase = dataContextOfAction.getCustomerCase();
     customerCase.setCurrentState(Case.State.CLOSED.name());
     caseRepository.save(customerCase);
diff --git a/service/src/main/java/io/mifos/individuallending/internal/service/CostComponentService.java b/service/src/main/java/io/mifos/individuallending/internal/service/CostComponentService.java
index 67bc6ce..51898aa 100644
--- a/service/src/main/java/io/mifos/individuallending/internal/service/CostComponentService.java
+++ b/service/src/main/java/io/mifos/individuallending/internal/service/CostComponentService.java
@@ -190,7 +190,7 @@
 
     if (dataContextOfAction.getCaseParameters().getMaximumBalance().compareTo(
         currentBalance.add(requestedDisbursalSize)) > 0)
-      throw ServiceException.badRequest("Cannot disburse over the maximum balance.");
+      throw ServiceException.conflict("Cannot disburse over the maximum balance.");
 
     final Optional<LocalDateTime> optionalStartOfTerm = accountingAdapter.getDateOfOldestEntryContainingMessage(
         customerLoanAccountIdentifier,
@@ -379,9 +379,46 @@
   private CostComponentsForRepaymentPeriod getCostComponentsForWriteOff(final DataContextOfAction dataContextOfAction) {
     return null;
   }
-  private CostComponentsForRepaymentPeriod getCostComponentsForClose(final DataContextOfAction dataContextOfAction) {
-    return null;
+
+  public CostComponentsForRepaymentPeriod getCostComponentsForClose(final DataContextOfAction dataContextOfAction) {
+    final DesignatorToAccountIdentifierMapper designatorToAccountIdentifierMapper
+        = new DesignatorToAccountIdentifierMapper(dataContextOfAction);
+    final String customerLoanAccountIdentifier = designatorToAccountIdentifierMapper.mapOrThrow(AccountDesignators.CUSTOMER_LOAN);
+    final BigDecimal currentBalance = accountingAdapter.getCurrentBalance(customerLoanAccountIdentifier);
+    if (currentBalance.compareTo(BigDecimal.ZERO) != 0)
+      throw ServiceException.conflict("Cannot close loan until the balance is zero.");
+
+    final LocalDate startOfTerm = getStartOfTermOrThrow(dataContextOfAction, customerLoanAccountIdentifier);
+
+    final CaseParameters caseParameters = dataContextOfAction.getCaseParameters();
+    final String productIdentifier = dataContextOfAction.getProduct().getIdentifier();
+    final int minorCurrencyUnitDigits = dataContextOfAction.getProduct().getMinorCurrencyUnitDigits();
+    final LocalDate today = today();
+    final ScheduledAction closeAction = new ScheduledAction(Action.CLOSE, today, new Period(1, today));
+
+    final List<ScheduledCharge> scheduledCharges = individualLoanService.getScheduledCharges(
+        productIdentifier,
+        Collections.singletonList(closeAction));
+
+    final Map<Boolean, List<ScheduledCharge>> chargesSplitIntoScheduledAndAccrued = scheduledCharges.stream()
+        .collect(Collectors.partitioningBy(x -> isAccruedChargeForAction(x.getChargeDefinition(), Action.CLOSE)));
+
+    final Map<ChargeDefinition, CostComponent> accruedCostComponents = chargesSplitIntoScheduledAndAccrued.get(true)
+        .stream()
+        .map(ScheduledCharge::getChargeDefinition)
+        .collect(Collectors.toMap(chargeDefinition -> chargeDefinition,
+            chargeDefinition -> getAccruedCostComponentToApply(dataContextOfAction, designatorToAccountIdentifierMapper, startOfTerm, chargeDefinition)));
+
+    return getCostComponentsForScheduledCharges(
+        accruedCostComponents,
+        chargesSplitIntoScheduledAndAccrued.get(false),
+        caseParameters.getMaximumBalance(),
+        currentBalance,
+        BigDecimal.ZERO,
+        minorCurrencyUnitDigits,
+        true);
   }
+
   private CostComponentsForRepaymentPeriod getCostComponentsForRecover(final DataContextOfAction dataContextOfAction) {
     return null;
   }