Merge pull request #41 from myrle-krantz/develop

multiple disbursals, multiple repayments, and close, plus straightening up in planned payments.
diff --git a/api/src/main/java/io/mifos/individuallending/api/v1/domain/product/ChargeIdentifiers.java b/api/src/main/java/io/mifos/individuallending/api/v1/domain/product/ChargeIdentifiers.java
index c0e0c56..9518a1b 100644
--- a/api/src/main/java/io/mifos/individuallending/api/v1/domain/product/ChargeIdentifiers.java
+++ b/api/src/main/java/io/mifos/individuallending/api/v1/domain/product/ChargeIdentifiers.java
@@ -46,9 +46,6 @@
   String REPAYMENT_ID = "repayment";
   String TRACK_RETURN_PRINCIPAL_NAME = "Track return principal";
   String TRACK_RETURN_PRINCIPAL_ID = "track-return-principal";
-  String MAXIMUM_BALANCE_DESIGNATOR = "{maximumbalance}";
-  String RUNNING_BALANCE_DESIGNATOR = "{runningbalance}";
-  String PRINCIPAL_ADJUSTMENT_DESIGNATOR = "{principaladjustment}";
 
   static String nameToIdentifier(String name) {
     return name.toLowerCase(Locale.US).replace(" ", "-");
diff --git a/api/src/main/java/io/mifos/individuallending/api/v1/domain/product/ChargeProportionalDesignator.java b/api/src/main/java/io/mifos/individuallending/api/v1/domain/product/ChargeProportionalDesignator.java
new file mode 100644
index 0000000..6f60a57
--- /dev/null
+++ b/api/src/main/java/io/mifos/individuallending/api/v1/domain/product/ChargeProportionalDesignator.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2017 The Mifos Initiative.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.mifos.individuallending.api.v1.domain.product;
+
+import java.util.Arrays;
+import java.util.Optional;
+
+/**
+ * @author Myrle Krantz
+ */
+public enum ChargeProportionalDesignator {
+  NOT_PROPORTIONAL("{notproportional}", 0),
+  MAXIMUM_BALANCE_DESIGNATOR("{maximumbalance}", 1),
+  RUNNING_BALANCE_DESIGNATOR("{runningbalance}", 2),
+  PRINCIPAL_ADJUSTMENT_DESIGNATOR("{principaladjustment}", 3),
+  REPAYMENT_DESIGNATOR("{repayment}", 4),
+  ;
+
+  private final String value;
+  private final int orderOfApplication;
+
+  ChargeProportionalDesignator(final String value, final int orderOfApplication) {
+    this.value = value;
+    this.orderOfApplication = orderOfApplication;
+  }
+
+  public String getValue() {
+    return value;
+  }
+
+  public int getOrderOfApplication() {
+    return orderOfApplication;
+  }
+
+  public static Optional<ChargeProportionalDesignator> fromString(final String value) {
+    if (value == null)
+      return Optional.of(NOT_PROPORTIONAL);
+    return Arrays.stream(ChargeProportionalDesignator.values())
+        .filter(x -> x.getValue().equals(value))
+        .findFirst();
+  }
+}
\ No newline at end of file
diff --git a/api/src/main/java/io/mifos/portfolio/api/v1/validation/CheckChargeReference.java b/api/src/main/java/io/mifos/portfolio/api/v1/validation/CheckChargeReference.java
index ab16e8b..8363d88 100644
--- a/api/src/main/java/io/mifos/portfolio/api/v1/validation/CheckChargeReference.java
+++ b/api/src/main/java/io/mifos/portfolio/api/v1/validation/CheckChargeReference.java
@@ -16,7 +16,7 @@
 package io.mifos.portfolio.api.v1.validation;
 
 import io.mifos.core.lang.validation.CheckIdentifier;
-import io.mifos.individuallending.api.v1.domain.product.ChargeIdentifiers;
+import io.mifos.individuallending.api.v1.domain.product.ChargeProportionalDesignator;
 
 import javax.validation.ConstraintValidator;
 import javax.validation.ConstraintValidatorContext;
@@ -36,8 +36,7 @@
     if (value == null)
       return true;
 
-    if (value.equals(ChargeIdentifiers.MAXIMUM_BALANCE_DESIGNATOR) ||
-        value.equals(ChargeIdentifiers.RUNNING_BALANCE_DESIGNATOR))
+    if (ChargeProportionalDesignator.fromString(value).isPresent())
       return true;
 
     final CheckIdentifier identifierChecker = new CheckIdentifier();
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 472fc5d..785627a 100644
--- a/component-test/src/main/java/io/mifos/portfolio/TestAccountingInteractionInLoanWorkflow.java
+++ b/component-test/src/main/java/io/mifos/portfolio/TestAccountingInteractionInLoanWorkflow.java
@@ -85,9 +85,25 @@
     step2CreateCase();
     step3OpenCase();
     step4ApproveCase();
-    step5DisburseFullAmount();
+    step5Disburse(BigDecimal.valueOf(2000L));
     step6CalculateInterestAccrual();
-    step7PaybackFullAmount();
+    step7PaybackPartialAmount(expectedCurrentBalance);
+    step8Close();
+  }
+
+
+  @Test
+  public void workflowWithTwoNearlyEqualRepayments() throws InterruptedException {
+    step1CreateProduct();
+    step2CreateCase();
+    step3OpenCase();
+    step4ApproveCase();
+    step5Disburse(BigDecimal.valueOf(2000L));
+    step6CalculateInterestAccrual();
+    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.
@@ -193,13 +209,14 @@
   }
 
   //Approve the case, accept a loan origination fee, and prepare to disburse the loan by earmarking the funds.
