Merge pull request #27 from myrle-krantz/develop

approve command (mostly) implemented
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 23d9462..37b337a 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
@@ -41,6 +41,9 @@
   String PAYMENT_NAME = "Payment";
   String PAYMENT_ID = nameToIdentifier(PAYMENT_NAME);
 
+  String MAXIMUM_BALANCE_DESIGNATOR = "{maximumbalance}";
+  String RUNNING_BALANCE_DESIGNATOR = "{runningbalance}";
+
   static String nameToIdentifier(String name) {
     return name.toLowerCase(Locale.US).replace(" ", "-");
   }
diff --git a/api/src/main/java/io/mifos/portfolio/api/v1/domain/ChargeDefinition.java b/api/src/main/java/io/mifos/portfolio/api/v1/domain/ChargeDefinition.java
index a831129..a7f1ffc 100644
--- a/api/src/main/java/io/mifos/portfolio/api/v1/domain/ChargeDefinition.java
+++ b/api/src/main/java/io/mifos/portfolio/api/v1/domain/ChargeDefinition.java
@@ -30,7 +30,10 @@
  * @author Myrle Krantz
  */
 @SuppressWarnings({"unused", "WeakerAccess"})
-@ScriptAssert(lang = "javascript", script = "_this.amount !== null && _this.amount.scale() <= 4 && ((_this.accrueAction === null && _this.accrualAccountDesignator === null) || (_this.accrueAction !== null && _this.accrualAccountDesignator !== null))")
+@ScriptAssert(lang = "javascript", script = "_this.amount !== null " +
+        "&& _this.amount.scale() <= 4 " +
+        "&& ((_this.accrueAction === null && _this.accrualAccountDesignator === null) || (_this.accrueAction !== null && _this.accrualAccountDesignator !== null))" +
+        "&& ((_this.chargeMethod == 'PROPORTIONAL' && _this.proportionalTo !== null) || (_this.chargeMethod == 'FIXED' && _this.proportionalTo === null))")
 public class ChargeDefinition {
   @SuppressWarnings("WeakerAccess")
   public enum ChargeMethod {
@@ -58,6 +61,9 @@
   @NotNull
   private ChargeMethod chargeMethod;
 
+  @ValidIdentifier(optional = true)
+  private String proportionalTo;
+
   @ValidIdentifier
   private String fromAccountDesignator; //Where it's going.
 
@@ -131,6 +137,14 @@
     this.chargeMethod = chargeMethod;
   }
 
+  public String getProportionalTo() {
+    return proportionalTo;
+  }
+
+  public void setProportionalTo(String proportionalTo) {
+    this.proportionalTo = proportionalTo;
+  }
+
   public String getFromAccountDesignator() {
     return fromAccountDesignator;
   }
@@ -176,6 +190,7 @@
             Objects.equals(chargeAction, that.chargeAction) &&
             Objects.equals(amount, that.amount) &&
             chargeMethod == that.chargeMethod &&
+            Objects.equals(proportionalTo, that.proportionalTo) &&
             Objects.equals(fromAccountDesignator, that.fromAccountDesignator) &&
             Objects.equals(accrualAccountDesignator, that.accrualAccountDesignator) &&
             Objects.equals(toAccountDesignator, that.toAccountDesignator) &&
@@ -184,7 +199,7 @@
 
   @Override
   public int hashCode() {
-    return Objects.hash(identifier, name, description, accrueAction, chargeAction, amount, chargeMethod, fromAccountDesignator, accrualAccountDesignator, toAccountDesignator, forCycleSizeUnit);
+    return Objects.hash(identifier, name, description, accrueAction, chargeAction, amount, chargeMethod, proportionalTo, fromAccountDesignator, accrualAccountDesignator, toAccountDesignator, forCycleSizeUnit);
   }
 
   @Override
@@ -197,6 +212,7 @@
             ", chargeAction='" + chargeAction + '\'' +
             ", amount=" + amount +
             ", chargeMethod=" + chargeMethod +
+            ", proportionalTo='" + proportionalTo + '\'' +
             ", fromAccountDesignator='" + fromAccountDesignator + '\'' +
             ", accrualAccountDesignator='" + accrualAccountDesignator + '\'' +
             ", toAccountDesignator='" + toAccountDesignator + '\'' +
diff --git a/api/src/test/java/io/mifos/portfolio/api/v1/domain/ChargeDefinitionTest.java b/api/src/test/java/io/mifos/portfolio/api/v1/domain/ChargeDefinitionTest.java
index bb4df4a..0838319 100644
--- a/api/src/test/java/io/mifos/portfolio/api/v1/domain/ChargeDefinitionTest.java
+++ b/api/src/test/java/io/mifos/portfolio/api/v1/domain/ChargeDefinitionTest.java
@@ -18,6 +18,7 @@
 import io.mifos.core.test.domain.ValidationTest;
 import io.mifos.core.test.domain.ValidationTestCase;
 import io.mifos.individuallending.api.v1.domain.workflow.Action;
+import org.apache.commons.lang.RandomStringUtils;
 import org.junit.runners.Parameterized;
 
 import java.math.BigDecimal;
@@ -42,6 +43,7 @@
     ret.setChargeAction(Action.OPEN.name());
     ret.setAmount(BigDecimal.ONE);
     ret.setChargeMethod(ChargeDefinition.ChargeMethod.PROPORTIONAL);
+    ret.setProportionalTo("balance");
     ret.setFromAccountDesignator("x1234567898");
     ret.setToAccountDesignator("y1234567898");
     ret.setForCycleSizeUnit(ChronoUnit.YEARS);
@@ -86,7 +88,21 @@
     ret.add(new ValidationTestCase<ChargeDefinition>("nullChargeMethod")
             .adjustment(x -> x.setChargeMethod(null))
             .valid(false));
+    ret.add(new ValidationTestCase<ChargeDefinition>("invalidProportionalToIdentifier")
+            .adjustment(x -> x.setProportionalTo(RandomStringUtils.random(33)))
+            .valid(false));
+    ret.add(new ValidationTestCase<ChargeDefinition>("missingProportionalToIdentifierOnProportionalCharge")
+            .adjustment(x -> x.setProportionalTo(null))
+            .valid(false));
+    ret.add(new ValidationTestCase<ChargeDefinition>("presentProportionalToIdentifierOnFixedCharge")
+            .adjustment(x -> x.setChargeMethod(ChargeDefinition.ChargeMethod.FIXED))
+            .valid(false));
+    ret.add(new ValidationTestCase<ChargeDefinition>("missingProportionalToIdentifierOnFixedCharge")
+            .adjustment(x -> {
+              x.setChargeMethod(ChargeDefinition.ChargeMethod.FIXED);
+              x.setProportionalTo(null);
+            })
+            .valid(true));
     return ret;
   }
-
 }
diff --git a/component-test/src/main/java/io/mifos/portfolio/AccountingFixture.java b/component-test/src/main/java/io/mifos/portfolio/AccountingFixture.java
index 3c401f3..c5d87fb 100644
--- a/component-test/src/main/java/io/mifos/portfolio/AccountingFixture.java
+++ b/component-test/src/main/java/io/mifos/portfolio/AccountingFixture.java
@@ -17,15 +17,17 @@
 
 import io.mifos.accounting.api.v1.client.LedgerManager;
 import io.mifos.accounting.api.v1.domain.*;
+import org.hamcrest.Description;
+import org.mockito.AdditionalMatchers;
 import org.mockito.ArgumentMatcher;
 import org.mockito.Mockito;
 
 import javax.validation.Validation;
 import javax.validation.Validator;
 import java.math.BigDecimal;
-import java.util.Optional;
+import java.util.Arrays;
+import java.util.Collections;
 import java.util.Set;
-import java.util.stream.Collectors;
 
 import static io.mifos.portfolio.Fixture.*;
 import static org.mockito.Matchers.argThat;
@@ -106,6 +108,52 @@
     return ret;
   }
 
+  private static AccountPage customerLoanAccountsPage() {
+    final Account customerLoanAccount1 = new Account();
+    customerLoanAccount1.setIdentifier("customerLoanAccount1");
+    final Account customerLoanAccount2 = new Account();
+    customerLoanAccount2.setIdentifier("customerLoanAccount2");
+    final Account customerLoanAccount3 = new Account();
+    customerLoanAccount3.setIdentifier("customerLoanAccount3");
+
+    final AccountPage ret = new AccountPage();
+    ret.setTotalElements(3L);
+    ret.setTotalPages(1);
+    ret.setAccounts(Arrays.asList(customerLoanAccount1, customerLoanAccount2, customerLoanAccount3));
+    return ret;
+  }
+
+  private static Object pendingDisbursalAccountsPage() {
+    final Account pendingDisbursalAccount1 = new Account();
+    pendingDisbursalAccount1.setIdentifier("pendingDisbursalAccount1");
+    final Account pendingDisbursalAccount2 = new Account();
+    pendingDisbursalAccount2.setIdentifier("pendingDisbursalAccount2");
+    final Account pendingDisbursalAccount3 = new Account();
+    pendingDisbursalAccount3.setIdentifier("pendingDisbursalAccount3");
+
+    final AccountPage ret = new AccountPage();
+    ret.setTotalElements(3L);
+    ret.setTotalPages(1);
+    ret.setAccounts(Arrays.asList(pendingDisbursalAccount1, pendingDisbursalAccount2, pendingDisbursalAccount3));
+    return ret;
+  }
+
+  private static <T> Valid<T> isValid() {
+    return new Valid<>();
+  }
+
+  private static class Valid<T> extends ArgumentMatcher<T> {
+    @Override
+    public boolean matches(final Object argument) {
+      if (argument == null)
+        return false;
+      final Validator validator = Validation.buildDefaultValidatorFactory().getValidator();
+      final Set errors = validator.validate(argument);
+
+      return errors.size() == 0;
+    }
+  }
+
   private static class AccountMatcher extends ArgumentMatcher<Account> {
     private final String ledgerIdentifer;
     private final AccountType type;
@@ -126,11 +174,7 @@
 
       checkedArgument = (Account) argument;
 
-      final Validator validator = Validation.buildDefaultValidatorFactory().getValidator();
-      final Set errors = validator.validate(checkedArgument, Account.class);
-
-      return errors.size() == 0 &&
-              checkedArgument.getLedger().equals(ledgerIdentifer) &&
+      return checkedArgument.getLedger().equals(ledgerIdentifer) &&
               checkedArgument.getType().equals(type.name()) &&
               checkedArgument.getBalance() == 0.0;
     }