-  private void step5DisburseFullAmount() throws InterruptedException {
-    logger.info("step5DisburseFullAmount");
+  private void step5Disburse(final BigDecimal amount) throws InterruptedException {
+    logger.info("step5Disburse");
     checkStateTransfer(
         product.getIdentifier(),
         customerCase.getIdentifier(),
         Action.DISBURSE,
         Collections.singletonList(assignEntryToTeller()),
+        amount,
         IndividualLoanEventConstants.DISBURSE_INDIVIDUALLOAN_CASE,
         Case.State.ACTIVE);
     checkNextActionsCorrect(product.getIdentifier(), customerCase.getIdentifier(), Action.APPLY_INTEREST,
@@ -246,20 +263,20 @@
 
     final Set<Debtor> debtors = new HashSet<>();
     debtors.add(new Debtor(
-        AccountingFixture.LOAN_INTEREST_ACCRUAL_ACCOUNT_IDENTIFIER,
+        customerLoanAccountIdentifier,
         calculatedInterest.toPlainString()));
 
     final Set<Creditor> creditors = new HashSet<>();
     creditors.add(new Creditor(
-        customerLoanAccountIdentifier,
+        AccountingFixture.LOAN_INTEREST_ACCRUAL_ACCOUNT_IDENTIFIER,
         calculatedInterest.toPlainString()));
     AccountingFixture.verifyTransfer(ledgerManager, debtors, creditors);
 
     expectedCurrentBalance = expectedCurrentBalance.add(calculatedInterest);
   }
 
-  private void step7PaybackFullAmount() throws InterruptedException {
-    logger.info("step7PaybackFullAmount");
+  private void step7PaybackPartialAmount(final BigDecimal amount) throws InterruptedException {
+    logger.info("step7PaybackPartialAmount");
 
     AccountingFixture.mockBalance(customerLoanAccountIdentifier, expectedCurrentBalance);
 
@@ -268,24 +285,45 @@
         customerCase.getIdentifier(),
         Action.ACCEPT_PAYMENT,
         Collections.singletonList(assignEntryToTeller()),
-        expectedCurrentBalance,
+        amount,
         IndividualLoanEventConstants.ACCEPT_PAYMENT_INDIVIDUALLOAN_CASE,
         Case.State.ACTIVE); //Close has to be done explicitly.
     checkNextActionsCorrect(product.getIdentifier(), customerCase.getIdentifier(), Action.APPLY_INTEREST,
         Action.APPLY_INTEREST, Action.MARK_LATE, Action.ACCEPT_PAYMENT, Action.DISBURSE, Action.WRITE_OFF, Action.CLOSE);
 
+    final BigDecimal principal = amount.subtract(interestAccrued);
+
     final Set<Debtor> debtors = new HashSet<>();
-    debtors.add(new Debtor(AccountingFixture.LOAN_INTEREST_ACCRUAL_ACCOUNT_IDENTIFIER, interestAccrued.toPlainString()));
-    debtors.add(new Debtor(AccountingFixture.LOANS_PAYABLE_ACCOUNT_IDENTIFIER, expectedCurrentBalance.subtract(interestAccrued).toPlainString()));
-    debtors.add(new Debtor(customerLoanAccountIdentifier, expectedCurrentBalance.toPlainString()));
+    debtors.add(new Debtor(customerLoanAccountIdentifier, amount.toPlainString()));
+    debtors.add(new Debtor(AccountingFixture.LOAN_FUNDS_SOURCE_ACCOUNT_IDENTIFIER, principal.toPlainString()));
+    if (interestAccrued.compareTo(BigDecimal.ZERO) != 0)
+      debtors.add(new Debtor(AccountingFixture.LOAN_INTEREST_ACCRUAL_ACCOUNT_IDENTIFIER, interestAccrued.toPlainString()));
 
     final Set<Creditor> creditors = new HashSet<>();
-    creditors.add(new Creditor(AccountingFixture.CONSUMER_LOAN_INTEREST_ACCOUNT_IDENTIFIER, interestAccrued.toPlainString()));
-    creditors.add(new Creditor(AccountingFixture.LOAN_FUNDS_SOURCE_ACCOUNT_IDENTIFIER, expectedCurrentBalance.subtract(interestAccrued).toPlainString()));
-    creditors.add(new Creditor(AccountingFixture.TELLER_ONE_ACCOUNT_IDENTIFIER, expectedCurrentBalance.toPlainString()));
+    creditors.add(new Creditor(AccountingFixture.TELLER_ONE_ACCOUNT_IDENTIFIER, amount.toPlainString()));
+    creditors.add(new Creditor(AccountingFixture.LOANS_PAYABLE_ACCOUNT_IDENTIFIER, principal.toPlainString()));
+    if (interestAccrued.compareTo(BigDecimal.ZERO) != 0)
+      creditors.add(new Creditor(AccountingFixture.CONSUMER_LOAN_INTEREST_ACCOUNT_IDENTIFIER, interestAccrued.toPlainString()));
 
     AccountingFixture.verifyTransfer(ledgerManager, debtors, creditors);
 
-    expectedCurrentBalance = expectedCurrentBalance.subtract(expectedCurrentBalance);
+    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/IndividualLendingPatternFactory.java b/service/src/main/java/io/mifos/individuallending/IndividualLendingPatternFactory.java
index 6a30102..ed4c8a0 100644
--- a/service/src/main/java/io/mifos/individuallending/IndividualLendingPatternFactory.java
+++ b/service/src/main/java/io/mifos/individuallending/IndividualLendingPatternFactory.java
@@ -19,6 +19,7 @@
 import io.mifos.core.lang.ServiceException;
 import io.mifos.individuallending.api.v1.domain.caseinstance.CaseParameters;
 import io.mifos.individuallending.api.v1.domain.product.ChargeIdentifiers;
+import io.mifos.individuallending.api.v1.domain.product.ChargeProportionalDesignator;
 import io.mifos.individuallending.api.v1.domain.workflow.Action;
 import io.mifos.individuallending.internal.mapper.CaseParametersMapper;
 import io.mifos.individuallending.internal.repository.CaseCreditWorthinessFactorEntity;
@@ -134,7 +135,7 @@
     disbursePayment.setDescription(DISBURSE_PAYMENT_NAME);
     disbursePayment.setFromAccountDesignator(LOANS_PAYABLE);
     disbursePayment.setToAccountDesignator(ENTRY);
-    disbursePayment.setProportionalTo(ChargeIdentifiers.PRINCIPAL_ADJUSTMENT_DESIGNATOR);
+    disbursePayment.setProportionalTo(ChargeProportionalDesignator.PRINCIPAL_ADJUSTMENT_DESIGNATOR.getValue());
     disbursePayment.setChargeMethod(ChargeDefinition.ChargeMethod.PROPORTIONAL);
     disbursePayment.setAmount(BigDecimal.ONE);
 
@@ -145,7 +146,7 @@
     trackPrincipalDisbursePayment.setDescription(TRACK_DISBURSAL_PAYMENT_NAME);
     trackPrincipalDisbursePayment.setFromAccountDesignator(PENDING_DISBURSAL);
     trackPrincipalDisbursePayment.setToAccountDesignator(CUSTOMER_LOAN);
-    trackPrincipalDisbursePayment.setProportionalTo(ChargeIdentifiers.PRINCIPAL_ADJUSTMENT_DESIGNATOR);
+    trackPrincipalDisbursePayment.setProportionalTo(ChargeProportionalDesignator.PRINCIPAL_ADJUSTMENT_DESIGNATOR.getValue());
     trackPrincipalDisbursePayment.setChargeMethod(ChargeDefinition.ChargeMethod.PROPORTIONAL);
     trackPrincipalDisbursePayment.setAmount(BigDecimal.ONE);
 
@@ -167,18 +168,18 @@
             BigDecimal.valueOf(0.30),
             PENDING_DISBURSAL,
             ARREARS_ALLOWANCE);
-    writeOffAllowanceCharge.setProportionalTo(ChargeIdentifiers.RUNNING_BALANCE_DESIGNATOR);
+    writeOffAllowanceCharge.setProportionalTo(ChargeProportionalDesignator.RUNNING_BALANCE_DESIGNATOR.getValue());
 
     final ChargeDefinition interestCharge = charge(
         INTEREST_NAME,
         Action.ACCEPT_PAYMENT,
         BigDecimal.valueOf(0.05),
-        INTEREST_ACCRUAL,
+        CUSTOMER_LOAN,
         INTEREST_INCOME);
     interestCharge.setForCycleSizeUnit(ChronoUnit.YEARS);
     interestCharge.setAccrueAction(Action.APPLY_INTEREST.name());
-    interestCharge.setAccrualAccountDesignator(CUSTOMER_LOAN);
-    interestCharge.setProportionalTo(ChargeIdentifiers.RUNNING_BALANCE_DESIGNATOR);
+    interestCharge.setAccrualAccountDesignator(INTEREST_ACCRUAL);
+    interestCharge.setProportionalTo(ChargeProportionalDesignator.RUNNING_BALANCE_DESIGNATOR.getValue());
 
     final ChargeDefinition customerRepaymentCharge = new ChargeDefinition();
     customerRepaymentCharge.setChargeAction(Action.ACCEPT_PAYMENT.name());
@@ -187,20 +188,20 @@
     customerRepaymentCharge.setDescription(REPAYMENT_NAME);
     customerRepaymentCharge.setFromAccountDesignator(CUSTOMER_LOAN);
     customerRepaymentCharge.setToAccountDesignator(ENTRY);
-    customerRepaymentCharge.setProportionalTo(ChargeIdentifiers.PRINCIPAL_ADJUSTMENT_DESIGNATOR);
+    customerRepaymentCharge.setProportionalTo(ChargeProportionalDesignator.REPAYMENT_DESIGNATOR.getValue());
     customerRepaymentCharge.setChargeMethod(ChargeDefinition.ChargeMethod.PROPORTIONAL);
     customerRepaymentCharge.setAmount(BigDecimal.ONE);
 
-    final ChargeDefinition trackPrincipalRepaymentCharge = new ChargeDefinition();
-    trackPrincipalRepaymentCharge.setChargeAction(Action.ACCEPT_PAYMENT.name());
-    trackPrincipalRepaymentCharge.setIdentifier(TRACK_RETURN_PRINCIPAL_ID);
-    trackPrincipalRepaymentCharge.setName(TRACK_RETURN_PRINCIPAL_NAME);
-    trackPrincipalRepaymentCharge.setDescription(TRACK_RETURN_PRINCIPAL_NAME);
-    trackPrincipalRepaymentCharge.setFromAccountDesignator(LOANS_PAYABLE);
-    trackPrincipalRepaymentCharge.setToAccountDesignator(LOAN_FUNDS_SOURCE);
-    trackPrincipalRepaymentCharge.setProportionalTo(ChargeIdentifiers.PRINCIPAL_ADJUSTMENT_DESIGNATOR);
-    trackPrincipalRepaymentCharge.setChargeMethod(ChargeDefinition.ChargeMethod.PROPORTIONAL);
-    trackPrincipalRepaymentCharge.setAmount(BigDecimal.ONE);
+    final ChargeDefinition trackReturnPrincipalCharge = new ChargeDefinition();
+    trackReturnPrincipalCharge.setChargeAction(Action.ACCEPT_PAYMENT.name());
+    trackReturnPrincipalCharge.setIdentifier(TRACK_RETURN_PRINCIPAL_ID);
+    trackReturnPrincipalCharge.setName(TRACK_RETURN_PRINCIPAL_NAME);
+    trackReturnPrincipalCharge.setDescription(TRACK_RETURN_PRINCIPAL_NAME);
+    trackReturnPrincipalCharge.setFromAccountDesignator(LOAN_FUNDS_SOURCE);
+    trackReturnPrincipalCharge.setToAccountDesignator(LOANS_PAYABLE);
+    trackReturnPrincipalCharge.setProportionalTo(ChargeProportionalDesignator.PRINCIPAL_ADJUSTMENT_DESIGNATOR.getValue());
+    trackReturnPrincipalCharge.setChargeMethod(ChargeDefinition.ChargeMethod.PROPORTIONAL);
+    trackReturnPrincipalCharge.setAmount(BigDecimal.ONE);
 
     final ChargeDefinition disbursementReturnCharge = charge(
             RETURN_DISBURSEMENT_NAME,
@@ -208,7 +209,7 @@
             BigDecimal.valueOf(1.0),
             PENDING_DISBURSAL,
             LOAN_FUNDS_SOURCE);
-    interestCharge.setProportionalTo(ChargeIdentifiers.RUNNING_BALANCE_DESIGNATOR); //TODO: Balance in which account?
+    interestCharge.setProportionalTo(ChargeProportionalDesignator.RUNNING_BALANCE_DESIGNATOR.getValue());
 
     ret.add(processingFee);
     ret.add(loanOriginationFee);
@@ -220,7 +221,7 @@
     ret.add(writeOffAllowanceCharge);
     ret.add(interestCharge);
     ret.add(customerRepaymentCharge);
-    ret.add(trackPrincipalRepaymentCharge);
+    ret.add(trackReturnPrincipalCharge);
     ret.add(disbursementReturnCharge);
 
     return ret;
@@ -371,7 +372,7 @@
     ret.setChargeAction(action.name());
     ret.setAmount(defaultAmount);
     ret.setChargeMethod(ChargeDefinition.ChargeMethod.PROPORTIONAL);
-    ret.setProportionalTo(ChargeIdentifiers.MAXIMUM_BALANCE_DESIGNATOR);
+    ret.setProportionalTo(ChargeProportionalDesignator.MAXIMUM_BALANCE_DESIGNATOR.getValue());
     ret.setFromAccountDesignator(fromAccount);
     ret.setToAccountDesignator(toAccount);
 
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 14ef0f9..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
@@ -22,7 +22,6 @@
 import io.mifos.core.command.annotation.EventEmitter;
 import io.mifos.core.lang.ServiceException;
 import io.mifos.individuallending.IndividualLendingPatternFactory;
-import io.mifos.individuallending.api.v1.domain.product.AccountDesignators;
 import io.mifos.individuallending.api.v1.domain.product.ChargeIdentifiers;
 import io.mifos.individuallending.api.v1.domain.workflow.Action;
 import io.mifos.individuallending.api.v1.events.IndividualLoanCommandEvent;
@@ -52,8 +51,8 @@
 import java.util.Collections;
 import java.util.List;
 import java.util.Map;
+import java.util.Optional;
 import java.util.stream.Collectors;
-import java.util.stream.Stream;
 
 /**
  * @author Myrle Krantz
@@ -103,6 +102,8 @@
             Action.OPEN,
             entry,
             designatorToAccountIdentifierMapper))
+        .filter(Optional::isPresent)
+        .map(Optional::get)
         .collect(Collectors.toList());
 
     accountingAdapter.bookCharges(charges,
@@ -142,6 +143,8 @@
             Action.DENY,
             entry,
             designatorToAccountIdentifierMapper))
+        .filter(Optional::isPresent)
+        .map(Optional::get)
         .collect(Collectors.toList());
 
     final CaseEntity customerCase = dataContextOfAction.getCustomerCase();
@@ -187,6 +190,8 @@
             Action.APPROVE,
             entry,
             designatorToAccountIdentifierMapper))
+        .filter(Optional::isPresent)
+        .map(Optional::get)
         .collect(Collectors.toList());
 
     accountingAdapter.bookCharges(charges,
@@ -214,20 +219,21 @@
 
     checkIfTasksAreOutstanding(dataContextOfAction, Action.DISBURSE);
 
+    final BigDecimal disbursalAmount = Optional.ofNullable(command.getCommand().getPaymentSize()).orElse(BigDecimal.ZERO);
     final CostComponentsForRepaymentPeriod costComponentsForRepaymentPeriod =
-        costComponentService.getCostComponentsForDisburse(dataContextOfAction);
+        costComponentService.getCostComponentsForDisburse(dataContextOfAction, disbursalAmount);
 
     final DesignatorToAccountIdentifierMapper designatorToAccountIdentifierMapper
         = new DesignatorToAccountIdentifierMapper(dataContextOfAction);
 
-    final BigDecimal disbursalAmount = dataContextOfAction.getCaseParameters().getMaximumBalance();
-    final List<ChargeInstance> charges = Stream.concat(
-          costComponentsForRepaymentPeriod.stream()
-              .map(entry -> mapCostComponentEntryToChargeInstance(
-                  Action.DISBURSE,
-                  entry,
-                  designatorToAccountIdentifierMapper)),
-          Stream.of(getDisbursalChargeInstance(disbursalAmount, designatorToAccountIdentifierMapper)))
+    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,
@@ -275,6 +281,8 @@
             Action.APPLY_INTEREST,
             entry,
             designatorToAccountIdentifierMapper))
+        .filter(Optional::isPresent)
+        .map(Optional::get)
         .collect(Collectors.toList());
 
     accountingAdapter.bookCharges(charges,
@@ -319,6 +327,8 @@
             Action.ACCEPT_PAYMENT,
             entry,
             designatorToAccountIdentifierMapper))
+        .filter(Optional::isPresent)
+        .map(Optional::get)
         .collect(Collectors.toList());
 
 
@@ -360,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);
@@ -384,39 +415,34 @@
     return new IndividualLoanCommandEvent(command.getProductIdentifier(), command.getCaseIdentifier());
   }
 
-  private static ChargeInstance mapCostComponentEntryToChargeInstance(
+  private static Optional<ChargeInstance> mapCostComponentEntryToChargeInstance(
       final Action action,
       final Map.Entry<ChargeDefinition, CostComponent> costComponentEntry,
       final DesignatorToAccountIdentifierMapper designatorToAccountIdentifierMapper) {
     final ChargeDefinition chargeDefinition = costComponentEntry.getKey();
     final BigDecimal chargeAmount = costComponentEntry.getValue().getAmount();
 
-    if (chargeDefinition.getAccrualAccountDesignator() != null) {
+    if (CostComponentService.chargeIsAccrued(chargeDefinition)) {
       if (Action.valueOf(chargeDefinition.getAccrueAction()) == action)
-        return new ChargeInstance(
+        return Optional.of(new ChargeInstance(
             designatorToAccountIdentifierMapper.mapOrThrow(chargeDefinition.getFromAccountDesignator()),
             designatorToAccountIdentifierMapper.mapOrThrow(chargeDefinition.getAccrualAccountDesignator()),
-            chargeAmount);
-      else
-        return new ChargeInstance(
-            designatorToAccountIdentifierMapper.mapOrThrow(chargeDefinition.getToAccountDesignator()),
+            chargeAmount));
+      else if (Action.valueOf(chargeDefinition.getChargeAction()) == action)
+        return Optional.of(new ChargeInstance(
             designatorToAccountIdentifierMapper.mapOrThrow(chargeDefinition.getAccrualAccountDesignator()),
-            chargeAmount);
+            designatorToAccountIdentifierMapper.mapOrThrow(chargeDefinition.getToAccountDesignator()),
+            chargeAmount));
+      else
+        return Optional.empty();
     }
-    else
-      return new ChargeInstance(
+    else if (Action.valueOf(chargeDefinition.getChargeAction()) == action)
+      return Optional.of(new ChargeInstance(
           designatorToAccountIdentifierMapper.mapOrThrow(chargeDefinition.getFromAccountDesignator()),
           designatorToAccountIdentifierMapper.mapOrThrow(chargeDefinition.getToAccountDesignator()),
-          chargeAmount);
-  }
-
-  private static ChargeInstance getDisbursalChargeInstance(
-      final BigDecimal amount,
-      final DesignatorToAccountIdentifierMapper designatorToAccountIdentifierMapper) {
-    return new ChargeInstance(
-        designatorToAccountIdentifierMapper.mapOrThrow(AccountDesignators.PENDING_DISBURSAL),
-        designatorToAccountIdentifierMapper.mapOrThrow(AccountDesignators.CUSTOMER_LOAN),
-        amount);
+          chargeAmount));
+    else
+      return Optional.empty();
   }
 
   private Map<String, BigDecimal> getRequestedChargeAmounts(final @Nullable List<CostComponent> costComponents) {
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 5c871ba..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
@@ -18,7 +18,7 @@
 import io.mifos.core.lang.ServiceException;
 import io.mifos.individuallending.api.v1.domain.caseinstance.CaseParameters;
 import io.mifos.individuallending.api.v1.domain.product.AccountDesignators;
-import io.mifos.individuallending.api.v1.domain.product.ChargeIdentifiers;
+import io.mifos.individuallending.api.v1.domain.product.ChargeProportionalDesignator;
 import io.mifos.individuallending.api.v1.domain.workflow.Action;
 import io.mifos.individuallending.internal.mapper.CaseParametersMapper;
 import io.mifos.individuallending.internal.repository.CaseParametersRepository;
@@ -35,6 +35,7 @@
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 
+import javax.annotation.Nonnull;
 import javax.annotation.Nullable;
 import javax.money.MonetaryAmount;
 import java.math.BigDecimal;
@@ -42,7 +43,7 @@
 import java.time.LocalDate;
 import java.time.LocalDateTime;
 import java.util.*;
-import java.util.function.BiFunction;
+import java.util.function.Function;
 import java.util.stream.Collectors;
 
 /**
@@ -106,7 +107,7 @@
       case DENY:
         return getCostComponentsForDeny(dataContextOfAction);
       case DISBURSE:
-        return getCostComponentsForDisburse(dataContextOfAction);
+        return getCostComponentsForDisburse(dataContextOfAction, dataContextOfAction.getCaseParameters().getMaximumBalance());
       case APPLY_INTEREST:
         return getCostComponentsForApplyInterest(dataContextOfAction);
       case ACCEPT_PAYMENT:
@@ -138,7 +139,8 @@
         caseParameters.getMaximumBalance(),
         BigDecimal.ZERO,
         BigDecimal.ZERO,
-        minorCurrencyUnitDigits);
+        minorCurrencyUnitDigits,
+        true);
   }
 
   public CostComponentsForRepaymentPeriod getCostComponentsForDeny(final DataContextOfAction dataContextOfAction) {
@@ -155,7 +157,8 @@
         caseParameters.getMaximumBalance(),
         BigDecimal.ZERO,
         BigDecimal.ZERO,
-        minorCurrencyUnitDigits);
+        minorCurrencyUnitDigits,
+        true);
   }
 
   public CostComponentsForRepaymentPeriod getCostComponentsForApprove(final DataContextOfAction dataContextOfAction) {
@@ -173,15 +176,21 @@
         caseParameters.getMaximumBalance(),
         BigDecimal.ZERO,
         BigDecimal.ZERO,
-        minorCurrencyUnitDigits);
+        minorCurrencyUnitDigits,
+        true);
   }
 
-  public CostComponentsForRepaymentPeriod getCostComponentsForDisburse(final DataContextOfAction dataContextOfAction) {
+  public CostComponentsForRepaymentPeriod getCostComponentsForDisburse(
+      final @Nonnull DataContextOfAction dataContextOfAction,
+      final @Nonnull BigDecimal requestedDisbursalSize) {
     final DesignatorToAccountIdentifierMapper designatorToAccountIdentifierMapper
         = new DesignatorToAccountIdentifierMapper(dataContextOfAction);
     final String customerLoanAccountIdentifier = designatorToAccountIdentifierMapper.mapOrThrow(AccountDesignators.CUSTOMER_LOAN);
     final BigDecimal currentBalance = accountingAdapter.getCurrentBalance(customerLoanAccountIdentifier);
 
+    if (dataContextOfAction.getCaseParameters().getMaximumBalance().compareTo(
+        currentBalance.add(requestedDisbursalSize)) > 0)
+      throw ServiceException.conflict("Cannot disburse over the maximum balance.");
 
     final Optional<LocalDateTime> optionalStartOfTerm = accountingAdapter.getDateOfOldestEntryContainingMessage(
         customerLoanAccountIdentifier,
@@ -190,12 +199,15 @@
     final String productIdentifier = dataContextOfAction.getProduct().getIdentifier();
     final int minorCurrencyUnitDigits = dataContextOfAction.getProduct().getMinorCurrencyUnitDigits();
     final List<ScheduledAction> scheduledActions = Collections.singletonList(new ScheduledAction(Action.DISBURSE, today()));
+
+    final BigDecimal disbursalSize = requestedDisbursalSize.negate();
+
     final List<ScheduledCharge> scheduledCharges = individualLoanService.getScheduledCharges(
         productIdentifier, scheduledActions);
 
 
     final Map<Boolean, List<ScheduledCharge>> chargesSplitIntoScheduledAndAccrued = scheduledCharges.stream()
-        .collect(Collectors.partitioningBy(x -> isAccruedChargeForAction(x, Action.DISBURSE)));
+        .collect(Collectors.partitioningBy(x -> isAccruedChargeForAction(x.getChargeDefinition(), Action.DISBURSE)));
 
     final Map<ChargeDefinition, CostComponent> accruedCostComponents =
         optionalStartOfTerm.map(startOfTerm ->
@@ -214,8 +226,9 @@
         chargesSplitIntoScheduledAndAccrued.get(false),
         caseParameters.getMaximumBalance(),
         currentBalance,
-        caseParameters.getMaximumBalance(),//TODO: This needs to be provided by the user.
-        minorCurrencyUnitDigits);
+        disbursalSize,
+        minorCurrencyUnitDigits,
+        true);
   }
 
   public CostComponentsForRepaymentPeriod getCostComponentsForApplyInterest(
@@ -239,7 +252,7 @@
         Collections.singletonList(interestAction));
 
     final Map<Boolean, List<ScheduledCharge>> chargesSplitIntoScheduledAndAccrued = scheduledCharges.stream()
-        .collect(Collectors.partitioningBy(x -> isAccruedChargeForAction(x, Action.APPLY_INTEREST)));
+        .collect(Collectors.partitioningBy(x -> isAccruedChargeForAction(x.getChargeDefinition(), Action.APPLY_INTEREST)));
 
     final Map<ChargeDefinition, CostComponent> accruedCostComponents = chargesSplitIntoScheduledAndAccrued.get(true)
         .stream()
@@ -253,7 +266,8 @@
         caseParameters.getMaximumBalance(),
         currentBalance,
         BigDecimal.ZERO,
-        minorCurrencyUnitDigits);
+        minorCurrencyUnitDigits,
+        true);
   }
 
   public CostComponentsForRepaymentPeriod getCostComponentsForAcceptPayment(
@@ -295,7 +309,7 @@
         Collections.singletonList(scheduledAction));
 
     final Map<Boolean, List<ScheduledCharge>> chargesSplitIntoScheduledAndAccrued = scheduledChargesForThisAction.stream()
-        .collect(Collectors.partitioningBy(x -> isAccruedChargeForAction(x, Action.ACCEPT_PAYMENT)));
+        .collect(Collectors.partitioningBy(x -> isAccruedChargeForAction(x.getChargeDefinition(), Action.ACCEPT_PAYMENT)));
 
     final Map<ChargeDefinition, CostComponent> accruedCostComponents = chargesSplitIntoScheduledAndAccrued.get(true)
         .stream()
@@ -311,12 +325,18 @@
         caseParameters.getMaximumBalance(),
         currentBalance,
         loanPaymentSize,
-        minorCurrencyUnitDigits);
+        minorCurrencyUnitDigits,
+        true);
   }
 
-  private static boolean isAccruedChargeForAction(final ScheduledCharge scheduledCharge, final Action action) {
-    return scheduledCharge.getChargeDefinition().getAccrueAction() != null &&
-        scheduledCharge.getChargeDefinition().getChargeAction().equals(action.name());
+  private static boolean isAccruedChargeForAction(final ChargeDefinition chargeDefinition, final Action action) {
+    return chargeDefinition.getAccrueAction() != null &&
+        chargeDefinition.getChargeAction().equals(action.name());
+  }
+
+  private static boolean isAccrualChargeForAction(final ChargeDefinition chargeDefinition, final Action action) {
+    return chargeDefinition.getAccrueAction() != null &&
+        chargeDefinition.getAccrueAction().equals(action.name());
   }
 
   private CostComponent getAccruedCostComponentToApply(final DataContextOfAction dataContextOfAction,
@@ -359,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;
   }
@@ -371,61 +428,87 @@
       final Collection<ScheduledCharge> scheduledCharges,
       final BigDecimal maximumBalance,
       final BigDecimal runningBalance,
-      final BigDecimal loanPaymentSize,
-      final int minorCurrencyUnitDigits) {
-    BigDecimal balanceAdjustment = BigDecimal.ZERO;
-    BigDecimal currentRunningBalance = runningBalance;
+      final BigDecimal entryAccountAdjustment, //disbursement or payment size.
+      final int minorCurrencyUnitDigits,
+      final boolean accrualAccounting) {
+    final Map<String, BigDecimal> balanceAdjustments = new HashMap<>();
+    balanceAdjustments.put(AccountDesignators.CUSTOMER_LOAN, BigDecimal.ZERO);
 
     final Map<ChargeDefinition, CostComponent> costComponentMap = new HashMap<>();
 
     for (Map.Entry<ChargeDefinition, CostComponent> entry : accruedCostComponents.entrySet()) {
-      costComponentMap.put(entry.getKey(), entry.getValue());
+      final ChargeDefinition chargeDefinition = entry.getKey();
+      final BigDecimal chargeAmount = entry.getValue().getAmount();
+      costComponentMap.put(
+          chargeDefinition,
+          entry.getValue());
 
-      if (chargeDefinitionTouchesAccount(entry.getKey(), AccountDesignators.CUSTOMER_LOAN))
-        balanceAdjustment = balanceAdjustment.add(entry.getValue().getAmount());
+      //TODO: This should adjust differently depending on accrual accounting.
+      // It can't be fixed until getAmountProportionalTo is fixed.
+      adjustBalance(chargeDefinition.getFromAccountDesignator(), chargeAmount.negate(), balanceAdjustments);
+      adjustBalance(chargeDefinition.getToAccountDesignator(), chargeAmount, balanceAdjustments);
     }
 
-    final Map<Boolean, List<ScheduledCharge>> partitionedCharges = scheduledCharges.stream()
-        .collect(Collectors.partitioningBy(CostComponentService::proportionalToPrincipalAdjustment));
 
-    for (final ScheduledCharge scheduledCharge : partitionedCharges.get(false))
-    {
-      final CostComponent costComponent = costComponentMap
-          .computeIfAbsent(scheduledCharge.getChargeDefinition(), CostComponentService::constructEmptyCostComponent);
+    for (final ScheduledCharge scheduledCharge : scheduledCharges) {
+      if (accrualAccounting || !isAccrualChargeForAction(scheduledCharge.getChargeDefinition(), scheduledCharge.getScheduledAction().action)) {
+        final BigDecimal amountProportionalTo = getAmountProportionalTo(
+            scheduledCharge,
+            maximumBalance,
+            runningBalance,
+            entryAccountAdjustment,
+            balanceAdjustments);
+        //TODO: getAmountProportionalTo is programmed under the assumption of non-accrual accounting.
 
-      final BigDecimal chargeAmount = howToApplyScheduledChargeToBalance(scheduledCharge)
-          .apply(maximumBalance, currentRunningBalance)
-          .setScale(minorCurrencyUnitDigits, BigDecimal.ROUND_HALF_EVEN);
-      if (chargeDefinitionTouchesAccount(scheduledCharge.getChargeDefinition(), AccountDesignators.CUSTOMER_LOAN))
-        balanceAdjustment = balanceAdjustment.add(chargeAmount);
-      costComponent.setAmount(costComponent.getAmount().add(chargeAmount));
-      currentRunningBalance = currentRunningBalance.add(chargeAmount);
-    }
+        final CostComponent costComponent = costComponentMap
+            .computeIfAbsent(scheduledCharge.getChargeDefinition(), CostComponentService::constructEmptyCostComponent);
 
-    final BigDecimal principalAdjustment = loanPaymentSize.subtract(balanceAdjustment);
-    for (final ScheduledCharge scheduledCharge : partitionedCharges.get(true))
-    {
-      final CostComponent costComponent = costComponentMap
-          .computeIfAbsent(scheduledCharge.getChargeDefinition(), CostComponentService::constructEmptyCostComponent);
-
-      final BigDecimal chargeAmount = applyPrincipalAdjustmentCharge(scheduledCharge, principalAdjustment)
-          .setScale(minorCurrencyUnitDigits, BigDecimal.ROUND_HALF_EVEN);
-      if (chargeDefinitionTouchesAccount(scheduledCharge.getChargeDefinition(), AccountDesignators.CUSTOMER_LOAN))
-        balanceAdjustment = balanceAdjustment.add(chargeAmount);
-      costComponent.setAmount(costComponent.getAmount().add(chargeAmount));
-      currentRunningBalance = currentRunningBalance.add(chargeAmount);
+        final BigDecimal chargeAmount = howToApplyScheduledChargeToAmount(scheduledCharge)
+            .apply(amountProportionalTo)
+            .setScale(minorCurrencyUnitDigits, BigDecimal.ROUND_HALF_EVEN);
+        adjustBalances(
+            scheduledCharge.getScheduledAction().action,
+            scheduledCharge.getChargeDefinition(),
+            chargeAmount,
+            balanceAdjustments,
+            false); //TODO: once you've fixed getAmountProportionalTo, use the passed in variable.
+        costComponent.setAmount(costComponent.getAmount().add(chargeAmount));
+      }
     }
 
     return new CostComponentsForRepaymentPeriod(
-        runningBalance,
         costComponentMap,
-        balanceAdjustment.negate());
+        balanceAdjustments.getOrDefault(AccountDesignators.LOANS_PAYABLE, BigDecimal.ZERO).negate());
   }
 
-  private static BigDecimal applyPrincipalAdjustmentCharge(
+  private static BigDecimal getAmountProportionalTo(
       final ScheduledCharge scheduledCharge,
-      final BigDecimal principalAdjustment) {
-    return scheduledCharge.getChargeDefinition().getAmount().multiply(principalAdjustment);
+      final BigDecimal maximumBalance,
+      final BigDecimal runningBalance,
+      final BigDecimal loanPaymentSize,
+      final Map<String, BigDecimal> balanceAdjustments) {
+    final Optional<ChargeProportionalDesignator> optionalChargeProportionalTo = proportionalToDesignator(scheduledCharge);
+    return optionalChargeProportionalTo.map(chargeProportionalTo -> {
+      switch (chargeProportionalTo) {
+        case NOT_PROPORTIONAL:
+          return BigDecimal.ZERO;
+        case MAXIMUM_BALANCE_DESIGNATOR:
+          return maximumBalance;
+        case RUNNING_BALANCE_DESIGNATOR:
+          return runningBalance.subtract(balanceAdjustments.getOrDefault(AccountDesignators.CUSTOMER_LOAN, BigDecimal.ZERO));
+        case REPAYMENT_DESIGNATOR:
+          return loanPaymentSize;
+        case PRINCIPAL_ADJUSTMENT_DESIGNATOR: {
+          final BigDecimal newRunningBalance
+              = runningBalance.subtract(balanceAdjustments.getOrDefault(AccountDesignators.CUSTOMER_LOAN, BigDecimal.ZERO));
+          final BigDecimal newLoanPaymentSize = loanPaymentSize.min(newRunningBalance);
+          return newLoanPaymentSize.add(balanceAdjustments.getOrDefault(AccountDesignators.CUSTOMER_LOAN, BigDecimal.ZERO)).abs();
+        }
+        default:
+          return BigDecimal.ZERO;
+      }
+    }).orElse(BigDecimal.ZERO);
+//TODO: correctly implement charges which are proportional to other charges.
   }
 
   private static CostComponent constructEmptyCostComponent(final ChargeDefinition chargeDefinition) {
@@ -435,64 +518,35 @@
     return ret;
   }
 
-  private static boolean proportionalToPrincipalAdjustment(final ScheduledCharge scheduledCharge) {
+  private static Optional<ChargeProportionalDesignator> proportionalToDesignator(final ScheduledCharge scheduledCharge) {
     if (!scheduledCharge.getChargeDefinition().getChargeMethod().equals(ChargeDefinition.ChargeMethod.PROPORTIONAL))
-      return false;
-    final String proportionalTo = scheduledCharge.getChargeDefinition().getProportionalTo();
-    return proportionalTo != null && proportionalTo.equals(ChargeIdentifiers.PRINCIPAL_ADJUSTMENT_DESIGNATOR);
+      return Optional.of(ChargeProportionalDesignator.NOT_PROPORTIONAL);
+
+    return ChargeProportionalDesignator.fromString(scheduledCharge.getChargeDefinition().getProportionalTo());
   }
 
-  private static BiFunction<BigDecimal, BigDecimal, BigDecimal> howToApplyScheduledChargeToBalance(
+  private static Function<BigDecimal, BigDecimal> howToApplyScheduledChargeToAmount(
       final ScheduledCharge scheduledCharge)
   {
-
     switch (scheduledCharge.getChargeDefinition().getChargeMethod())
     {
       case FIXED:
-        return (maximumBalance, runningBalance) -> scheduledCharge.getChargeDefinition().getAmount();
-      case PROPORTIONAL: {
-        switch (scheduledCharge.getChargeDefinition().getProportionalTo()) {
-          case ChargeIdentifiers.RUNNING_BALANCE_DESIGNATOR:
-            return (maximumBalance, runningBalance) ->
-                PeriodChargeCalculator.chargeAmountPerPeriod(scheduledCharge, RUNNING_CALCULATION_PRECISION)
-                    .multiply(runningBalance);
-          case ChargeIdentifiers.MAXIMUM_BALANCE_DESIGNATOR:
-            return (maximumBalance, runningBalance) ->
-                PeriodChargeCalculator.chargeAmountPerPeriod(scheduledCharge, RUNNING_CALCULATION_PRECISION)
-                    .multiply(maximumBalance);
-          case ChargeIdentifiers.PRINCIPAL_ADJUSTMENT_DESIGNATOR: //This is handled elsewhere.
-            throw new IllegalStateException("A principal adjustment charge should not be passed to the same application function as the other charges.");
-          default:
-//TODO: correctly implement charges which are proportionate to other charges.
-            return (maximumBalance, runningBalance) ->
-                PeriodChargeCalculator.chargeAmountPerPeriod(scheduledCharge, RUNNING_CALCULATION_PRECISION)
-                    .multiply(maximumBalance);
-        }
-      }
+        return (amountProportionalTo) -> scheduledCharge.getChargeDefinition().getAmount();
+      case PROPORTIONAL:
+        return (amountProportionalTo) ->
+            PeriodChargeCalculator.chargeAmountPerPeriod(scheduledCharge, RUNNING_CALCULATION_PRECISION)
+                .multiply(amountProportionalTo);
       default:
-        return (maximumBalance, runningBalance) -> BigDecimal.ZERO;
+        return (amountProportionalTo) -> BigDecimal.ZERO;
     }
   }
 
-  private static boolean chargeDefinitionTouchesCustomerVisibleAccount(final ChargeDefinition chargeDefinition)
-  {
-    return chargeDefinitionTouchesAccount(chargeDefinition, AccountDesignators.CUSTOMER_LOAN) ||
-        chargeDefinitionTouchesAccount(chargeDefinition, AccountDesignators.ENTRY);
-  }
-
-  private static boolean chargeDefinitionTouchesAccount(final ChargeDefinition chargeDefinition, final String accountDesignator)
-  {
-    return chargeDefinition.getToAccountDesignator().equals(accountDesignator) ||
-        chargeDefinition.getFromAccountDesignator().equals(accountDesignator) ||
-        (chargeDefinition.getAccrualAccountDesignator() != null && chargeDefinition.getAccrualAccountDesignator().equals(accountDesignator));
-  }
-
   static BigDecimal getLoanPaymentSize(final BigDecimal startingBalance,
                                        final int minorCurrencyUnitDigits,
                                        final List<ScheduledCharge> scheduledCharges) {
     final int precision = startingBalance.precision() + minorCurrencyUnitDigits + EXTRA_PRECISION;
     final Map<Period, BigDecimal> accrualRatesByPeriod
-        = PeriodChargeCalculator.getPeriodAccrualRates(scheduledCharges, precision);
+        = PeriodChargeCalculator.getPeriodAccrualInterestRate(scheduledCharges, precision);
 
     final int periodCount = accrualRatesByPeriod.size();
     if (periodCount == 0)
@@ -505,7 +559,46 @@
         Money.of(startingBalance, "XXX"),
         Rate.of(geometricMeanAccrualRate),
         periodCount);
-    return BigDecimal.valueOf(presentValue.getNumber().doubleValueExact());
+    return BigDecimal.valueOf(presentValue.getNumber().doubleValueExact()).setScale(minorCurrencyUnitDigits, BigDecimal.ROUND_HALF_EVEN);
+  }
+
+  private static void adjustBalances(
+      final Action action,
+      final ChargeDefinition chargeDefinition,
+      final BigDecimal chargeAmount,
+      final Map<String, BigDecimal> balanceAdjustments,
+      boolean accrualAccounting) {
+    if (accrualAccounting) {
+      if (chargeIsAccrued(chargeDefinition)) {
+        if (Action.valueOf(chargeDefinition.getAccrueAction()) == action) {
+          adjustBalance(chargeDefinition.getFromAccountDesignator(), chargeAmount.negate(), balanceAdjustments);
+          adjustBalance(chargeDefinition.getAccrualAccountDesignator(), chargeAmount, balanceAdjustments);
+        } else if (Action.valueOf(chargeDefinition.getChargeAction()) == action) {
+          adjustBalance(chargeDefinition.getAccrualAccountDesignator(), chargeAmount.negate(), balanceAdjustments);
+          adjustBalance(chargeDefinition.getToAccountDesignator(), chargeAmount, balanceAdjustments);
+        }
+      } else if (Action.valueOf(chargeDefinition.getChargeAction()) == action) {
+        adjustBalance(chargeDefinition.getFromAccountDesignator(), chargeAmount.negate(), balanceAdjustments);
+        adjustBalance(chargeDefinition.getToAccountDesignator(), chargeAmount, balanceAdjustments);
+      }
+    }
+    else if (Action.valueOf(chargeDefinition.getChargeAction()) == action) {
+      adjustBalance(chargeDefinition.getFromAccountDesignator(), chargeAmount.negate(), balanceAdjustments);
+      adjustBalance(chargeDefinition.getToAccountDesignator(), chargeAmount, balanceAdjustments);
+    }
+  }
+
+  private static void adjustBalance(
+      final String designator,
+      final BigDecimal chargeAmount,
+      final Map<String, BigDecimal> balanceAdjustments) {
+    final BigDecimal balance = balanceAdjustments.computeIfAbsent(designator, (x) -> BigDecimal.ZERO);
+    final BigDecimal newBalance = balance.add(chargeAmount);
+    balanceAdjustments.put(designator, newBalance);
+  }
+
+  public static boolean chargeIsAccrued(final ChargeDefinition chargeDefinition) {
+    return chargeDefinition.getAccrualAccountDesignator() != null;
   }
 
   private static LocalDate today() {
diff --git a/service/src/main/java/io/mifos/individuallending/internal/service/CostComponentsForRepaymentPeriod.java b/service/src/main/java/io/mifos/individuallending/internal/service/CostComponentsForRepaymentPeriod.java
index defa570..cb7938a 100644
--- a/service/src/main/java/io/mifos/individuallending/internal/service/CostComponentsForRepaymentPeriod.java
+++ b/service/src/main/java/io/mifos/individuallending/internal/service/CostComponentsForRepaymentPeriod.java
@@ -26,23 +26,16 @@
  * @author Myrle Krantz
  */
 public class CostComponentsForRepaymentPeriod {
-  final private BigDecimal runningBalance;
   final private Map<ChargeDefinition, CostComponent> costComponents;
   final private BigDecimal balanceAdjustment;
 
   CostComponentsForRepaymentPeriod(
-      final BigDecimal runningBalance,
       final Map<ChargeDefinition, CostComponent> costComponents,
       final BigDecimal balanceAdjustment) {
-    this.runningBalance = runningBalance;
     this.costComponents = costComponents;
     this.balanceAdjustment = balanceAdjustment;
   }
 
-  public BigDecimal getRunningBalance() {
-    return runningBalance;
-  }
-
   Map<ChargeDefinition, CostComponent> getCostComponents() {
     return costComponents;
   }
diff --git a/service/src/main/java/io/mifos/individuallending/internal/service/IndividualLoanService.java b/service/src/main/java/io/mifos/individuallending/internal/service/IndividualLoanService.java
index ebdf4cf..e7423fb 100644
--- a/service/src/main/java/io/mifos/individuallending/internal/service/IndividualLoanService.java
+++ b/service/src/main/java/io/mifos/individuallending/internal/service/IndividualLoanService.java
@@ -19,6 +19,7 @@
 import io.mifos.individuallending.api.v1.domain.caseinstance.ChargeName;
 import io.mifos.individuallending.api.v1.domain.caseinstance.PlannedPayment;
 import io.mifos.individuallending.api.v1.domain.caseinstance.PlannedPaymentPage;
+import io.mifos.individuallending.api.v1.domain.product.ChargeProportionalDesignator;
 import io.mifos.portfolio.api.v1.domain.ChargeDefinition;
 import io.mifos.portfolio.api.v1.domain.Product;
 import io.mifos.portfolio.service.internal.service.ChargeDefinitionService;
@@ -124,10 +125,14 @@
       final int minorCurrencyUnitDigits,
       final List<ScheduledCharge> scheduledCharges,
       final BigDecimal loanPaymentSize) {
-    final Map<Period, Set<ScheduledCharge>> orderedScheduledChargesGroupedByPeriod
-            = scheduledCharges.stream()
-            .collect(Collectors.groupingBy(IndividualLoanService::getPeriodFromScheduledCharge,
-                    Collectors.mapping(x -> x, Collectors.toSet())));
+    final Map<Period, SortedSet<ScheduledCharge>> orderedScheduledChargesGroupedByPeriod
+        = scheduledCharges.stream()
+        .collect(Collectors.groupingBy(IndividualLoanService::getPeriodFromScheduledCharge,
+            Collectors.mapping(x -> x,
+                Collector.of(
+                    () -> new TreeSet<>(new ScheduledChargeComparator()),
+                    SortedSet::add,
+                    (left, right) -> { left.addAll(right); return left; }))));
 
     final List<Period> sortedRepaymentPeriods
         = orderedScheduledChargesGroupedByPeriod.keySet().stream()
@@ -136,27 +141,31 @@
 
     BigDecimal balance = initialBalance.setScale(minorCurrencyUnitDigits, BigDecimal.ROUND_HALF_EVEN);
     final List<PlannedPayment> plannedPayments = new ArrayList<>();
-    for (final Period repaymentPeriod : sortedRepaymentPeriods)
+    for (int i = 0; i < sortedRepaymentPeriods.size(); i++)
     {
+      final Period repaymentPeriod = sortedRepaymentPeriods.get(i);
       final BigDecimal currentLoanPaymentSize;
       if (repaymentPeriod.isDefined()) {
-        if (balance.compareTo(loanPaymentSize) < 0)
-          currentLoanPaymentSize = balance;
+        // last repayment period: Force the proposed payment to "overhang". Cost component calculation
+        // corrects last loan payment downwards but not upwards.
+        if (i == sortedRepaymentPeriods.size() - 1)
+          currentLoanPaymentSize = loanPaymentSize.add(BigDecimal.valueOf(sortedRepaymentPeriods.size()));
         else
           currentLoanPaymentSize = loanPaymentSize;
       }
       else
         currentLoanPaymentSize = BigDecimal.ZERO;
 
-      final Set<ScheduledCharge> scheduledChargesInPeriod = orderedScheduledChargesGroupedByPeriod.get(repaymentPeriod);
+      final SortedSet<ScheduledCharge> scheduledChargesInPeriod = orderedScheduledChargesGroupedByPeriod.get(repaymentPeriod);
       final CostComponentsForRepaymentPeriod costComponentsForRepaymentPeriod =
               CostComponentService.getCostComponentsForScheduledCharges(
                   Collections.emptyMap(),
                   scheduledChargesInPeriod,
-                  balance,
+                  initialBalance,
                   balance,
                   currentLoanPaymentSize,
-                  minorCurrencyUnitDigits);
+                  minorCurrencyUnitDigits,
+                  false);
 
       final PlannedPayment plannedPayment = new PlannedPayment();
       plannedPayment.setCostComponents(new ArrayList<>(costComponentsForRepaymentPeriod.getCostComponents().values()));
@@ -205,7 +214,42 @@
     if (accrueMapping == null)
       accrueMapping = Stream.empty();
 
+    return Stream.concat(
+        accrueMapping.sorted(IndividualLoanService::proportionalityApplicationOrder),
+        chargeMapping.sorted(IndividualLoanService::proportionalityApplicationOrder));
+  }
 
-    return Stream.concat(accrueMapping, chargeMapping);
+  private static class ScheduledChargeComparator implements Comparator<ScheduledCharge>
+  {
+    @Override
+    public int compare(ScheduledCharge o1, ScheduledCharge o2) {
+      int ret = o1.getScheduledAction().when.compareTo(o2.getScheduledAction().when);
+      if (ret == 0)
+        ret = o1.getScheduledAction().action.compareTo(o2.getScheduledAction().action);
+      if (ret == 0)
+        ret = proportionalityApplicationOrder(o1.getChargeDefinition(), o2.getChargeDefinition());
+      if (ret == 0)
+        return o1.getChargeDefinition().getIdentifier().compareTo(o2.getChargeDefinition().getIdentifier());
+      else
+        return ret;
+    }
+  }
+
+  private static int proportionalityApplicationOrder(final ChargeDefinition o1, final ChargeDefinition o2) {
+    final Optional<ChargeProportionalDesignator> aProportionalToDesignator
+        = ChargeProportionalDesignator.fromString(o1.getProportionalTo());
+    final Optional<ChargeProportionalDesignator> bProportionalToDesignator
+        = ChargeProportionalDesignator.fromString(o2.getProportionalTo());
+
+    if (aProportionalToDesignator.isPresent() && bProportionalToDesignator.isPresent())
+      return Integer.compare(
+          aProportionalToDesignator.get().getOrderOfApplication(),
+          bProportionalToDesignator.get().getOrderOfApplication());
+    else if (aProportionalToDesignator.isPresent())
+      return 1;
+    else if (bProportionalToDesignator.isPresent())
+      return -1;
+    else
+      return 0;
   }
 }
\ No newline at end of file
diff --git a/service/src/main/java/io/mifos/individuallending/internal/service/PeriodChargeCalculator.java b/service/src/main/java/io/mifos/individuallending/internal/service/PeriodChargeCalculator.java
index f43aa99..58cac97 100644
--- a/service/src/main/java/io/mifos/individuallending/internal/service/PeriodChargeCalculator.java
+++ b/service/src/main/java/io/mifos/individuallending/internal/service/PeriodChargeCalculator.java
@@ -15,14 +15,18 @@
  */
 package io.mifos.individuallending.internal.service;
 
+import io.mifos.individuallending.api.v1.domain.workflow.Action;
+import io.mifos.portfolio.api.v1.domain.ChargeDefinition;
 import org.springframework.stereotype.Service;
 
 import java.math.BigDecimal;
+import java.time.Duration;
 import java.time.temporal.ChronoUnit;
 import java.util.List;
 import java.util.Map;
 import java.util.Optional;
 import java.util.stream.Collectors;
+import java.util.stream.Stream;
 
 /**
  * @author Myrle Krantz
@@ -34,38 +38,59 @@
   {
   }
 
-  static Map<Period, BigDecimal> getPeriodAccrualRates(final List<ScheduledCharge> scheduledCharges, final int precision) {
+  static Map<Period, BigDecimal> getPeriodAccrualInterestRate(final List<ScheduledCharge> scheduledCharges,
+                                                              final int precision) {
     return scheduledCharges.stream()
-            .filter(PeriodChargeCalculator::accruedCharge)
+            .filter(PeriodChargeCalculator::accruedInterestCharge)
             .collect(Collectors.groupingBy(scheduledCharge -> scheduledCharge.getScheduledAction().repaymentPeriod,
                     Collectors.mapping(x -> chargeAmountPerPeriod(x, precision), RateCollectors.compound(precision))));
   }
 
-  private static boolean accruedCharge(final ScheduledCharge scheduledCharge)
+  private static boolean accruedInterestCharge(final ScheduledCharge scheduledCharge)
   {
     return scheduledCharge.getChargeDefinition().getAccrualAccountDesignator() != null &&
         scheduledCharge.getChargeDefinition().getAccrueAction() != null &&
-        scheduledCharge.getScheduledAction().repaymentPeriod != null;
+        scheduledCharge.getChargeDefinition().getAccrueAction().equals(Action.APPLY_INTEREST.name()) &&
+        scheduledCharge.getScheduledAction().action == Action.ACCEPT_PAYMENT &&
+        scheduledCharge.getScheduledAction().actionPeriod != null;
   }
 
   static BigDecimal chargeAmountPerPeriod(final ScheduledCharge scheduledCharge, final int precision)
   {
-    if (scheduledCharge.getChargeDefinition().getForCycleSizeUnit() == null)
-      return scheduledCharge.getChargeDefinition().getAmount();
+    final ChargeDefinition chargeDefinition = scheduledCharge.getChargeDefinition();
+    final ScheduledAction scheduledAction = scheduledCharge.getScheduledAction();
+    if (chargeDefinition.getForCycleSizeUnit() == null)
+      return chargeDefinition.getAmount();
 
     final BigDecimal actionPeriodDuration
-            = BigDecimal.valueOf(
-            scheduledCharge.getScheduledAction().actionPeriod
-                    .getDuration()
-                    .getSeconds());
+        = BigDecimal.valueOf(
+        scheduledAction.actionPeriod
+            .getDuration()
+            .getSeconds());
+    final Optional<BigDecimal> accrualPeriodDuration = Optional.ofNullable(chargeDefinition.getAccrueAction())
+        .flatMap(action -> ScheduledActionHelpers.getAccrualPeriodDurationForAction(Action.valueOf(action)))
+        .map(Duration::getSeconds)
+        .map(BigDecimal::valueOf);
+
     final BigDecimal chargeDefinitionCycleSizeUnitDuration
             = BigDecimal.valueOf(
-            Optional.ofNullable(scheduledCharge.getChargeDefinition().getForCycleSizeUnit())
+            Optional.ofNullable(chargeDefinition.getForCycleSizeUnit())
                     .orElse(ChronoUnit.YEARS)
                     .getDuration()
                     .getSeconds());
 
-    final BigDecimal periodsInCycle = chargeDefinitionCycleSizeUnitDuration.divide(actionPeriodDuration, precision, BigDecimal.ROUND_HALF_EVEN);
-    return scheduledCharge.getChargeDefinition().getAmount().divide(periodsInCycle, precision, BigDecimal.ROUND_HALF_EVEN);
+    final BigDecimal accrualPeriodsInCycle = chargeDefinitionCycleSizeUnitDuration.divide(
+        accrualPeriodDuration.orElse(actionPeriodDuration), precision, BigDecimal.ROUND_HALF_EVEN);
+    final int accrualPeriodsInActionPeriod = actionPeriodDuration.divide(
+        accrualPeriodDuration.orElse(actionPeriodDuration), precision, BigDecimal.ROUND_HALF_EVEN)
+        .intValueExact();
+    final BigDecimal rateForAccrualPeriod = chargeDefinition.getAmount().divide(
+        accrualPeriodsInCycle, precision, BigDecimal.ROUND_HALF_EVEN);
+    return createCompoundedRate(rateForAccrualPeriod, accrualPeriodsInActionPeriod, precision);
+  }
+
+  static BigDecimal createCompoundedRate(final BigDecimal interestRate, final int periodCount, final int precision)
+  {
+    return Stream.generate(() -> interestRate).limit(periodCount).collect(RateCollectors.compound(precision));
   }
 }
diff --git a/service/src/main/java/io/mifos/individuallending/internal/service/ScheduledActionHelpers.java b/service/src/main/java/io/mifos/individuallending/internal/service/ScheduledActionHelpers.java
index 434414b..b9c5eb6 100644
--- a/service/src/main/java/io/mifos/individuallending/internal/service/ScheduledActionHelpers.java
+++ b/service/src/main/java/io/mifos/individuallending/internal/service/ScheduledActionHelpers.java
@@ -262,4 +262,11 @@
     final int maxDay = YearMonth.of(paymentDate.getYear(), paymentDate.getMonth()).lengthOfMonth()-1;
     return paymentDate.plusDays(Math.min(maxDay, alignmentDay));
   }
+
+  public static Optional<Duration> getAccrualPeriodDurationForAction(final Action action) {
+    if (action == Action.APPLY_INTEREST)
+      return Optional.of(ChronoUnit.DAYS.getDuration());
+    else
+      return Optional.empty();
+  }
 }
\ No newline at end of file
diff --git a/service/src/main/java/io/mifos/portfolio/service/internal/mapper/ChargeDefinitionMapper.java b/service/src/main/java/io/mifos/portfolio/service/internal/mapper/ChargeDefinitionMapper.java
index adffc64..4718e1b 100644
--- a/service/src/main/java/io/mifos/portfolio/service/internal/mapper/ChargeDefinitionMapper.java
+++ b/service/src/main/java/io/mifos/portfolio/service/internal/mapper/ChargeDefinitionMapper.java
@@ -15,6 +15,7 @@
  */
 package io.mifos.portfolio.service.internal.mapper;
 
+import io.mifos.individuallending.api.v1.domain.product.ChargeProportionalDesignator;
 import io.mifos.portfolio.api.v1.domain.ChargeDefinition;
 import io.mifos.portfolio.service.internal.repository.ChargeDefinitionEntity;
 import io.mifos.portfolio.service.internal.repository.ProductEntity;
@@ -71,15 +72,17 @@
     if ((chargeMethod == ChargeDefinition.ChargeMethod.FIXED) || (from.getProportionalTo() != null))
       return from.getProportionalTo();
 
-    if (identifier.equals(LOAN_FUNDS_ALLOCATION_ID))
-      return MAXIMUM_BALANCE_DESIGNATOR;
-    else if (identifier.equals(LOAN_ORIGINATION_FEE_ID))
-      return MAXIMUM_BALANCE_DESIGNATOR;
-    else if (identifier.equals(PROCESSING_FEE_ID))
-      return MAXIMUM_BALANCE_DESIGNATOR;
-    else if (identifier.equals(LATE_FEE_ID))
-      return REPAYMENT_ID;
-    else
-      return RUNNING_BALANCE_DESIGNATOR;
+    switch (identifier) {
+      case LOAN_FUNDS_ALLOCATION_ID:
+        return ChargeProportionalDesignator.MAXIMUM_BALANCE_DESIGNATOR.getValue();
+      case LOAN_ORIGINATION_FEE_ID:
+        return ChargeProportionalDesignator.MAXIMUM_BALANCE_DESIGNATOR.getValue();
+      case PROCESSING_FEE_ID:
+        return ChargeProportionalDesignator.MAXIMUM_BALANCE_DESIGNATOR.getValue();
+      case LATE_FEE_ID:
+        return ChargeProportionalDesignator.REPAYMENT_DESIGNATOR.getValue();
+      default:
+        return ChargeProportionalDesignator.RUNNING_BALANCE_DESIGNATOR.getValue();
+    }
   }
 }