@@ -141,17 +185,14 @@
   }
 
   private static class JournalEntryMatcher extends ArgumentMatcher<JournalEntry> {
-    private final String expectedFromAccountIdentifier;
-    private final String expectedToAccountIdentifier;
-    private final BigDecimal expectedAmount;
+    private final Set<Debtor> debtors;
+    private final Set<Creditor> creditors;
     private JournalEntry checkedArgument;
 
-    private JournalEntryMatcher(final String expectedFromAccountIdentifier,
-                                final String expectedToAccountIdentifier,
-                                final BigDecimal amount) {
-      this.expectedFromAccountIdentifier = expectedFromAccountIdentifier;
-      this.expectedToAccountIdentifier = expectedToAccountIdentifier;
-      this.expectedAmount = amount;
+    private JournalEntryMatcher(final Set<Debtor> debtors,
+                                final Set<Creditor> creditors) {
+      this.debtors = debtors;
+      this.creditors = creditors;
       this.checkedArgument = null; //Set when matches called.
     }
 
@@ -163,37 +204,24 @@
         return false;
 
       checkedArgument = (JournalEntry) argument;
-      final Validator validator = Validation.buildDefaultValidatorFactory().getValidator();
-      final Set errors = validator.validate(checkedArgument);
 
-      final Double debitAmount = checkedArgument.getDebtors().stream()
-              .collect(Collectors.summingDouble(x -> Double.valueOf(x.getAmount())));
+      checkedArgument.getDebtors();
+      checkedArgument.getCreditors();
 
-      final Optional<String> fromAccountIdentifier = checkedArgument.getDebtors().stream().findFirst().map(Debtor::getAccountNumber);
-
-      final Double creditAmount = checkedArgument.getCreditors().stream()
-              .collect(Collectors.summingDouble(x -> Double.valueOf(x.getAmount())));
-
-      final Optional<String> toAccountIdentifier = checkedArgument.getCreditors().stream().findFirst().map(Creditor::getAccountNumber);
-
-      return (errors.size() == 0 &&
-              fromAccountIdentifier.isPresent() && fromAccountIdentifier.get().equals(expectedFromAccountIdentifier) &&
-              toAccountIdentifier.isPresent() && toAccountIdentifier.get().equals(expectedToAccountIdentifier) &&
-              creditAmount.equals(debitAmount) &&
-              creditAmount.equals(expectedAmount.doubleValue()));
+      return this.debtors.equals(checkedArgument.getDebtors()) &&
+              this.creditors.equals(checkedArgument.getCreditors());
     }
 
-    JournalEntry getCheckedArgument() {
-      return checkedArgument;
+    @Override
+    public void describeTo(final Description description) {
+      description.appendText(this.toString());
     }
 
     @Override
     public String toString() {
       return "JournalEntryMatcher{" +
-              "expectedFromAccountIdentifier='" + expectedFromAccountIdentifier + '\'' +
-              ", expectedToAccountIdentifier='" + expectedToAccountIdentifier + '\'' +
-              ", expectedAmount=" + expectedAmount +
-              ", checkedArgument=" + checkedArgument +
+              "debtors=" + debtors +
+              ", creditors=" + creditors +
               '}';
     }
   }
@@ -208,13 +236,17 @@
     Mockito.doReturn(loanOriginationFeesIncomeAccount()).when(ledgerManagerMock).findAccount(LOAN_ORIGINATION_FEES_ACCOUNT_IDENTIFIER);
     Mockito.doReturn(processingFeeIncomeAccount()).when(ledgerManagerMock).findAccount(PROCESSING_FEE_INCOME_ACCOUNT_IDENTIFIER);
     Mockito.doReturn(tellerOneAccount()).when(ledgerManagerMock).findAccount(TELLER_ONE_ACCOUNT_IDENTIFIER);
+    Mockito.doReturn(customerLoanAccountsPage()).when(ledgerManagerMock).fetchAccountsOfLedger(Mockito.eq(CUSTOMER_LOAN_LEDGER_IDENTIFIER),
+            Mockito.any(), Mockito.any(), Mockito.any(), Mockito.any());
+    Mockito.doReturn(pendingDisbursalAccountsPage()).when(ledgerManagerMock).fetchAccountsOfLedger(Mockito.eq(PENDING_DISBURSAL_LEDGER_IDENTIFIER),
+            Mockito.any(), Mockito.any(), Mockito.any(), Mockito.any());
   }
 
   static String verifyAccountCreation(final LedgerManager ledgerManager,
                                       final String ledgerIdentifier,
                                       final AccountType type) {
     final AccountMatcher specifiesCorrectAccount = new AccountMatcher(ledgerIdentifier, type);
-    Mockito.verify(ledgerManager).createAccount(argThat(specifiesCorrectAccount));
+    Mockito.verify(ledgerManager).createAccount(AdditionalMatchers.and(argThat(isValid()), argThat(specifiesCorrectAccount)));
     return specifiesCorrectAccount.getCheckedArgument().getIdentifier();
   }
 
@@ -222,7 +254,16 @@
                              final String fromAccountIdentifier,
                              final String toAccountIdentifier,
                              final BigDecimal amount) {
-    final JournalEntryMatcher specifiesCorrectJournalEntry = new JournalEntryMatcher(fromAccountIdentifier, toAccountIdentifier, amount);
-    Mockito.verify(ledgerManager).createJournalEntry(argThat(specifiesCorrectJournalEntry));
+    final JournalEntryMatcher specifiesCorrectJournalEntry = new JournalEntryMatcher(
+            Collections.singleton(new Debtor(fromAccountIdentifier, amount.toPlainString())),
+            Collections.singleton(new Creditor(toAccountIdentifier, amount.toPlainString())));
+    Mockito.verify(ledgerManager).createJournalEntry(AdditionalMatchers.and(argThat(isValid()), argThat(specifiesCorrectJournalEntry)));
   }
-}
+
+  static void verifyTransfer(final LedgerManager ledgerManager,
+                             final Set<Debtor> debtors,
+                             final Set<Creditor> creditors) {
+    final JournalEntryMatcher specifiesCorrectJournalEntry = new JournalEntryMatcher(debtors, creditors);
+    Mockito.verify(ledgerManager).createJournalEntry(AdditionalMatchers.and(argThat(isValid()), argThat(specifiesCorrectJournalEntry)));
+  }
+}
\ No newline at end of file
diff --git a/component-test/src/main/java/io/mifos/portfolio/Fixture.java b/component-test/src/main/java/io/mifos/portfolio/Fixture.java
index b9f0893..3ad05ee 100644
--- a/component-test/src/main/java/io/mifos/portfolio/Fixture.java
+++ b/component-test/src/main/java/io/mifos/portfolio/Fixture.java
@@ -63,7 +63,10 @@
     product.setMinorCurrencyUnitDigits(2);
 
     final Set<AccountAssignment> accountAssignments = new HashSet<>();
-    accountAssignments.add(new AccountAssignment(PENDING_DISBURSAL, PENDING_DISBURSAL_LEDGER_IDENTIFIER));
+    final AccountAssignment pendingDisbursalAccountAssignment = new AccountAssignment();
+    pendingDisbursalAccountAssignment.setDesignator(PENDING_DISBURSAL);
+    pendingDisbursalAccountAssignment.setLedgerIdentifier(PENDING_DISBURSAL_LEDGER_IDENTIFIER);
+    accountAssignments.add(pendingDisbursalAccountAssignment);
     accountAssignments.add(new AccountAssignment(PROCESSING_FEE_INCOME, PROCESSING_FEE_INCOME_ACCOUNT_IDENTIFIER));
     accountAssignments.add(new AccountAssignment(ORIGINATION_FEE_INCOME, LOAN_ORIGINATION_FEES_ACCOUNT_IDENTIFIER));
     accountAssignments.add(new AccountAssignment(DISBURSEMENT_FEE_INCOME, "001-004"));
diff --git a/component-test/src/main/java/io/mifos/portfolio/TestAccountingInteraction.java b/component-test/src/main/java/io/mifos/portfolio/TestAccountingInteraction.java
index c471655..91bf999 100644
--- a/component-test/src/main/java/io/mifos/portfolio/TestAccountingInteraction.java
+++ b/component-test/src/main/java/io/mifos/portfolio/TestAccountingInteraction.java
@@ -17,6 +17,8 @@
 
 import com.google.gson.Gson;
 import io.mifos.accounting.api.v1.domain.AccountType;
+import io.mifos.accounting.api.v1.domain.Creditor;
+import io.mifos.accounting.api.v1.domain.Debtor;
 import io.mifos.individuallending.api.v1.domain.caseinstance.CaseParameters;
 import io.mifos.individuallending.api.v1.domain.workflow.Action;
 import io.mifos.portfolio.api.v1.domain.*;
@@ -27,6 +29,8 @@
 
 import java.math.BigDecimal;
 import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
 
 import static io.mifos.individuallending.api.v1.domain.product.ChargeIdentifiers.*;
 import static io.mifos.individuallending.api.v1.events.IndividualLoanEventConstants.APPROVE_INDIVIDUALLOAN_CASE;
@@ -46,6 +50,7 @@
     final ChargeDefinition processingFee = portfolioManager.getChargeDefinition(product.getIdentifier(), PROCESSING_FEE_ID);
     processingFee.setChargeMethod(ChargeDefinition.ChargeMethod.FIXED);
     processingFee.setAmount(BigDecimal.valueOf(10_0000, 4));
+    processingFee.setProportionalTo(null);
     portfolioManager.changeChargeDefinition(product.getIdentifier(), PROCESSING_FEE_ID, processingFee);
     Assert.assertTrue(this.eventRecorder.wait(EventConstants.PUT_CHARGE_DEFINITION,
             new ChargeDefinitionEvent(product.getIdentifier(), PROCESSING_FEE_ID)));
@@ -53,6 +58,7 @@
     final ChargeDefinition loanOriginationFee = portfolioManager.getChargeDefinition(product.getIdentifier(), LOAN_ORIGINATION_FEE_ID);
     loanOriginationFee.setChargeMethod(ChargeDefinition.ChargeMethod.FIXED);
     loanOriginationFee.setAmount(BigDecimal.valueOf(100_0000, 4));
+    loanOriginationFee.setProportionalTo(null);
     portfolioManager.changeChargeDefinition(product.getIdentifier(), LOAN_ORIGINATION_FEE_ID, loanOriginationFee);
     Assert.assertTrue(this.eventRecorder.wait(EventConstants.PUT_CHARGE_DEFINITION,
             new ChargeDefinitionEvent(product.getIdentifier(), LOAN_ORIGINATION_FEE_ID)));
@@ -60,8 +66,6 @@
     portfolioManager.enableProduct(product.getIdentifier(), true);
     Assert.assertTrue(this.eventRecorder.wait(EventConstants.PUT_PRODUCT_ENABLE, product.getIdentifier()));
 
-
-
     //Create case.
     final CaseParameters caseParameters = Fixture.createAdjustedCaseParameters(x -> {});
     final String caseParametersAsString = new Gson().toJson(caseParameters);
@@ -70,7 +74,7 @@
     //Open the case and accept a processing fee.
     checkNextActionsCorrect(product.getIdentifier(), customerCase.getIdentifier(), Action.OPEN);
     checkCostComponentForActionCorrect(product.getIdentifier(), customerCase.getIdentifier(), Action.OPEN,
-            new CostComponent(processingFee.getIdentifier(), processingFee.getAmount()));
+            new CostComponent(processingFee.getIdentifier(), processingFee.getAmount().setScale(product.getMinorCurrencyUnitDigits(), BigDecimal.ROUND_UNNECESSARY)));
 
     final AccountAssignment openCommandProcessingFeeAccountAssignment = new AccountAssignment();
     openCommandProcessingFeeAccountAssignment.setDesignator(processingFee.getFromAccountDesignator());
@@ -81,11 +85,41 @@
             OPEN_INDIVIDUALLOAN_CASE, Case.State.PENDING);
     checkNextActionsCorrect(product.getIdentifier(), customerCase.getIdentifier(), Action.APPROVE, Action.DENY);
     checkCostComponentForActionCorrect(product.getIdentifier(), customerCase.getIdentifier(), Action.APPROVE,
-            new CostComponent(loanOriginationFee.getIdentifier(), loanOriginationFee.getAmount()));
+            new CostComponent(loanOriginationFee.getIdentifier(), loanOriginationFee.getAmount().setScale(product.getMinorCurrencyUnitDigits(), BigDecimal.ROUND_UNNECESSARY)),
+            new CostComponent(LOAN_FUNDS_ALLOCATION_ID, caseParameters.getMaximumBalance().setScale(product.getMinorCurrencyUnitDigits(), BigDecimal.ROUND_UNNECESSARY)));
 
     AccountingFixture.verifyTransfer(ledgerManager,
             TELLER_ONE_ACCOUNT_IDENTIFIER, PROCESSING_FEE_INCOME_ACCOUNT_IDENTIFIER,
-            processingFee.getAmount()
+            processingFee.getAmount().setScale(product.getMinorCurrencyUnitDigits(), BigDecimal.ROUND_UNNECESSARY)
             );