diff --git a/service/src/test/java/io/mifos/individuallending/internal/service/Fixture.java b/service/src/test/java/io/mifos/individuallending/internal/service/Fixture.java
index 2dc3d1c..c649180 100644
--- a/service/src/test/java/io/mifos/individuallending/internal/service/Fixture.java
+++ b/service/src/test/java/io/mifos/individuallending/internal/service/Fixture.java
@@ -85,19 +85,19 @@
     return new ScheduledAction(Action.ACCEPT_PAYMENT, to, repaymentPeriod, repaymentPeriod);
   }
 
-  static ScheduledCharge scheduledInterestCharge(
-          final double amount,
-          final LocalDate initialDate,
-          final int chargeDateDelta,
-          final int periodBeginDelta,
-          final int periodLength)
+  static ScheduledCharge scheduledInterestBookingCharge(
+      final double amount,
+      final LocalDate initialDate,
+      final int chargeDateDelta,
+      final int periodBeginDelta,
+      final int periodLength)
   {
     final LocalDate chargeDate = initialDate.plusDays(chargeDateDelta);
     final ScheduledAction scheduledAction = new ScheduledAction(
-            Action.APPLY_INTEREST,
-            chargeDate,
-            new Period(chargeDate, 1),
-            getPeriod(initialDate, periodBeginDelta, periodLength));
+        Action.ACCEPT_PAYMENT,
+        chargeDate,
+        new Period(chargeDate, periodLength),
+        getPeriod(initialDate, periodBeginDelta, periodLength));
     final ChargeDefinition chargeDefinition = new ChargeDefinition();
     chargeDefinition.setChargeMethod(ChargeDefinition.ChargeMethod.PROPORTIONAL);
     chargeDefinition.setForCycleSizeUnit(ChronoUnit.YEARS);