+
+
+    //Approve the case, accept a loan origination fee, and prepare to disburse the loan by earmarking the funds.
+    final AccountAssignment approveCommandOriginationFeeAccountAssignment = new AccountAssignment();
+    approveCommandOriginationFeeAccountAssignment.setDesignator(loanOriginationFee.getFromAccountDesignator());
+    approveCommandOriginationFeeAccountAssignment.setAccountIdentifier(TELLER_ONE_ACCOUNT_IDENTIFIER);
+
+    checkStateTransfer(product.getIdentifier(), customerCase.getIdentifier(), Action.APPROVE,
+            Collections.singletonList(approveCommandOriginationFeeAccountAssignment),
+            APPROVE_INDIVIDUALLOAN_CASE, Case.State.APPROVED);
+    checkNextActionsCorrect(product.getIdentifier(), customerCase.getIdentifier(), Action.DISBURSE, Action.CLOSE);
+
+    final String pendingDisbursalAccountIdentifier =
+            AccountingFixture.verifyAccountCreation(ledgerManager, Fixture.PENDING_DISBURSAL_LEDGER_IDENTIFIER, AccountType.ASSET);
+    final String customerLoanAccountIdentifier =
+            AccountingFixture.verifyAccountCreation(ledgerManager, Fixture.CUSTOMER_LOAN_LEDGER_IDENTIFIER, AccountType.ASSET);
+
+    final Set<Debtor> debtors = new HashSet<>();
+    debtors.add(new Debtor(LOAN_FUNDS_SOURCE_ACCOUNT_IDENTIFIER, toString(caseParameters.getMaximumBalance(), product.getMinorCurrencyUnitDigits())));
+    debtors.add(new Debtor(TELLER_ONE_ACCOUNT_IDENTIFIER, toString(loanOriginationFee.getAmount(), product.getMinorCurrencyUnitDigits())));
+
+    final Set<Creditor> creditors = new HashSet<>();
+    creditors.add(new Creditor(pendingDisbursalAccountIdentifier, toString(caseParameters.getMaximumBalance(), product.getMinorCurrencyUnitDigits())));
+    creditors.add(new Creditor(LOAN_ORIGINATION_FEES_ACCOUNT_IDENTIFIER, toString(loanOriginationFee.getAmount(), product.getMinorCurrencyUnitDigits())));
+    AccountingFixture.verifyTransfer(ledgerManager, debtors, creditors);
   }
-}
+
+  private String toString(final BigDecimal bigDecimal, final int scale) {
+    return bigDecimal.setScale(scale, BigDecimal.ROUND_UNNECESSARY).toPlainString();
+  }
+}
\ No newline at end of file
diff --git a/component-test/src/main/java/io/mifos/portfolio/TestChargeDefinitions.java b/component-test/src/main/java/io/mifos/portfolio/TestChargeDefinitions.java
index 91976f4..b67db68 100644
--- a/component-test/src/main/java/io/mifos/portfolio/TestChargeDefinitions.java
+++ b/component-test/src/main/java/io/mifos/portfolio/TestChargeDefinitions.java
@@ -46,6 +46,7 @@
             ChargeIdentifiers.DISBURSEMENT_FEE_ID,
             ChargeIdentifiers.INTEREST_ID,
             ChargeIdentifiers.LATE_FEE_ID,
+            ChargeIdentifiers.LOAN_FUNDS_ALLOCATION_ID,
             ChargeIdentifiers.LOAN_ORIGINATION_FEE_ID,
             ChargeIdentifiers.PROCESSING_FEE_ID,
             ChargeIdentifiers.RETURN_DISBURSEMENT_ID)
diff --git a/service/src/main/java/io/mifos/individuallending/IndividualLendingPatternFactory.java b/service/src/main/java/io/mifos/individuallending/IndividualLendingPatternFactory.java
index bd4ff15..6591023 100644
--- a/service/src/main/java/io/mifos/individuallending/IndividualLendingPatternFactory.java
+++ b/service/src/main/java/io/mifos/individuallending/IndividualLendingPatternFactory.java
@@ -17,14 +17,17 @@
 
 import com.google.gson.Gson;
 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.workflow.Action;
 import io.mifos.individuallending.internal.mapper.CaseParametersMapper;
 import io.mifos.individuallending.internal.repository.CaseCreditWorthinessFactorEntity;
 import io.mifos.individuallending.internal.repository.CaseParametersEntity;
 import io.mifos.individuallending.internal.repository.CaseParametersRepository;
 import io.mifos.individuallending.internal.repository.CreditWorthinessFactorType;
+import io.mifos.individuallending.internal.service.CostComponentService;
 import io.mifos.portfolio.api.v1.domain.Case;
 import io.mifos.portfolio.api.v1.domain.ChargeDefinition;
+import io.mifos.portfolio.api.v1.domain.CostComponent;
 import io.mifos.portfolio.api.v1.domain.Pattern;
 import io.mifos.portfolio.service.ServiceConstants;
 import io.mifos.products.spi.PatternFactory;
@@ -50,16 +53,19 @@
 public class IndividualLendingPatternFactory implements PatternFactory {
   final static private String INDIVIDUAL_LENDING_PACKAGE = "io.mifos.individuallending.api.v1";
   private final CaseParametersRepository caseParametersRepository;
-  private IndividualLendingCommandDispatcher individualLendingCommandDispatcher;
+  private final CostComponentService costComponentService;
+  private final IndividualLendingCommandDispatcher individualLendingCommandDispatcher;
   private final Gson gson;
 
   @Autowired
   IndividualLendingPatternFactory(
           final CaseParametersRepository caseParametersRepository,
+          final CostComponentService costComponentService,
           final IndividualLendingCommandDispatcher individualLendingCommandDispatcher,
           @Qualifier(ServiceConstants.GSON_NAME) final Gson gson)
   {
     this.caseParametersRepository = caseParametersRepository;
+    this.costComponentService = costComponentService;
     this.individualLendingCommandDispatcher = individualLendingCommandDispatcher;
     this.gson = gson;
   }
@@ -118,15 +124,16 @@
             ENTRY,
             DISBURSEMENT_FEE_INCOME);
 
-    //TODO: Make proportional to payment rather than loan amount.
+    //TODO: Make payable at time of ACCEPT_PAYMENT but accrued at MARK_LATE
     final ChargeDefinition lateFee = charge(
             LATE_FEE_NAME,
-            Action.ACCEPT_PAYMENT,
+            Action.MARK_LATE,
             BigDecimal.valueOf(0.01),
             CUSTOMER_LOAN,
             LATE_FEE_INCOME);
     lateFee.setAccrueAction(Action.MARK_LATE.name());
     lateFee.setAccrualAccountDesignator(LATE_FEE_ACCRUAL);
+    lateFee.setProportionalTo(ChargeIdentifiers.PAYMENT_ID);
 
     //TODO: Make multiple write off allowance charges.
     final ChargeDefinition writeOffAllowanceCharge = charge(
@@ -135,6 +142,7 @@
             BigDecimal.valueOf(0.30),
             PENDING_DISBURSAL,
             ARREARS_ALLOWANCE);
+    writeOffAllowanceCharge.setProportionalTo(ChargeIdentifiers.RUNNING_BALANCE_DESIGNATOR);
 
     final ChargeDefinition interestCharge = charge(
             INTEREST_NAME,
@@ -145,6 +153,7 @@
     interestCharge.setForCycleSizeUnit(ChronoUnit.YEARS);
     interestCharge.setAccrueAction(Action.APPLY_INTEREST.name());
     interestCharge.setAccrualAccountDesignator(INTEREST_ACCRUAL);
+    interestCharge.setProportionalTo(ChargeIdentifiers.RUNNING_BALANCE_DESIGNATOR);
 
     final ChargeDefinition disbursementReturnCharge = charge(
             RETURN_DISBURSEMENT_NAME,
@@ -152,9 +161,11 @@
             BigDecimal.valueOf(1.0),
             PENDING_DISBURSAL,
             LOAN_FUNDS_SOURCE);
+    interestCharge.setProportionalTo(ChargeIdentifiers.RUNNING_BALANCE_DESIGNATOR); //TODO: Balance in which account?
 
     ret.add(processingFee);
     ret.add(loanOriginationFee);
+    ret.add(loanFundsAllocation);
     ret.add(disbursementFee);
     ret.add(lateFee);
     ret.add(writeOffAllowanceCharge);
@@ -247,6 +258,17 @@
     return getAllowedNextActionsForState(state).stream().map(Enum::name).collect(Collectors.toSet());
   }
 