diff --git a/service/src/test/java/io/mifos/individuallending/internal/service/IndividualLoanServiceTest.java b/service/src/test/java/io/mifos/individuallending/internal/service/IndividualLoanServiceTest.java
index 209dafd..0cbf933 100644
--- a/service/src/test/java/io/mifos/individuallending/internal/service/IndividualLoanServiceTest.java
+++ b/service/src/test/java/io/mifos/individuallending/internal/service/IndividualLoanServiceTest.java
@@ -299,8 +299,13 @@
               .map(CostComponent::getAmount)
               .reduce(BigDecimal::add)
               .orElse(BigDecimal.ZERO);
+          final BigDecimal valueOfPrincipleTrackingCostComponent = allPlannedPayments.get(x).getCostComponents().stream()
+              .filter(costComponent -> costComponent.getChargeIdentifier().equals(ChargeIdentifiers.TRACK_RETURN_PRINCIPAL_ID))
+              .map(CostComponent::getAmount)
+              .reduce(BigDecimal::add)
+              .orElse(BigDecimal.ZERO);
           final BigDecimal principalDifference = allPlannedPayments.get(x-1).getRemainingPrincipal().subtract(allPlannedPayments.get(x).getRemainingPrincipal());
-          Assert.assertEquals(costComponentSum, principalDifference);
+          Assert.assertEquals(valueOfPrincipleTrackingCostComponent, principalDifference);
           Assert.assertNotEquals("Remaining principle should always be positive or zero.",
               allPlannedPayments.get(x).getRemainingPrincipal().signum(), -1);
           return costComponentSum;
@@ -325,7 +330,8 @@
     Assert.assertTrue(maxPayment.isPresent());
     Assert.assertTrue(minPayment.isPresent());
     final double percentDifference = percentDifference(maxPayment.get(), minPayment.get());
-    Assert.assertTrue("Percent difference = " + percentDifference, percentDifference < 0.01);
+    Assert.assertTrue("Percent difference = " + percentDifference + ", max = " + maxPayment.get() + ", min = " + minPayment.get(),
+        percentDifference < 0.01);
 
     //Final balance should be zero.
     Assert.assertEquals(BigDecimal.ZERO.setScale(testCase.minorCurrencyUnitDigits, BigDecimal.ROUND_HALF_EVEN),
diff --git a/service/src/test/java/io/mifos/individuallending/internal/service/PeriodChargeCalculatorTest.java b/service/src/test/java/io/mifos/individuallending/internal/service/PeriodChargeCalculatorTest.java
index 8571638..6b3c2ff 100644
--- a/service/src/test/java/io/mifos/individuallending/internal/service/PeriodChargeCalculatorTest.java
+++ b/service/src/test/java/io/mifos/individuallending/internal/service/PeriodChargeCalculatorTest.java
@@ -23,10 +23,9 @@
 import java.math.BigDecimal;
 import java.time.LocalDate;
 import java.util.*;
-import java.util.stream.Stream;
 
 import static io.mifos.individuallending.internal.service.Fixture.getPeriod;
-import static io.mifos.individuallending.internal.service.Fixture.scheduledInterestCharge;
+import static io.mifos.individuallending.internal.service.Fixture.scheduledInterestBookingCharge;
 
 /**
  * @author Myrle Krantz
@@ -80,68 +79,57 @@
   {
     final LocalDate initialDate = LocalDate.now();
     final List<ScheduledCharge> scheduledCharges = new ArrayList<>();
-    scheduledCharges.add(scheduledInterestCharge(0.01, initialDate, 0, 0, 1));
-    scheduledCharges.add(scheduledInterestCharge(0.01, initialDate, 1, 1, 1));
+    scheduledCharges.add(scheduledInterestBookingCharge(0.01, initialDate, 0, 0, 1));
+    scheduledCharges.add(scheduledInterestBookingCharge(0.01, initialDate, 1, 1, 1));
 
     final BigDecimal dailyInterestRate = BigDecimal.valueOf(0.01)
-            .divide(BigDecimal.valueOf(365.2425), 20, BigDecimal.ROUND_HALF_EVEN);
+        .divide(BigDecimal.valueOf(365.2425), 20, BigDecimal.ROUND_HALF_EVEN);
 
     final Map<Period, BigDecimal> expectedPeriodRates = new HashMap<>();
     expectedPeriodRates.put(getPeriod(initialDate, 0, 1), dailyInterestRate);
     expectedPeriodRates.put(getPeriod(initialDate, 1, 1), dailyInterestRate);
 
     return new TestCase("simpleCase")
-            .scheduledCharges(scheduledCharges)
-            .precision(20)
-            .expectedPeriodRates(expectedPeriodRates);
+        .scheduledCharges(scheduledCharges)
+        .precision(20)
+        .expectedPeriodRates(expectedPeriodRates);
   }
 
   private static TestCase bitOfCompoundingCase()
   {
     final LocalDate initialDate = LocalDate.now();
     final List<ScheduledCharge> scheduledCharges = new ArrayList<>();
-    scheduledCharges.add(scheduledInterestCharge(0.01, initialDate, 0, 0, 3));
-    scheduledCharges.add(scheduledInterestCharge(0.01, initialDate, 1, 0, 3));
-    scheduledCharges.add(scheduledInterestCharge(0.01, initialDate, 2, 0, 3));
-    scheduledCharges.add(scheduledInterestCharge(0.01, initialDate, 3, 2, 2));
-    scheduledCharges.add(scheduledInterestCharge(0.01, initialDate, 4, 2, 2));
+    scheduledCharges.add(scheduledInterestBookingCharge(0.10, initialDate, 2, 0, 3));
+    scheduledCharges.add(scheduledInterestBookingCharge(0.10, initialDate, 4, 2, 2));
 
-    final BigDecimal dailyInterestRate = BigDecimal.valueOf(0.01)
-            .divide(BigDecimal.valueOf(365.2425), 20, BigDecimal.ROUND_HALF_EVEN);
+    final BigDecimal dailyInterestRate = BigDecimal.valueOf(0.10)
+        .divide(BigDecimal.valueOf(365.2425), 20, BigDecimal.ROUND_HALF_EVEN);
 
     final Map<Period, BigDecimal> expectedPeriodRates = new HashMap<>();
-    expectedPeriodRates.put(getPeriod(initialDate, 0, 3), createCompoundedInterestRate(dailyInterestRate, 3, 20));
-    expectedPeriodRates.put(getPeriod(initialDate, 2, 2), createCompoundedInterestRate(dailyInterestRate, 2, 20));
+    expectedPeriodRates.put(getPeriod(initialDate, 0, 3), PeriodChargeCalculator.createCompoundedRate(dailyInterestRate, 3, 20));
+    expectedPeriodRates.put(getPeriod(initialDate, 2, 2), PeriodChargeCalculator.createCompoundedRate(dailyInterestRate, 2, 20));
 
     return new TestCase("bitOfCompoundingCase")
-            .scheduledCharges(scheduledCharges)
-            .precision(20)
-            .expectedPeriodRates(expectedPeriodRates);
+        .scheduledCharges(scheduledCharges)
+        .precision(20)
+        .expectedPeriodRates(expectedPeriodRates);
   }
 
   private static TestCase zeroInterestPerPeriod()
   {
     final LocalDate initialDate = LocalDate.now();
     final List<ScheduledCharge> scheduledCharges = new ArrayList<>();
-    scheduledCharges.add(scheduledInterestCharge(0.00, initialDate, 0, 0, 3));
-    scheduledCharges.add(scheduledInterestCharge(0.00, initialDate, 1, 0, 3));
-    scheduledCharges.add(scheduledInterestCharge(0.00, initialDate, 2, 0, 3));
-    scheduledCharges.add(scheduledInterestCharge(0.00, initialDate, 3, 2, 2));
-    scheduledCharges.add(scheduledInterestCharge(0.00, initialDate, 4, 2, 2));
+    scheduledCharges.add(scheduledInterestBookingCharge(0.00, initialDate, 2, 0, 3));
+    scheduledCharges.add(scheduledInterestBookingCharge(0.00, initialDate, 4, 2, 2));
 
     final Map<Period, BigDecimal> expectedPeriodRates = new HashMap<>();
     expectedPeriodRates.put(getPeriod(initialDate, 0, 3), BigDecimal.ZERO.setScale(20, BigDecimal.ROUND_UNNECESSARY));
     expectedPeriodRates.put(getPeriod(initialDate, 2, 2), BigDecimal.ZERO.setScale(20, BigDecimal.ROUND_UNNECESSARY));
 
     return new TestCase("zeroInterestPerPeriod")
-            .scheduledCharges(scheduledCharges)
-            .precision(20)
-            .expectedPeriodRates(expectedPeriodRates);
-  }
-
-  private static BigDecimal createCompoundedInterestRate(BigDecimal interestRate, int periodCount, int precision)
-  {
-    return Stream.generate(() -> interestRate).limit(periodCount).collect(RateCollectors.compound(precision));
+        .scheduledCharges(scheduledCharges)
+        .precision(20)
+        .expectedPeriodRates(expectedPeriodRates);
   }
 
   private final TestCase testCase;
@@ -151,10 +139,9 @@
   }
 
   @Test
-  public void test()
+  public void getPeriodAccrualRatesTest()
   {
-    final PeriodChargeCalculator testSubject = new PeriodChargeCalculator();
-    final Map<Period, BigDecimal> periodRates = testSubject.getPeriodAccrualRates(testCase.scheduledCharges, testCase.precision);
+    final Map<Period, BigDecimal> periodRates = PeriodChargeCalculator.getPeriodAccrualInterestRate(testCase.scheduledCharges, testCase.precision);
     Assert.assertEquals(testCase.expectedPeriodRates, periodRates);
   }
 }
diff --git a/service/src/test/java/io/mifos/individuallending/internal/service/ScheduledActionTest.java b/service/src/test/java/io/mifos/individuallending/internal/service/ScheduledActionTest.java
index ff22aea..a883ee5 100644
--- a/service/src/test/java/io/mifos/individuallending/internal/service/ScheduledActionTest.java
+++ b/service/src/test/java/io/mifos/individuallending/internal/service/ScheduledActionTest.java
@@ -1,3 +1,18 @@
+/*
+ * Copyright 2017 The Mifos Initiative.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
 package io.mifos.individuallending.internal.service;
 
 import io.mifos.individuallending.api.v1.domain.workflow.Action;