+  @Override
+  public List<CostComponent> getCostComponentsForAction(
+          final String productIdentifier,
+          final String caseIdentifier,
+          final String actionIdentifier) {
+    return costComponentService.getCostComponents(productIdentifier, caseIdentifier, Action.valueOf(actionIdentifier))
+            .stream()
+            .map(x -> new CostComponent(x.getChargeIdentifier(), x.getAmount()))
+            .collect(Collectors.toList());
+  }
+
   public static Set<Action> getAllowedNextActionsForState(Case.State state) {
     switch (state)
     {
@@ -285,6 +307,7 @@
     ret.setChargeAction(action.name());
     ret.setAmount(defaultAmount);
     ret.setChargeMethod(ChargeDefinition.ChargeMethod.PROPORTIONAL);
+    ret.setProportionalTo(ChargeIdentifiers.MAXIMUM_BALANCE_DESIGNATOR);
     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 4d42435..6f65ccb 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,19 +22,15 @@
 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.caseinstance.CaseParameters;
 import io.mifos.individuallending.api.v1.domain.workflow.Action;
 import io.mifos.individuallending.api.v1.events.IndividualLoanCommandEvent;
 import io.mifos.individuallending.api.v1.events.IndividualLoanEventConstants;
 import io.mifos.individuallending.internal.command.*;
-import io.mifos.individuallending.internal.mapper.CaseParametersMapper;
-import io.mifos.individuallending.internal.repository.CaseParametersRepository;
-import io.mifos.individuallending.internal.service.IndividualLoanService;
+import io.mifos.individuallending.internal.service.*;
 import io.mifos.portfolio.api.v1.domain.AccountAssignment;
 import io.mifos.portfolio.api.v1.domain.Case;
 import io.mifos.portfolio.api.v1.events.EventConstants;
 import io.mifos.portfolio.service.internal.mapper.CaseMapper;
-import io.mifos.portfolio.service.internal.mapper.ProductMapper;
 import io.mifos.portfolio.service.internal.repository.*;
 import io.mifos.portfolio.service.internal.util.AccountingAdapter;
 import io.mifos.portfolio.service.internal.util.ChargeInstance;
@@ -45,9 +41,7 @@
 import java.time.LocalDate;
 import java.time.ZoneId;
 import java.util.List;
-import java.util.Set;
 import java.util.stream.Collectors;
-import java.util.stream.Stream;
 
 /**
  * @author Myrle Krantz
@@ -55,94 +49,66 @@
 @SuppressWarnings("unused")
 @Aggregate
 public class IndividualLoanCommandHandler {
-
-  private final ProductRepository productRepository;
   private final CaseRepository caseRepository;
-  private final CaseParametersRepository caseParametersRepository;
-  private final AccountingAdapter accountingAdapter;
+  private final CostComponentService costComponentService;
   private final IndividualLoanService individualLoanService;
+  private final AccountingAdapter accountingAdapter;
 
   @Autowired
-  public IndividualLoanCommandHandler(final ProductRepository productRepository,
-                                      final CaseRepository caseRepository,
-                                      final CaseParametersRepository caseParametersRepository,
-                                      final AccountingAdapter accountingAdapter,
-                                      final IndividualLoanService individualLoanService) {
-    this.productRepository = productRepository;
+  public IndividualLoanCommandHandler(
+          final CaseRepository caseRepository,
+          final CostComponentService costComponentService,
+          final IndividualLoanService individualLoanService,
+          final AccountingAdapter accountingAdapter) {
     this.caseRepository = caseRepository;
-    this.caseParametersRepository = caseParametersRepository;
-    this.accountingAdapter = accountingAdapter;
+    this.costComponentService = costComponentService;
     this.individualLoanService = individualLoanService;
+    this.accountingAdapter = accountingAdapter;
   }
 
   @Transactional
   @CommandHandler(logStart = CommandLogLevel.INFO, logFinish = CommandLogLevel.INFO)
   @EventEmitter(selectorName = EventConstants.SELECTOR_NAME, selectorValue = IndividualLoanEventConstants.OPEN_INDIVIDUALLOAN_CASE)
   public IndividualLoanCommandEvent process(final OpenCommand command) {
-    final ProductEntity product = getProductOrThrow(command.getProductIdentifier());
-    final CaseEntity customerCase = getCaseOrThrow(command.getProductIdentifier(), command.getCaseIdentifier());
-    checkActionCanBeExecuted(Case.State.valueOf(customerCase.getCurrentState()), Action.OPEN);
+    final String productIdentifier = command.getProductIdentifier();
+    final String caseIdentifier = command.getCaseIdentifier();
+    final DataContextOfAction dataContextOfAction = costComponentService.checkedGetDataContext(
+            productIdentifier, caseIdentifier, command.getCommand().getOneTimeAccountAssignments());
+    checkActionCanBeExecuted(Case.State.valueOf(dataContextOfAction.getCustomerCase().getCurrentState()), Action.OPEN);
 
-    final CaseParameters caseParameters =
-            caseParametersRepository.findByCaseId(customerCase.getId())
-            .map(CaseParametersMapper::mapEntity)
-            .orElseThrow(() -> ServiceException.notFound(
-                    "Individual loan with identifier ''{0}''.''{1}'' doesn''t exist.",
-                    command.getProductIdentifier(), command.getCaseIdentifier()));
 
-    final Set<ProductAccountAssignmentEntity> productAccountAssignments = product.getAccountAssignments();
-    final Set<CaseAccountAssignmentEntity> caseAccountAssignments = customerCase.getAccountAssignments();
+    final CostComponentsForRepaymentPeriod costComponentsForRepaymentPeriod =
+            individualLoanService.getCostComponentsForRepaymentPeriod(productIdentifier, dataContextOfAction.getCaseParameters(), BigDecimal.ZERO, Action.OPEN, today(), LocalDate.now());
 
-    final List<ChargeInstance> chargesNamedViaAccountDesignators =
-            individualLoanService.getChargeInstances(command.getProductIdentifier(), caseParameters, BigDecimal.ZERO, Action.OPEN, today(), LocalDate.now());
-    final List<ChargeInstance> chargesNamedViaAccountIdentifier = chargesNamedViaAccountDesignators.stream().map(x -> new ChargeInstance(
-            designatorToAccountIdentifierOrThrow(x.getFromAccount(), command.getCommand().getOneTimeAccountAssignments(), caseAccountAssignments, productAccountAssignments),
-            designatorToAccountIdentifierOrThrow(x.getToAccount(), command.getCommand().getOneTimeAccountAssignments(), caseAccountAssignments, productAccountAssignments),
-            x.getAmount())).collect(Collectors.toList());
+
+    final DesignatorToAccountIdentifierMapper designatorToAccountIdentifierMapper
+            = new DesignatorToAccountIdentifierMapper(dataContextOfAction);
+
+    final List<ChargeInstance> charges = costComponentsForRepaymentPeriod.stream().map(x -> new ChargeInstance(
+            designatorToAccountIdentifierMapper.mapOrThrow(x.getKey().getFromAccountDesignator()),
+            designatorToAccountIdentifierMapper.mapOrThrow(x.getKey().getToAccountDesignator()),
+            x.getValue().getAmount())).collect(Collectors.toList());
     //TODO: Accrual
 
-    accountingAdapter.bookCharges(chargesNamedViaAccountIdentifier,
+    accountingAdapter.bookCharges(charges,
             command.getCommand().getNote(),
-            command.getProductIdentifier() + "." + command.getCaseIdentifier() + "." + Action.OPEN.name(),
+            productIdentifier + "." + caseIdentifier + "." + Action.OPEN.name(),
             Action.OPEN.getTransactionType());
-    //Only move to pending if book charges command was accepted.
-    updateCaseState(customerCase, Case.State.PENDING);
+    //Only move to new state if book charges command was accepted.
+    updateCaseState(dataContextOfAction.getCustomerCase(), Case.State.PENDING);
 
-    return new IndividualLoanCommandEvent(command.getProductIdentifier(), command.getCaseIdentifier(), "x");
-  }
-
-  private static LocalDate today() {
-    return LocalDate.now(ZoneId.of("UTC"));
-  }
-
-  private String designatorToAccountIdentifierOrThrow(final String accountDesignator,
-                                                      final List<AccountAssignment> oneTimeAccountAssignments,
-                                                      final Set<CaseAccountAssignmentEntity> caseAccountAssignments,
-                                                      final Set<ProductAccountAssignmentEntity> productAccountAssignments) {
-    return allAccountAssignmentsAsStream(oneTimeAccountAssignments, caseAccountAssignments, productAccountAssignments)
-            .filter(x -> x.getDesignator().equals(accountDesignator))
-            .findFirst()
-            .map(AccountAssignment::getAccountIdentifier)
-            .orElseThrow(() -> ServiceException.badRequest("A required account designator was not set ''{0}''.", accountDesignator));
-  }
-
-  private Stream<AccountAssignment> allAccountAssignmentsAsStream(
-          final List<AccountAssignment> oneTimeAccountAssignments,
-          final Set<CaseAccountAssignmentEntity> caseAccountAssignments,
-          final Set<ProductAccountAssignmentEntity> productAccountAssignments) {
-    return Stream.concat(Stream.concat(
-            oneTimeAccountAssignments.stream(),
-            caseAccountAssignments.stream().map(CaseMapper::mapAccountAssignmentEntity)),
-            productAccountAssignments.stream().map(ProductMapper::mapAccountAssignmentEntity));
+    return new IndividualLoanCommandEvent(productIdentifier, caseIdentifier, Action.OPEN.name());
   }
 
   @Transactional
   @CommandHandler(logStart = CommandLogLevel.INFO, logFinish = CommandLogLevel.INFO)
   @EventEmitter(selectorName = EventConstants.SELECTOR_NAME, selectorValue = IndividualLoanEventConstants.DENY_INDIVIDUALLOAN_CASE)
   public IndividualLoanCommandEvent process(final DenyCommand command) {
-    final CaseEntity customerCase = getCaseOrThrow(command.getProductIdentifier(), command.getCaseIdentifier());
-    checkActionCanBeExecuted(Case.State.valueOf(customerCase.getCurrentState()), Action.DENY);
-    updateCaseState(customerCase, Case.State.CLOSED);
+    final String productIdentifier = command.getProductIdentifier();
+    final String caseIdentifier = command.getCaseIdentifier();
+    final DataContextOfAction dataContextOfAction = costComponentService.checkedGetDataContext(productIdentifier, caseIdentifier, command.getCommand().getOneTimeAccountAssignments());
+    checkActionCanBeExecuted(Case.State.valueOf(dataContextOfAction.getCustomerCase().getCurrentState()), Action.DENY);
+    updateCaseState(dataContextOfAction.getCustomerCase(), Case.State.CLOSED);
     return new IndividualLoanCommandEvent(command.getProductIdentifier(), command.getCaseIdentifier(), "x");
   }
 
@@ -150,19 +116,56 @@
   @CommandHandler(logStart = CommandLogLevel.INFO, logFinish = CommandLogLevel.INFO)
   @EventEmitter(selectorName = EventConstants.SELECTOR_NAME, selectorValue = IndividualLoanEventConstants.APPROVE_INDIVIDUALLOAN_CASE)
   public IndividualLoanCommandEvent process(final ApproveCommand command) {
-    final CaseEntity customerCase = getCaseOrThrow(command.getProductIdentifier(), command.getCaseIdentifier());
-    checkActionCanBeExecuted(Case.State.valueOf(customerCase.getCurrentState()), Action.APPROVE);
-    updateCaseState(customerCase, Case.State.APPROVED);
-    return new IndividualLoanCommandEvent(command.getProductIdentifier(), command.getCaseIdentifier(), "x");
+    final String productIdentifier = command.getProductIdentifier();
+    final String caseIdentifier = command.getCaseIdentifier();
+    final DataContextOfAction dataContextOfAction = costComponentService.checkedGetDataContext(productIdentifier, caseIdentifier, command.getCommand().getOneTimeAccountAssignments());
+    checkActionCanBeExecuted(Case.State.valueOf(dataContextOfAction.getCustomerCase().getCurrentState()), Action.APPROVE);
+
+    //TODO: Check for incomplete task instances.
+
+    final DesignatorToAccountIdentifierMapper designatorToAccountIdentifierMapper
+            = new DesignatorToAccountIdentifierMapper(dataContextOfAction);
+
+    //Create the needed account assignments and persist them for the case.
+    designatorToAccountIdentifierMapper.getLedgersNeedingAccounts()
+            .map(ledger ->
+                    new AccountAssignment(ledger.getDesignator(),
+                            accountingAdapter.createAccountForLedgerAssignment(dataContextOfAction.getCaseParameters().getCustomerIdentifier(), ledger)))
+            .map(accountAssignment -> CaseMapper.map(accountAssignment, dataContextOfAction.getCustomerCase()))
+            .forEach(caseAccountAssignmentEntity ->
+              dataContextOfAction.getCustomerCase().getAccountAssignments().add(caseAccountAssignmentEntity)
+            );
+    caseRepository.save(dataContextOfAction.getCustomerCase());
+
+    //Charge the approval fee if applicable.
+    final CostComponentsForRepaymentPeriod costComponentsForRepaymentPeriod =
+            individualLoanService.getCostComponentsForRepaymentPeriod(productIdentifier, dataContextOfAction.getCaseParameters(), BigDecimal.ZERO, Action.APPROVE, today(), LocalDate.now());
+
+    final List<ChargeInstance> charges = costComponentsForRepaymentPeriod.stream().map(x -> new ChargeInstance(
+            designatorToAccountIdentifierMapper.mapOrThrow(x.getKey().getFromAccountDesignator()),
+            designatorToAccountIdentifierMapper.mapOrThrow(x.getKey().getToAccountDesignator()),
+            x.getValue().getAmount())).collect(Collectors.toList());
+
+    accountingAdapter.bookCharges(charges,
+            command.getCommand().getNote(),
+            productIdentifier + "." + caseIdentifier + "." + Action.APPROVE.name(),
+            Action.APPROVE.getTransactionType());
+
+    //Only move to new state if book charges command was accepted.
+    updateCaseState(dataContextOfAction.getCustomerCase(), Case.State.APPROVED);
+
+    return new IndividualLoanCommandEvent(productIdentifier, caseIdentifier, Action.APPROVE.name());
   }
 
   @Transactional
   @CommandHandler(logStart = CommandLogLevel.INFO, logFinish = CommandLogLevel.INFO)
   @EventEmitter(selectorName = EventConstants.SELECTOR_NAME, selectorValue = IndividualLoanEventConstants.DISBURSE_INDIVIDUALLOAN_CASE)
   public IndividualLoanCommandEvent process(final DisburseCommand command) {
-    final CaseEntity customerCase = getCaseOrThrow(command.getProductIdentifier(), command.getCaseIdentifier());
-    checkActionCanBeExecuted(Case.State.valueOf(customerCase.getCurrentState()), Action.DISBURSE);
-    updateCaseState(customerCase, Case.State.ACTIVE);
+    final String productIdentifier = command.getProductIdentifier();
+    final String caseIdentifier = command.getCaseIdentifier();
+    final DataContextOfAction dataContextOfAction = costComponentService.checkedGetDataContext(productIdentifier, caseIdentifier, command.getCommand().getOneTimeAccountAssignments());
+    checkActionCanBeExecuted(Case.State.valueOf(dataContextOfAction.getCustomerCase().getCurrentState()), Action.DISBURSE);
+    updateCaseState(dataContextOfAction.getCustomerCase(), Case.State.ACTIVE);
     return new IndividualLoanCommandEvent(command.getProductIdentifier(), command.getCaseIdentifier(), "x");
   }
 
@@ -170,9 +173,11 @@
   @CommandHandler(logStart = CommandLogLevel.INFO, logFinish = CommandLogLevel.INFO)
   @EventEmitter(selectorName = EventConstants.SELECTOR_NAME, selectorValue = IndividualLoanEventConstants.ACCEPT_PAYMENT_INDIVIDUALLOAN_CASE)
   public IndividualLoanCommandEvent process(final AcceptPaymentCommand command) {
-    final CaseEntity customerCase = getCaseOrThrow(command.getProductIdentifier(), command.getCaseIdentifier());
-    checkActionCanBeExecuted(Case.State.valueOf(customerCase.getCurrentState()), Action.ACCEPT_PAYMENT);
-    updateCaseState(customerCase, Case.State.ACTIVE);
+    final String productIdentifier = command.getProductIdentifier();
+    final String caseIdentifier = command.getCaseIdentifier();
+    final DataContextOfAction dataContextOfAction = costComponentService.checkedGetDataContext(productIdentifier, caseIdentifier, command.getCommand().getOneTimeAccountAssignments());
+    checkActionCanBeExecuted(Case.State.valueOf(dataContextOfAction.getCustomerCase().getCurrentState()), Action.ACCEPT_PAYMENT);
+    updateCaseState(dataContextOfAction.getCustomerCase(), Case.State.ACTIVE);
     return new IndividualLoanCommandEvent(command.getProductIdentifier(), command.getCaseIdentifier(), "x");
   }
 
@@ -180,9 +185,11 @@
   @CommandHandler(logStart = CommandLogLevel.INFO, logFinish = CommandLogLevel.INFO)
   @EventEmitter(selectorName = EventConstants.SELECTOR_NAME, selectorValue = IndividualLoanEventConstants.WRITE_OFF_INDIVIDUALLOAN_CASE)
   public IndividualLoanCommandEvent process(final WriteOffCommand command) {
-    final CaseEntity customerCase = getCaseOrThrow(command.getProductIdentifier(), command.getCaseIdentifier());
-    checkActionCanBeExecuted(Case.State.valueOf(customerCase.getCurrentState()), Action.WRITE_OFF);
-    updateCaseState(customerCase, Case.State.CLOSED);
+    final String productIdentifier = command.getProductIdentifier();
+    final String caseIdentifier = command.getCaseIdentifier();
+    final DataContextOfAction dataContextOfAction = costComponentService.checkedGetDataContext(productIdentifier, caseIdentifier, command.getCommand().getOneTimeAccountAssignments());
+    checkActionCanBeExecuted(Case.State.valueOf(dataContextOfAction.getCustomerCase().getCurrentState()), Action.WRITE_OFF);
+    updateCaseState(dataContextOfAction.getCustomerCase(), Case.State.CLOSED);
     return new IndividualLoanCommandEvent(command.getProductIdentifier(), command.getCaseIdentifier(), "x");
   }
 
@@ -190,9 +197,11 @@
   @CommandHandler(logStart = CommandLogLevel.INFO, logFinish = CommandLogLevel.INFO)
   @EventEmitter(selectorName = EventConstants.SELECTOR_NAME, selectorValue = IndividualLoanEventConstants.CLOSE_INDIVIDUALLOAN_CASE)
   public IndividualLoanCommandEvent process(final CloseCommand command) {
-    final CaseEntity customerCase = getCaseOrThrow(command.getProductIdentifier(), command.getCaseIdentifier());
-    checkActionCanBeExecuted(Case.State.valueOf(customerCase.getCurrentState()), Action.CLOSE);
-    updateCaseState(customerCase, Case.State.CLOSED);
+    final String productIdentifier = command.getProductIdentifier();
+    final String caseIdentifier = command.getCaseIdentifier();
+    final DataContextOfAction dataContextOfAction = costComponentService.checkedGetDataContext(productIdentifier, caseIdentifier, command.getCommand().getOneTimeAccountAssignments());
+    checkActionCanBeExecuted(Case.State.valueOf(dataContextOfAction.getCustomerCase().getCurrentState()), Action.CLOSE);
+    updateCaseState(dataContextOfAction.getCustomerCase(), Case.State.CLOSED);
     return new IndividualLoanCommandEvent(command.getProductIdentifier(), command.getCaseIdentifier(), "x");
   }
 
@@ -200,20 +209,16 @@
   @CommandHandler(logStart = CommandLogLevel.INFO, logFinish = CommandLogLevel.INFO)
   @EventEmitter(selectorName = EventConstants.SELECTOR_NAME, selectorValue = IndividualLoanEventConstants.RECOVER_INDIVIDUALLOAN_CASE)
   public IndividualLoanCommandEvent process(final RecoverCommand command) {
-    final CaseEntity customerCase = getCaseOrThrow(command.getProductIdentifier(), command.getCaseIdentifier());
-    checkActionCanBeExecuted(Case.State.valueOf(customerCase.getCurrentState()), Action.RECOVER);
-    updateCaseState(customerCase, Case.State.CLOSED);
+    final String productIdentifier = command.getProductIdentifier();
+    final String caseIdentifier = command.getCaseIdentifier();
+    final DataContextOfAction dataContextOfAction = costComponentService.checkedGetDataContext(productIdentifier, caseIdentifier, command.getCommand().getOneTimeAccountAssignments());
+    checkActionCanBeExecuted(Case.State.valueOf(dataContextOfAction.getCustomerCase().getCurrentState()), Action.RECOVER);
+    updateCaseState(dataContextOfAction.getCustomerCase(), Case.State.CLOSED);
     return new IndividualLoanCommandEvent(command.getProductIdentifier(), command.getCaseIdentifier(), "x");
   }
 
-  private CaseEntity getCaseOrThrow(final String productIdentifier, final String caseIdentifier) {
-    return caseRepository.findByProductIdentifierAndIdentifier(productIdentifier, caseIdentifier)
-            .orElseThrow(() -> ServiceException.notFound("Case not found ''{0}.{1}''.", productIdentifier, caseIdentifier));
-  }
-
-  private ProductEntity getProductOrThrow(final String productIdentifier) {
-    return productRepository.findByIdentifier(productIdentifier)
-            .orElseThrow(() -> ServiceException.notFound("Product not found ''{0}''.", productIdentifier));
+  private static LocalDate today() {
+    return LocalDate.now(ZoneId.of("UTC"));
   }
 
   private void checkActionCanBeExecuted(final Case.State state, final Action action) {
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
new file mode 100644
index 0000000..be997ee
--- /dev/null
+++ b/service/src/main/java/io/mifos/individuallending/internal/service/CostComponentService.java
@@ -0,0 +1,107 @@
+/*
+ * 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.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.workflow.Action;
+import io.mifos.individuallending.internal.mapper.CaseParametersMapper;
+import io.mifos.individuallending.internal.repository.CaseParametersRepository;
+import io.mifos.portfolio.api.v1.domain.AccountAssignment;
+import io.mifos.portfolio.api.v1.domain.Case;
+import io.mifos.portfolio.api.v1.domain.CostComponent;
+import io.mifos.portfolio.service.internal.repository.CaseEntity;
+import io.mifos.portfolio.service.internal.repository.CaseRepository;
+import io.mifos.portfolio.service.internal.repository.ProductEntity;
+import io.mifos.portfolio.service.internal.repository.ProductRepository;
+import io.mifos.portfolio.service.internal.util.AccountingAdapter;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.math.BigDecimal;
+import java.time.LocalDate;
+import java.time.ZoneId;
+import java.util.Collections;
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * @author Myrle Krantz
+ */
+@Service
+public class CostComponentService {
+
+  private final ProductRepository productRepository;
+  private final CaseRepository caseRepository;
+  private final CaseParametersRepository caseParametersRepository;
+  private final IndividualLoanService individualLoanService;
+  private final AccountingAdapter accountingAdapter;
+
+  @Autowired
+  public CostComponentService(
+          final ProductRepository productRepository,
+          final CaseRepository caseRepository,
+          final CaseParametersRepository caseParametersRepository,
+          final IndividualLoanService individualLoanService,
+          final AccountingAdapter accountingAdapter) {
+    this.productRepository = productRepository;
+    this.caseRepository = caseRepository;
+    this.caseParametersRepository = caseParametersRepository;
+    this.individualLoanService = individualLoanService;
+    this.accountingAdapter = accountingAdapter;
+  }
+
+  public DataContextOfAction checkedGetDataContext(
+          final String productIdentifier,
+          final String caseIdentifier,
+          final List<AccountAssignment> oneTimeAccountAssignments) {
+
+    final ProductEntity product =
+            productRepository.findByIdentifier(productIdentifier)
+                    .orElseThrow(() -> ServiceException.notFound("Product not found ''{0}''.", productIdentifier));
+    final CaseEntity customerCase =
+            caseRepository.findByProductIdentifierAndIdentifier(productIdentifier, caseIdentifier)
+                    .orElseThrow(() -> ServiceException.notFound("Case not found ''{0}.{1}''.", productIdentifier, caseIdentifier));
+
+    final CaseParameters caseParameters =
+            caseParametersRepository.findByCaseId(customerCase.getId())
+                    .map(CaseParametersMapper::mapEntity)
+                    .orElseThrow(() -> ServiceException.notFound(
+                            "Individual loan not found ''{0}.{1}''.",
+                            productIdentifier, caseIdentifier));
+
+    return new DataContextOfAction(product, customerCase, caseParameters, oneTimeAccountAssignments);
+  }
+
+  public List<CostComponent> getCostComponents(final String productIdentifier, final String caseIdentifier, final Action action) {
+    final DataContextOfAction context = checkedGetDataContext(productIdentifier, caseIdentifier, Collections.emptyList());
+    final Case.State caseState = Case.State.valueOf(context.getCustomerCase().getCurrentState());
+    final BigDecimal runningBalance;
+    if (caseState == Case.State.ACTIVE) {
+      final DesignatorToAccountIdentifierMapper mapper = new DesignatorToAccountIdentifierMapper(context);
+      final String customerLoanAccountIdentifier = mapper.mapOrThrow(AccountDesignators.CUSTOMER_LOAN);
+      runningBalance = accountingAdapter.getCurrentBalance(customerLoanAccountIdentifier);
+    }
+    else
+      runningBalance = BigDecimal.ZERO;
+
+    return individualLoanService.getCostComponentsForRepaymentPeriod(productIdentifier, context.getCaseParameters(), runningBalance, action, LocalDate.now(ZoneId.of("UTC")), LocalDate.now(ZoneId.of("UTC")))
+            .stream()
+            .map(x -> new CostComponent(x.getKey().getIdentifier(), x.getValue().getAmount()))
+            .collect(Collectors.toList()); //TODO: initial disbursal date.
+  }
+}
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
new file mode 100644
index 0000000..a51e975
--- /dev/null
+++ b/service/src/main/java/io/mifos/individuallending/internal/service/CostComponentsForRepaymentPeriod.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2017 The Mifos Initiative.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.mifos.individuallending.internal.service;
+
+import io.mifos.portfolio.api.v1.domain.ChargeDefinition;
+import io.mifos.portfolio.api.v1.domain.CostComponent;
+
+import java.math.BigDecimal;
+import java.util.Map;
+import java.util.stream.Stream;
+
+/**
+ * @author Myrle Krantz
+ */
+public class CostComponentsForRepaymentPeriod {
+  final Map<ChargeDefinition, CostComponent> costComponents;
+  final BigDecimal balanceAdjustment;
+
+  CostComponentsForRepaymentPeriod(
+          final Map<ChargeDefinition, CostComponent> costComponents,
+          final BigDecimal balanceAdjustment) {
+    this.costComponents = costComponents;
+    this.balanceAdjustment = balanceAdjustment;
+  }
+
+  public Stream<Map.Entry<ChargeDefinition, CostComponent>> stream() {
+    return costComponents.entrySet().stream();
+  }
+}
diff --git a/service/src/main/java/io/mifos/individuallending/internal/service/DataContextOfAction.java b/service/src/main/java/io/mifos/individuallending/internal/service/DataContextOfAction.java
new file mode 100644
index 0000000..7687b3c
--- /dev/null
+++ b/service/src/main/java/io/mifos/individuallending/internal/service/DataContextOfAction.java
@@ -0,0 +1,59 @@
+/*
+ * 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.caseinstance.CaseParameters;
+import io.mifos.portfolio.api.v1.domain.AccountAssignment;
+import io.mifos.portfolio.service.internal.repository.CaseEntity;
+import io.mifos.portfolio.service.internal.repository.ProductEntity;
+
+import java.util.List;
+
+/**
+ * @author Myrle Krantz
+ */
+public class DataContextOfAction {
+  private final ProductEntity product;
+  private final CaseEntity customerCase;
+  private final CaseParameters caseParameters;
+  private final List<AccountAssignment> oneTimeAccountAssignments;
+
+  DataContextOfAction(final ProductEntity product,
+                      final CaseEntity customerCase,
+                      final CaseParameters caseParameters,
+                      final List<AccountAssignment> oneTimeAccountAssignments) {
+    this.product = product;
+    this.customerCase = customerCase;
+    this.caseParameters = caseParameters;
+    this.oneTimeAccountAssignments = oneTimeAccountAssignments;
+  }
+
+  public ProductEntity getProduct() {
+    return product;
+  }
+
+  public CaseEntity getCustomerCase() {
+    return customerCase;
+  }
+
+  public CaseParameters getCaseParameters() {
+    return caseParameters;
+  }
+
+  public List<AccountAssignment> getOneTimeAccountAssignments() {
+    return oneTimeAccountAssignments;
+  }
+}
diff --git a/service/src/main/java/io/mifos/individuallending/internal/service/DesignatorToAccountIdentifierMapper.java b/service/src/main/java/io/mifos/individuallending/internal/service/DesignatorToAccountIdentifierMapper.java
new file mode 100644
index 0000000..e082f9b
--- /dev/null
+++ b/service/src/main/java/io/mifos/individuallending/internal/service/DesignatorToAccountIdentifierMapper.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2017 The Mifos Initiative.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.mifos.individuallending.internal.service;
+
+import io.mifos.core.lang.ServiceException;
+import io.mifos.individuallending.api.v1.domain.product.AccountDesignators;
+import io.mifos.portfolio.api.v1.domain.AccountAssignment;
+import io.mifos.portfolio.service.internal.mapper.CaseMapper;
+import io.mifos.portfolio.service.internal.mapper.ProductMapper;
+import io.mifos.portfolio.service.internal.repository.CaseAccountAssignmentEntity;
+import io.mifos.portfolio.service.internal.repository.ProductAccountAssignmentEntity;
+
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Stream;
+
+/**
+ * @author Myrle Krantz
+ */
+public class DesignatorToAccountIdentifierMapper {
+  private final Set<ProductAccountAssignmentEntity> productAccountAssignments;
+  private final Set<CaseAccountAssignmentEntity> caseAccountAssignments;
+  private final List<AccountAssignment> oneTimeAccountAssignments;
+
+  public DesignatorToAccountIdentifierMapper(final DataContextOfAction dataContextOfAction) {
+    this.productAccountAssignments = dataContextOfAction.getProduct().getAccountAssignments();
+    this.caseAccountAssignments = dataContextOfAction.getCustomerCase().getAccountAssignments();
+    this.oneTimeAccountAssignments = dataContextOfAction.getOneTimeAccountAssignments();
+  }
+
+  private Stream<AccountAssignment> allAccountAssignmentsAsStream() {
+    return Stream.concat(oneTimeAccountAssignments.stream(), fixedAccountAssignmentsAsStream());
+  }
+
+  private Stream<AccountAssignment> fixedAccountAssignmentsAsStream() {
+    return Stream.concat(caseAccountAssignments.stream().map(CaseMapper::mapAccountAssignmentEntity),
+            productAccountAssignments.stream().map(ProductMapper::mapAccountAssignmentEntity));
+  }
+
+  public String mapOrThrow(final String accountDesignator) {
+    return allAccountAssignmentsAsStream()
+            .filter(x -> x.getDesignator().equals(accountDesignator))
+            .findFirst()
+            .map(AccountAssignment::getAccountIdentifier)
+            .orElseThrow(() -> ServiceException.badRequest("A required account designator was not set ''{0}''.", accountDesignator));
+  }
+
+  public Stream<AccountAssignment> getLedgersNeedingAccounts() {
+    return fixedAccountAssignmentsAsStream()
+            .filter(x -> !x.getDesignator().equals(AccountDesignators.ENTRY))
+            .filter(x -> (x.getAccountIdentifier() == null) && (x.getLedgerIdentifier() != null));
+  }
+}
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 e3ec87b..4167b81 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
@@ -15,19 +15,19 @@
  */
 package io.mifos.individuallending.internal.service;
 
-import io.mifos.portfolio.api.v1.domain.CostComponent;
-import io.mifos.portfolio.service.internal.service.ChargeDefinitionService;
-import io.mifos.portfolio.service.internal.service.ProductService;
 import io.mifos.core.lang.DateConverter;
 import io.mifos.individuallending.api.v1.domain.caseinstance.CaseParameters;
 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.AccountDesignators;
+import io.mifos.individuallending.api.v1.domain.product.ChargeIdentifiers;
 import io.mifos.individuallending.api.v1.domain.workflow.Action;
 import io.mifos.portfolio.api.v1.domain.ChargeDefinition;
+import io.mifos.portfolio.api.v1.domain.CostComponent;
 import io.mifos.portfolio.api.v1.domain.Product;
-import io.mifos.portfolio.service.internal.util.ChargeInstance;
+import io.mifos.portfolio.service.internal.service.ChargeDefinitionService;
+import io.mifos.portfolio.service.internal.service.ProductService;
 import org.javamoney.calc.common.Rate;
 import org.javamoney.moneta.Money;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -38,13 +38,13 @@
 import java.math.BigDecimal;
 import java.time.LocalDate;
 import java.util.*;
-import java.util.function.Function;
+import java.util.function.BiFunction;
 import java.util.stream.Collector;
 import java.util.stream.Collectors;
 import java.util.stream.Stream;
 
-import static io.mifos.individuallending.api.v1.domain.product.AccountDesignators.PENDING_DISBURSAL;
 import static io.mifos.individuallending.api.v1.domain.product.AccountDesignators.CUSTOMER_LOAN;
+import static io.mifos.individuallending.api.v1.domain.product.AccountDesignators.PENDING_DISBURSAL;
 import static io.mifos.individuallending.api.v1.domain.product.ChargeIdentifiers.PAYMENT_ID;
 import static io.mifos.individuallending.api.v1.domain.product.ChargeIdentifiers.PAYMENT_NAME;
 import static io.mifos.portfolio.api.v1.domain.ChargeDefinition.ChargeMethod.FIXED;
@@ -113,27 +113,23 @@
     return ret;
   }
 
-  public List<ChargeInstance> getChargeInstances(final String productIdentifier,
-                                                 final CaseParameters caseParameters,
-                                                 final BigDecimal currentBalance,
-                                                 final Action action,
-                                                 final LocalDate initialDisbursalDate,
-                                                 final LocalDate forDate) {
+  public CostComponentsForRepaymentPeriod getCostComponentsForRepaymentPeriod(final String productIdentifier,
+                                                                              final CaseParameters caseParameters,
+                                                                              final BigDecimal runningBalance,
+                                                                              final Action action,
+                                                                              final LocalDate initialDisbursalDate,
+                                                                              final LocalDate forDate) {
     final Product product = productService.findByIdentifier(productIdentifier)
             .orElseThrow(() -> new IllegalArgumentException("Non-existent product identifier."));
     final int minorCurrencyUnitDigits = product.getMinorCurrencyUnitDigits();
     final List<ScheduledAction> scheduledActions = scheduledActionService.getScheduledActions(initialDisbursalDate, caseParameters, action, forDate);
-    final List<ScheduledCharge> scheduledCharges = getScheduledCharges(productIdentifier, minorCurrencyUnitDigits, currentBalance, scheduledActions);
+    final List<ScheduledCharge> scheduledCharges = getScheduledCharges(productIdentifier, minorCurrencyUnitDigits, runningBalance, scheduledActions);
 
-    final CostComponentsForRepaymentPeriod costComponentsForScheduledCharges = getCostComponentsForScheduledCharges(scheduledCharges, currentBalance, minorCurrencyUnitDigits);
-
-    return costComponentsForScheduledCharges.costComponents.entrySet().stream()
-            .map(IndividualLoanService::mapToChargeInstance)
-            .collect(Collectors.toList());
-  }
-
-  private static ChargeInstance mapToChargeInstance(final Map.Entry<ChargeDefinition, CostComponent> x) {
-    return new ChargeInstance(x.getKey().getFromAccountDesignator(), x.getKey().getToAccountDesignator(), x.getValue().getAmount());
+    return getCostComponentsForScheduledCharges(
+            scheduledCharges,
+            caseParameters.getMaximumBalance(),
+            runningBalance,
+            minorCurrencyUnitDigits);
   }
 
   private static ChargeName chargeNameFromChargeDefinition(final ScheduledCharge scheduledCharge) {
@@ -221,7 +217,7 @@
     {
       final SortedSet<ScheduledCharge> scheduledChargesInPeriod = orderedScheduledChargesGroupedByPeriod.get(repaymentPeriod);
       final CostComponentsForRepaymentPeriod costComponentsForRepaymentPeriod =
-              getCostComponentsForScheduledCharges(scheduledChargesInPeriod, balance, minorCurrencyUnitDigits);
+              getCostComponentsForScheduledCharges(scheduledChargesInPeriod, balance, balance, minorCurrencyUnitDigits);
 
       final PlannedPayment plannedPayment = new PlannedPayment();
       plannedPayment.setCostComponents(costComponentsForRepaymentPeriod.costComponents.values().stream().collect(Collectors.toList()));
@@ -243,21 +239,10 @@
     return plannedPayments;
   }
 
-  private static class CostComponentsForRepaymentPeriod {
-    final Map<ChargeDefinition, CostComponent> costComponents;
-    final BigDecimal balanceAdjustment;
-
-    private CostComponentsForRepaymentPeriod(
-            final Map<ChargeDefinition, CostComponent> costComponents,
-            final BigDecimal balanceAdjustment) {
-      this.costComponents = costComponents;
-      this.balanceAdjustment = balanceAdjustment;
-    }
-  }
-
   static private CostComponentsForRepaymentPeriod getCostComponentsForScheduledCharges(
           final Collection<ScheduledCharge> scheduledCharges,
-          final BigDecimal balance,
+          final BigDecimal maximumBalance,
+          final BigDecimal runningBalance,
           final int minorCurrencyUnitDigits) {
     BigDecimal balanceAdjustment = BigDecimal.ZERO;
 
@@ -274,7 +259,7 @@
                       });
 
       final BigDecimal chargeAmount = howToApplyScheduledChargeToBalance(scheduledCharge, 8)
-              .apply(balance)
+              .apply(maximumBalance, runningBalance)
               .setScale(minorCurrencyUnitDigits, BigDecimal.ROUND_HALF_EVEN);
       if (chargeDefinitionTouchesCustomerLoanAccount(scheduledCharge.getChargeDefinition()))
         balanceAdjustment = balanceAdjustment.add(chargeAmount);
@@ -293,18 +278,25 @@
             (chargeDefinition.getAccrualAccountDesignator() != null && chargeDefinition.getAccrualAccountDesignator().equals(AccountDesignators.CUSTOMER_LOAN));
   }
 
-  private static Function<BigDecimal, BigDecimal> howToApplyScheduledChargeToBalance(
+  private static BiFunction<BigDecimal, BigDecimal, BigDecimal> howToApplyScheduledChargeToBalance(
           final ScheduledCharge scheduledCharge,
           final int precision)
   {
+
     switch (scheduledCharge.getChargeDefinition().getChargeMethod())
     {
       case FIXED:
-        return (x) -> scheduledCharge.getChargeDefinition().getAmount();
-      case PROPORTIONAL:
-        return (x) -> PeriodChargeCalculator.chargeAmountPerPeriod(scheduledCharge, precision).multiply(x);
+        return (maximumBalance, runningBalance) -> scheduledCharge.getChargeDefinition().getAmount();
+      case PROPORTIONAL: {
+        if (scheduledCharge.getChargeDefinition().getProportionalTo().equals(ChargeIdentifiers.RUNNING_BALANCE_DESIGNATOR))
+          return (maximumBalance, runningBalance) -> PeriodChargeCalculator.chargeAmountPerPeriod(scheduledCharge, precision).multiply(runningBalance);
+        else if (scheduledCharge.getChargeDefinition().getProportionalTo().equals(ChargeIdentifiers.MAXIMUM_BALANCE_DESIGNATOR))
+          return (maximumBalance, runningBalance) -> PeriodChargeCalculator.chargeAmountPerPeriod(scheduledCharge, precision).multiply(maximumBalance);
+        else //TODO: correctly implement charges which are proportionate to other charges.
+          return (maximumBalance, runningBalance) -> PeriodChargeCalculator.chargeAmountPerPeriod(scheduledCharge, precision).multiply(maximumBalance);
+      }
       default:
-        return (x) -> BigDecimal.ZERO;
+        return (maximumBalance, runningBalance) -> BigDecimal.ZERO;
     }
   }
 
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 1a48fec..f4bff53 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
@@ -19,6 +19,8 @@
 import io.mifos.portfolio.service.internal.repository.ChargeDefinitionEntity;
 import io.mifos.portfolio.service.internal.repository.ProductEntity;
 
+import static io.mifos.individuallending.api.v1.domain.product.ChargeIdentifiers.*;
+
 /**
  * @author Myrle Krantz
  */
@@ -35,6 +37,7 @@
     ret.setChargeAction(chargeDefinition.getChargeAction());
     ret.setAmount(chargeDefinition.getAmount());
     ret.setChargeMethod(chargeDefinition.getChargeMethod());
+    ret.setProportionalTo(chargeDefinition.getProportionalTo());
     ret.setForCycleSizeUnit(chargeDefinition.getForCycleSizeUnit());
     ret.setFromAccountDesignator(chargeDefinition.getFromAccountDesignator());
     ret.setAccrualAccountDesignator(chargeDefinition.getAccrualAccountDesignator());
@@ -53,6 +56,7 @@
     ret.setChargeAction(from.getChargeAction());
     ret.setAmount(from.getAmount());
     ret.setChargeMethod(from.getChargeMethod());
+    ret.setProportionalTo(proportionalToLegacyMapper(from, from.getChargeMethod(), from.getIdentifier()));
     ret.setForCycleSizeUnit(from.getForCycleSizeUnit());
     ret.setFromAccountDesignator(from.getFromAccountDesignator());
     ret.setAccrualAccountDesignator(from.getAccrualAccountDesignator());
@@ -60,4 +64,22 @@
 
     return ret;
   }
+
+  private static String proportionalToLegacyMapper(final ChargeDefinitionEntity from,
+                                                   final ChargeDefinition.ChargeMethod chargeMethod,
+                                                   final String identifier) {
+    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 PAYMENT_ID;
+    else
+      return RUNNING_BALANCE_DESIGNATOR;
+  }
 }
diff --git a/service/src/main/java/io/mifos/portfolio/service/internal/repository/ChargeDefinitionEntity.java b/service/src/main/java/io/mifos/portfolio/service/internal/repository/ChargeDefinitionEntity.java
index 92c436c..3f0eab0 100644
--- a/service/src/main/java/io/mifos/portfolio/service/internal/repository/ChargeDefinitionEntity.java
+++ b/service/src/main/java/io/mifos/portfolio/service/internal/repository/ChargeDefinitionEntity.java
@@ -20,6 +20,7 @@
 import javax.persistence.*;
 import java.math.BigDecimal;
 import java.time.temporal.ChronoUnit;
+import java.util.Objects;
 
 /**
  * @author Myrle Krantz
@@ -59,6 +60,9 @@
   @Column(name = "charge_method")
   private ChargeDefinition.ChargeMethod chargeMethod;
 
+  @Column(name = "proportional_to")
+  private String proportionalTo;
+
   @Column(name = "from_account_designator")
   private String fromAccountDesignator;
 
@@ -147,6 +151,14 @@
     this.chargeMethod = chargeMethod;
   }
 
+  public String getProportionalTo() {
+    return proportionalTo;
+  }
+
+  public void setProportionalTo(String proportionalTo) {
+    this.proportionalTo = proportionalTo;
+  }
+
   public String getFromAccountDesignator() {
     return fromAccountDesignator;
   }
@@ -178,4 +190,18 @@
   public void setForCycleSizeUnit(ChronoUnit forCycleSizeUnit) {
     this.forCycleSizeUnit = forCycleSizeUnit;
   }
+
+  @Override
+  public boolean equals(Object o) {
+    if (this == o) return true;
+    if (o == null || getClass() != o.getClass()) return false;
+    ChargeDefinitionEntity that = (ChargeDefinitionEntity) o;
+    return Objects.equals(identifier, that.identifier) &&
+            Objects.equals(product, that.product);
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hash(identifier, product);
+  }
 }
diff --git a/service/src/main/java/io/mifos/portfolio/service/internal/service/CaseService.java b/service/src/main/java/io/mifos/portfolio/service/internal/service/CaseService.java
index 11368c1..a6b96e4 100644
--- a/service/src/main/java/io/mifos/portfolio/service/internal/service/CaseService.java
+++ b/service/src/main/java/io/mifos/portfolio/service/internal/service/CaseService.java
@@ -18,7 +18,6 @@
 import io.mifos.core.lang.ServiceException;
 import io.mifos.portfolio.api.v1.domain.Case;
 import io.mifos.portfolio.api.v1.domain.CasePage;
-import io.mifos.portfolio.api.v1.domain.ChargeDefinition;
 import io.mifos.portfolio.api.v1.domain.CostComponent;
 import io.mifos.portfolio.service.internal.mapper.CaseMapper;
 import io.mifos.portfolio.service.internal.pattern.PatternFactoryRegistry;
@@ -35,7 +34,10 @@
 import org.springframework.data.domain.Sort;
 import org.springframework.stereotype.Service;
 
-import java.util.*;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Optional;
+import java.util.Set;
 import java.util.stream.Collectors;
 import java.util.stream.Stream;
 
@@ -47,18 +49,15 @@
   private final PatternFactoryRegistry patternFactoryRegistry;
   private final ProductRepository productRepository;
   private final CaseRepository caseRepository;
-  private final ChargeDefinitionService chargeDefinitionService;
 
   @Autowired
   public CaseService(
           final PatternFactoryRegistry patternFactoryRegistry,
           final ProductRepository productRepository,
-          final CaseRepository caseRepository,
-          final ChargeDefinitionService chargeDefinitionService) {
+          final CaseRepository caseRepository) {
     this.patternFactoryRegistry = patternFactoryRegistry;
     this.productRepository = productRepository;
     this.caseRepository = caseRepository;
-    this.chargeDefinitionService = chargeDefinitionService;
   }
 
   public CasePage findAllEntities(final String productIdentifier,
@@ -127,14 +126,7 @@
   public List<CostComponent> getActionCostComponentsForCase(final String productIdentifier,
                                                             final String caseIdentifier,
                                                             final String actionIdentifier) {
-    final Map<String, List<ChargeDefinition>> chargeDefinitions = chargeDefinitionService.getChargeDefinitionsMappedByChargeAction(productIdentifier);
-    final List<ChargeDefinition> chargeDefinitionsForAction = chargeDefinitions.get(actionIdentifier);
-    return chargeDefinitionsForAction.stream().map(x -> {
-      final CostComponent ret = new CostComponent();
-      ret.setChargeIdentifier(x.getIdentifier());
-      ret.setAmount(x.getAmount()); //TODO: This is too simplistic.  Will only work for fixed charges and no accrual.
-      return ret;
-    }).collect(Collectors.toList());
-
+    return getPatternFactoryOrThrow(productIdentifier)
+            .getCostComponentsForAction(productIdentifier, caseIdentifier, actionIdentifier);
   }
 }
\ No newline at end of file
diff --git a/service/src/main/java/io/mifos/portfolio/service/internal/util/AccountingAdapter.java b/service/src/main/java/io/mifos/portfolio/service/internal/util/AccountingAdapter.java
index f802c51..f88a277 100644
--- a/service/src/main/java/io/mifos/portfolio/service/internal/util/AccountingAdapter.java
+++ b/service/src/main/java/io/mifos/portfolio/service/internal/util/AccountingAdapter.java
@@ -18,10 +18,7 @@
 import io.mifos.accounting.api.v1.client.AccountNotFoundException;
 import io.mifos.accounting.api.v1.client.LedgerManager;
 import io.mifos.accounting.api.v1.client.LedgerNotFoundException;
-import io.mifos.accounting.api.v1.domain.Account;
-import io.mifos.accounting.api.v1.domain.Creditor;
-import io.mifos.accounting.api.v1.domain.Debtor;
-import io.mifos.accounting.api.v1.domain.JournalEntry;
+import io.mifos.accounting.api.v1.domain.*;
 import io.mifos.core.api.util.UserContextHolder;
 import io.mifos.core.lang.DateConverter;
 import io.mifos.portfolio.api.v1.domain.AccountAssignment;
@@ -109,6 +106,24 @@
     return BigDecimal.valueOf(account.getBalance());
   }
 
+  public String createAccountForLedgerAssignment(final String customerIdentifier, final AccountAssignment ledgerAssignment) {
+    final Ledger ledger = ledgerManager.findLedger(ledgerAssignment.getLedgerIdentifier());
+    final AccountPage accountsOfLedger = ledgerManager.fetchAccountsOfLedger(ledger.getIdentifier(), null, null, null, null);
+
+    final Account generatedAccount = new Account();
+    generatedAccount.setBalance(0.0);
+    generatedAccount.setType(ledger.getType());
+    generatedAccount.setState(Account.State.OPEN.name());
+    final String accountNumber = customerIdentifier + "." + ledgerAssignment.getDesignator()
+            + "." + String.format("%05d", accountsOfLedger.getTotalElements() + 1);
+    generatedAccount.setIdentifier(accountNumber);
+    generatedAccount.setLedger(ledger.getIdentifier());
+    generatedAccount.setName(accountNumber);
+    ledgerManager.createAccount(generatedAccount);
+
+    return accountNumber;
+  }
+
 
   public static boolean accountAssignmentsCoverChargeDefinitions(
           final Set<AccountAssignment> accountAssignments,
diff --git a/service/src/main/java/io/mifos/products/spi/PatternFactory.java b/service/src/main/java/io/mifos/products/spi/PatternFactory.java
index 36bbb81..74a0f16 100644
--- a/service/src/main/java/io/mifos/products/spi/PatternFactory.java
+++ b/service/src/main/java/io/mifos/products/spi/PatternFactory.java
@@ -18,6 +18,7 @@
 
 import io.mifos.portfolio.api.v1.domain.Case;
 import io.mifos.portfolio.api.v1.domain.ChargeDefinition;
+import io.mifos.portfolio.api.v1.domain.CostComponent;
 import io.mifos.portfolio.api.v1.domain.Pattern;
 
 import java.util.List;
@@ -33,6 +34,7 @@
   void persistParameters(Long caseId, String parameters);
   void changeParameters(Long caseId, String parameters);
   Optional<String> getParameters(Long caseId);
-  Set<String> getNextActionsForState(final Case.State state);
+  Set<String> getNextActionsForState(Case.State state);
+  List<CostComponent> getCostComponentsForAction(String productIdentifier, String caseIdentifier, String actionIdentifier);
   ProductCommandDispatcher getIndividualLendingCommandDispatcher();
 }
diff --git a/service/src/main/resources/db/migrations/mariadb/V2__in_motion.sql b/service/src/main/resources/db/migrations/mariadb/V2__in_motion.sql
new file mode 100644
index 0000000..f094533
--- /dev/null
+++ b/service/src/main/resources/db/migrations/mariadb/V2__in_motion.sql
@@ -0,0 +1,19 @@
+--
+-- Copyright 2017 The Mifos Initiative.
+--
+-- Licensed under the Apache License, Version 2.0 (the "License");
+-- you may not use this file except in compliance with the License.
+-- You may obtain a copy of the License at
+--
+--    http://www.apache.org/licenses/LICENSE-2.0
+--
+-- Unless required by applicable law or agreed to in writing, software
+-- distributed under the License is distributed on an "AS IS" BASIS,
+-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+-- See the License for the specific language governing permissions and
+-- limitations under the License.
+--
+
+# noinspection SqlNoDataSourceInspectionForFile
+
+ALTER TABLE bastet_p_chrg_defs ADD COLUMN proportional_to VARCHAR(32) NULL;
\ No newline at end of file
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 08f2c5c..70b5b34 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
@@ -40,7 +40,7 @@
 import java.util.stream.Collectors;
 import java.util.stream.Stream;
 
-import static io.mifos.individuallending.api.v1.domain.product.ChargeIdentifiers.PROCESSING_FEE_ID;
+import static io.mifos.individuallending.api.v1.domain.product.ChargeIdentifiers.*;
 
 /**
  * @author Myrle Krantz
@@ -98,6 +98,9 @@
     private Map<String, List<ChargeDefinition>> chargeDefinitionsMappedByAction;
     private Set<String> expectedChargeIdentifiers = new HashSet<>(Arrays.asList(ChargeIdentifiers.INTEREST_ID, ChargeIdentifiers.PAYMENT_ID));
     private Map<ActionDatePair, List<ChargeInstance>> chargeInstancesForActions = new HashMap<>();
+    //This is an abuse of the ChargeInstance since everywhere else it's intended to contain account identifiers and not
+    //account designators.  Don't copy the code around charge instances in this test without thinking about what you're
+    //doing carefully first.
 
     TestCase(final String description) {
       this.description = description;
@@ -175,7 +178,12 @@
     final Map<String, List<ChargeDefinition>> chargeDefinitionsMappedByAction = new HashMap<>();
     chargeDefinitionsMappedByAction.put(Action.APPLY_INTEREST.name(), getInterestChargeDefinition(0.01, ChronoUnit.YEARS));
     chargeDefinitionsMappedByAction.put(Action.OPEN.name(),
-            getFixedSingleChargeDefinition(10.0, Action.OPEN, PROCESSING_FEE_ID, AccountDesignators.PROCESSING_FEE_INCOME));
+            Collections.singletonList(
+                    getFixedSingleChargeDefinition(10.0, Action.OPEN, PROCESSING_FEE_ID, AccountDesignators.PROCESSING_FEE_INCOME)));
+    chargeDefinitionsMappedByAction.put(Action.APPROVE.name(),
+            Arrays.asList(
+                    getFixedSingleChargeDefinition(100.0, Action.APPROVE, LOAN_ORIGINATION_FEE_ID, AccountDesignators.ORIGINATION_FEE_INCOME),
+                    getProportionalSingleChargeDefinition(1.0, Action.APPROVE, LOAN_FUNDS_ALLOCATION_ID, AccountDesignators.LOAN_FUNDS_SOURCE, AccountDesignators.PENDING_DISBURSAL)));
 
     return new TestCase("simpleCase")
             .minorCurrencyUnitDigits(2)
@@ -183,11 +191,24 @@
             .initialDisbursementDate(initialDisbursementDate)
             .chargeDefinitionsMappedByAction(chargeDefinitionsMappedByAction)
             .expectAdditionalChargeIdentifier(PROCESSING_FEE_ID)
+            .expectAdditionalChargeIdentifier(LOAN_FUNDS_ALLOCATION_ID)
+            .expectAdditionalChargeIdentifier(LOAN_ORIGINATION_FEE_ID)
             .expectChargeInstancesForActionDatePair(Action.OPEN, initialDisbursementDate,
                     Collections.singletonList(new ChargeInstance(
                             AccountDesignators.ENTRY,
                             AccountDesignators.PROCESSING_FEE_INCOME,
-                            BigDecimal.valueOf(10).setScale(2, BigDecimal.ROUND_UNNECESSARY))));
+                            BigDecimal.valueOf(10).setScale(2, BigDecimal.ROUND_UNNECESSARY))))
+            .expectChargeInstancesForActionDatePair(Action.APPROVE, initialDisbursementDate,
+                    Arrays.asList(
+                            new ChargeInstance(
+                                    AccountDesignators.ENTRY,
+                                    AccountDesignators.ORIGINATION_FEE_INCOME,
+                                    BigDecimal.valueOf(100.0).setScale(2, BigDecimal.ROUND_UNNECESSARY)),
+                            new ChargeInstance(
+                                    AccountDesignators.LOAN_FUNDS_SOURCE,
+                                    AccountDesignators.PENDING_DISBURSAL,
+                                    caseParameters.getMaximumBalance().setScale(2, BigDecimal.ROUND_UNNECESSARY)
+                            )));
   }
 
   private static TestCase yearLoanTestCase()
@@ -217,18 +238,20 @@
     caseParameters.setPaymentCycle(new PaymentCycle(ChronoUnit.WEEKS, 1, 1, 0, 0));
     caseParameters.setMaximumBalance(BigDecimal.valueOf(2000));
 
-    final Map<String, List<ChargeDefinition>> chargeDefinitionsMappedByAction = new HashMap<>();
-    chargeDefinitionsMappedByAction.put(Action.APPLY_INTEREST.name(), getInterestChargeDefinition(0.05, ChronoUnit.YEARS));
-
     final List<ChargeDefinition> defaultLoanCharges = IndividualLendingPatternFactory.defaultIndividualLoanCharges();
-    defaultLoanCharges.forEach(x -> chargeDefinitionsMappedByAction.put(x.getChargeAction(), Collections.singletonList(x)));
+
+    final Map<String, List<ChargeDefinition>> chargeDefinitionsMappedByAction = defaultLoanCharges.stream()
+            .collect(Collectors.groupingBy(ChargeDefinition::getChargeAction,
+                    Collectors.mapping(x -> x, Collectors.toList())));
+
+    chargeDefinitionsMappedByAction.put(Action.APPLY_INTEREST.name(), getInterestChargeDefinition(0.05, ChronoUnit.YEARS));
 
     return new TestCase("chargeDefaultsCase")
             .minorCurrencyUnitDigits(2)
             .caseParameters(caseParameters)
             .initialDisbursementDate(initialDisbursementDate)
             .chargeDefinitionsMappedByAction(chargeDefinitionsMappedByAction)
-            .expectedChargeIdentifiers(new HashSet<>(Arrays.asList(PROCESSING_FEE_ID, ChargeIdentifiers.RETURN_DISBURSEMENT_ID, ChargeIdentifiers.LOAN_ORIGINATION_FEE_ID, ChargeIdentifiers.INTEREST_ID, ChargeIdentifiers.PAYMENT_ID)));
+            .expectedChargeIdentifiers(new HashSet<>(Arrays.asList(PROCESSING_FEE_ID, LOAN_FUNDS_ALLOCATION_ID, RETURN_DISBURSEMENT_ID, LOAN_ORIGINATION_FEE_ID, INTEREST_ID, PAYMENT_ID)));
   }
 
   private static List<ChargeDefinition> getInterestChargeDefinition(final double amount, final ChronoUnit forCycleSizeUnit) {
@@ -238,6 +261,7 @@
     ret.setAccrueAction(Action.APPLY_INTEREST.name());
     ret.setChargeAction(Action.ACCEPT_PAYMENT.name());
     ret.setChargeMethod(ChargeDefinition.ChargeMethod.PROPORTIONAL);
+    ret.setProportionalTo(ChargeIdentifiers.RUNNING_BALANCE_DESIGNATOR);
     ret.setFromAccountDesignator(AccountDesignators.CUSTOMER_LOAN);
     ret.setAccrualAccountDesignator(AccountDesignators.INTEREST_ACCRUAL);
     ret.setToAccountDesignator(AccountDesignators.INTEREST_INCOME);
@@ -245,7 +269,7 @@
     return Collections.singletonList(ret);
   }
 
-  private static List<ChargeDefinition> getFixedSingleChargeDefinition(
+  private static ChargeDefinition getFixedSingleChargeDefinition(
           final double amount,
           final Action action,
           final String chargeIdentifier,
@@ -256,10 +280,30 @@
     ret.setAccrueAction(null);
     ret.setChargeAction(action.name());
     ret.setChargeMethod(ChargeDefinition.ChargeMethod.FIXED);
+    ret.setProportionalTo(null);
     ret.setFromAccountDesignator(AccountDesignators.ENTRY);
     ret.setToAccountDesignator(feeAccountDesignator);
     ret.setForCycleSizeUnit(null);
-    return Collections.singletonList(ret);
+    return ret;
+  }
+
+  private static ChargeDefinition getProportionalSingleChargeDefinition(
+          final double amount,
+          final Action action,
+          final String chargeIdentifier,
+          final String fromAccountDesignator,
+          final String toAccountDesignator) {
+    final ChargeDefinition ret = new ChargeDefinition();
+    ret.setAmount(BigDecimal.valueOf(amount));
+    ret.setIdentifier(chargeIdentifier);
+    ret.setAccrueAction(null);
+    ret.setChargeAction(action.name());
+    ret.setChargeMethod(ChargeDefinition.ChargeMethod.PROPORTIONAL);
+    ret.setProportionalTo(ChargeIdentifiers.MAXIMUM_BALANCE_DESIGNATOR);
+    ret.setFromAccountDesignator(fromAccountDesignator);
+    ret.setToAccountDesignator(toAccountDesignator);
+    ret.setForCycleSizeUnit(null);
+    return ret;
   }
 
   public IndividualLoanServiceTest(final TestCase testCase)
@@ -334,16 +378,20 @@
   }
 
   @Test
-  public void createChargeInstances() {
+  public void getCostComponentsForRepaymentPeriod() {
     testCase.chargeInstancesForActions.entrySet().forEach(entry ->
             Assert.assertEquals(
-                    entry.getValue(),
-                    testSubject.getChargeInstances(
+                    entry.getValue().stream().collect(Collectors.toSet()),
+                    testSubject.getCostComponentsForRepaymentPeriod(
                             testCase.productIdentifier,
                             testCase.caseParameters,
                             testCase.caseParameters.getMaximumBalance(),
                             entry.getKey().getAction(),
-                            testCase.initialDisbursementDate, entry.getKey().getLocalDate())));
+                            testCase.initialDisbursementDate, entry.getKey().getLocalDate())
+                    .stream()
+                    .map(x -> new ChargeInstance(x.getKey().getFromAccountDesignator(), x.getKey().getToAccountDesignator(), x.getValue().getAmount()))
+                    .collect(Collectors.toSet())
+            ));
   }
 
   private double percentDifference(final BigDecimal maxPayment, final BigDecimal minPayment) {