Merge pull request #26 from myrle-krantz/develop

open command processing and scale loosening.
diff --git a/api/src/main/java/io/mifos/individuallending/api/v1/domain/caseinstance/CaseParameters.java b/api/src/main/java/io/mifos/individuallending/api/v1/domain/caseinstance/CaseParameters.java
index 9e5ad0e..5fc2e9c 100644
--- a/api/src/main/java/io/mifos/individuallending/api/v1/domain/caseinstance/CaseParameters.java
+++ b/api/src/main/java/io/mifos/individuallending/api/v1/domain/caseinstance/CaseParameters.java
@@ -31,7 +31,7 @@
  * @author Myrle Krantz
  */
 @SuppressWarnings({"WeakerAccess", "unused"})
-@ScriptAssert(lang = "javascript", script = "_this.maximumBalance !== null && _this.maximumBalance.scale() == 4")
+@ScriptAssert(lang = "javascript", script = "_this.maximumBalance !== null && _this.maximumBalance.scale() <= 4")
 public final class CaseParameters {
   @ValidIdentifier
   private String customerIdentifier;
diff --git a/api/src/main/java/io/mifos/individuallending/api/v1/domain/workflow/Action.java b/api/src/main/java/io/mifos/individuallending/api/v1/domain/workflow/Action.java
index e91750d..cd51961 100644
--- a/api/src/main/java/io/mifos/individuallending/api/v1/domain/workflow/Action.java
+++ b/api/src/main/java/io/mifos/individuallending/api/v1/domain/workflow/Action.java
@@ -23,14 +23,24 @@
  */
 @SuppressWarnings({"WeakerAccess", "unused"})
 public enum Action {
-  OPEN,
-  DENY,
-  APPROVE,
-  DISBURSE,
-  APPLY_INTEREST,
-  ACCEPT_PAYMENT,
-  MARK_LATE,
-  WRITE_OFF,
-  CLOSE,
-  RECOVER
+  OPEN("CHRG"),
+  DENY("CHRG"),
+  APPROVE("ACCO"),
+  DISBURSE("CDIS"),
+  APPLY_INTEREST("INTR"),
+  ACCEPT_PAYMENT("PPAY"),
+  MARK_LATE("ICCT"),
+  WRITE_OFF("ICCT"),
+  CLOSE("ICCT"),
+  RECOVER("ICCT");
+
+  private final String transactionType;
+
+  Action(final String transactionType) {
+    this.transactionType = transactionType;
+  }
+
+  public String getTransactionType() {
+    return transactionType;
+  }
 }
diff --git a/api/src/main/java/io/mifos/portfolio/api/v1/domain/Command.java b/api/src/main/java/io/mifos/portfolio/api/v1/domain/Command.java
index 4cb0cc6..708b65a 100644
--- a/api/src/main/java/io/mifos/portfolio/api/v1/domain/Command.java
+++ b/api/src/main/java/io/mifos/portfolio/api/v1/domain/Command.java
@@ -15,29 +15,39 @@
  */
 package io.mifos.portfolio.api.v1.domain;
 
-import org.hibernate.validator.constraints.NotBlank;
-
+import javax.validation.Valid;
 import java.util.List;
+import java.util.Objects;
 
 /**
  * @author Myrle Krantz
  */
 @SuppressWarnings({"WeakerAccess", "unused"})
 public final class Command {
+  @Valid
   private List<AccountAssignment> oneTimeAccountAssignments;
-  private String comment;
+
+  private String note;
   private String createdOn;
   private String createdBy;
 
   public Command() {
   }
 
-  public String getComment() {
-    return comment;
+  public List<AccountAssignment> getOneTimeAccountAssignments() {
+    return oneTimeAccountAssignments;
   }
 
-  public void setComment(String comment) {
-    this.comment = comment;
+  public void setOneTimeAccountAssignments(List<AccountAssignment> oneTimeAccountAssignments) {
+    this.oneTimeAccountAssignments = oneTimeAccountAssignments;
+  }
+
+  public String getNote() {
+    return note;
+  }
+
+  public void setNote(String note) {
+    this.note = note;
   }
 
   public String getCreatedOn() {
@@ -55,4 +65,30 @@
   public void setCreatedBy(String createdBy) {
     this.createdBy = createdBy;
   }
+
+  @Override
+  public boolean equals(Object o) {
+    if (this == o) return true;
+    if (o == null || getClass() != o.getClass()) return false;
+    Command command = (Command) o;
+    return Objects.equals(oneTimeAccountAssignments, command.oneTimeAccountAssignments) &&
+            Objects.equals(note, command.note) &&
+            Objects.equals(createdOn, command.createdOn) &&
+            Objects.equals(createdBy, command.createdBy);
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hash(oneTimeAccountAssignments, note, createdOn, createdBy);
+  }
+
+  @Override
+  public String toString() {
+    return "Command{" +
+            "oneTimeAccountAssignments=" + oneTimeAccountAssignments +
+            ", note='" + note + '\'' +
+            ", createdOn='" + createdOn + '\'' +
+            ", createdBy='" + createdBy + '\'' +
+            '}';
+  }
 }
diff --git a/api/src/main/java/io/mifos/portfolio/api/v1/domain/InterestRange.java b/api/src/main/java/io/mifos/portfolio/api/v1/domain/InterestRange.java
index 9bb75c4..d119bd7 100644
--- a/api/src/main/java/io/mifos/portfolio/api/v1/domain/InterestRange.java
+++ b/api/src/main/java/io/mifos/portfolio/api/v1/domain/InterestRange.java
@@ -25,7 +25,7 @@
  * @author Myrle Krantz
  */
 @SuppressWarnings({"WeakerAccess", "unused"})
-@ScriptAssert(lang = "javascript", script = "_this.maximum != null && _this.minimum != null && _this.maximum.compareTo(_this.minimum) >= 0 && _this.minimum.scale() == 2 && _this.maximum.scale() == 2")
+@ScriptAssert(lang = "javascript", script = "_this.maximum != null && _this.minimum != null && _this.maximum.compareTo(_this.minimum) >= 0 && _this.minimum.scale() <= 2 && _this.maximum.scale() <= 2")
 public class InterestRange {
   @DecimalMin(value = "0.00")
   @DecimalMax(value = "999.99")
diff --git a/api/src/test/java/io/mifos/Fixture.java b/api/src/test/java/io/mifos/Fixture.java
index a022fe6..c253395 100644
--- a/api/src/test/java/io/mifos/Fixture.java
+++ b/api/src/test/java/io/mifos/Fixture.java
@@ -35,6 +35,16 @@
  */
 @SuppressWarnings("WeakerAccess")
 public class Fixture {
+  static final String INCOME_LEDGER_IDENTIFIER = "1000";
+  static final String FEES_AND_CHARGES_LEDGER_IDENTIFIER = "1300";
+  static final String CASH_LEDGER_IDENTIFIER = "7300";
+  static final String PENDING_DISBURSAL_LEDGER_IDENTIFIER = "7320";
+  static final String CUSTOMER_LOAN_LEDGER_IDENTIFIER = "7353";
+  static final String LOAN_FUNDS_SOURCE_ACCOUNT_IDENTIFIER = "7310";
+  static final String LOAN_ORIGINATION_FEES_ACCOUNT_IDENTIFIER = "1310";
+  static final String PROCESSING_FEE_INCOME_ACCOUNT_IDENTIFIER = "1312";
+  static final String TELLER_ONE_ACCOUNT_IDENTIFIER = "7352";
+
   static int uniquenessSuffix = 0;
 
   static public Product getTestProduct() {
@@ -52,15 +62,19 @@
     product.setMinorCurrencyUnitDigits(2);
 
     final Set<AccountAssignment> accountAssignments = new HashSet<>();
-    accountAssignments.add(new AccountAssignment(PENDING_DISBURSAL, "001-003"));
-    accountAssignments.add(new AccountAssignment(PROCESSING_FEE_INCOME, "001-004"));
-    accountAssignments.add(new AccountAssignment(ORIGINATION_FEE_INCOME, "001-004"));
+    accountAssignments.add(new AccountAssignment(PENDING_DISBURSAL, PENDING_DISBURSAL_LEDGER_IDENTIFIER));
+    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"));
     accountAssignments.add(new AccountAssignment(INTEREST_INCOME, "001-005"));
     accountAssignments.add(new AccountAssignment(INTEREST_ACCRUAL, "001-007"));
     accountAssignments.add(new AccountAssignment(LATE_FEE_INCOME, "001-008"));
     accountAssignments.add(new AccountAssignment(LATE_FEE_ACCRUAL, "001-009"));
     accountAssignments.add(new AccountAssignment(ARREARS_ALLOWANCE, "001-010"));
+    //accountAssignments.add(new AccountAssignment(ENTRY, ...));
+    // Don't assign entry account in test since it usually will not be assigned IRL.
+    accountAssignments.add(new AccountAssignment(LOAN_FUNDS_SOURCE, LOAN_FUNDS_SOURCE_ACCOUNT_IDENTIFIER));
+    accountAssignments.add(new AccountAssignment(CUSTOMER_LOAN, "001-013"));
     product.setAccountAssignments(accountAssignments);
 
     final ProductParameters productParameters = new ProductParameters();
diff --git a/api/src/test/java/io/mifos/individuallending/api/v1/domain/caseinstance/CaseParametersTest.java b/api/src/test/java/io/mifos/individuallending/api/v1/domain/caseinstance/CaseParametersTest.java
index decfa89..89973c8 100644
--- a/api/src/test/java/io/mifos/individuallending/api/v1/domain/caseinstance/CaseParametersTest.java
+++ b/api/src/test/java/io/mifos/individuallending/api/v1/domain/caseinstance/CaseParametersTest.java
@@ -58,9 +58,12 @@
     ret.add(new ValidationTestCase<CaseParameters>("nullBalanceRange")
             .adjustment(x -> x.setMaximumBalance(null))
             .valid(false));
-    ret.add(new ValidationTestCase<CaseParameters>("badBalanceRangeScale")
+    ret.add(new ValidationTestCase<CaseParameters>("tooLargeBalanceRangeScale")
             .adjustment(x -> x.setMaximumBalance(BigDecimal.TEN.setScale(5, BigDecimal.ROUND_FLOOR)))
             .valid(false));
+    ret.add(new ValidationTestCase<CaseParameters>("smallerBalanceRangeScale")
+            .adjustment(x -> x.setMaximumBalance(BigDecimal.TEN.setScale(3, BigDecimal.ROUND_FLOOR)))
+            .valid(true));
     ret.add(new ValidationTestCase<CaseParameters>("invalid payment cycle unit")
             .adjustment(x -> x.getPaymentCycle().setTemporalUnit(ChronoUnit.SECONDS))
             .valid(false));
diff --git a/api/src/test/java/io/mifos/portfolio/api/v1/domain/CommandTest.java b/api/src/test/java/io/mifos/portfolio/api/v1/domain/CommandTest.java
index 20cf510..5e4430b 100644
--- a/api/src/test/java/io/mifos/portfolio/api/v1/domain/CommandTest.java
+++ b/api/src/test/java/io/mifos/portfolio/api/v1/domain/CommandTest.java
@@ -22,6 +22,9 @@
 
 import java.util.ArrayList;
 import java.util.Collection;
+import java.util.Collections;
+
+import static io.mifos.individuallending.api.v1.domain.product.ChargeIdentifiers.PROCESSING_FEE_ID;
 
 /**
  * @author Myrle Krantz
@@ -34,13 +37,21 @@
 
   @Override
   protected Command createValidTestSubject() {
-    return new Command();
+    final Command ret = new Command();
+    ret.setOneTimeAccountAssignments(Collections.emptyList());
+    return ret;
   }
 
   @Parameterized.Parameters
   public static Collection testCases() {
     final Collection<ValidationTestCase> ret = new ArrayList<>();
     ret.add(new ValidationTestCase<Command>("valid"));
+    ret.add(new ValidationTestCase<Command>("invalidAccountAssignment")
+            .adjustment(x -> x.setOneTimeAccountAssignments(Collections.singletonList(new AccountAssignment("", ""))))
+            .valid(false));
+    ret.add(new ValidationTestCase<Command>("validAccountAssignment")
+            .adjustment(x -> x.setOneTimeAccountAssignments(Collections.singletonList(new AccountAssignment(PROCESSING_FEE_ID, "7534"))))
+            .valid(true));
     return ret;
   }
 }
diff --git a/api/src/test/java/io/mifos/portfolio/api/v1/domain/InterestRangeTest.java b/api/src/test/java/io/mifos/portfolio/api/v1/domain/InterestRangeTest.java
index 666aeac..0f4a851 100644
--- a/api/src/test/java/io/mifos/portfolio/api/v1/domain/InterestRangeTest.java
+++ b/api/src/test/java/io/mifos/portfolio/api/v1/domain/InterestRangeTest.java
@@ -66,7 +66,14 @@
               x.setMaximum(BigDecimal.valueOf(5L).setScale(2, BigDecimal.ROUND_UNNECESSARY));
             })
             .valid(false));
+    ret.add(new ValidationTestCase<InterestRange>("too large scale")
+            .adjustment(x ->
+                    x.setMinimum(x.getMinimum().setScale(3, BigDecimal.ROUND_UNNECESSARY)))
+            .valid(false));
+    ret.add(new ValidationTestCase<InterestRange>("smaller scale")
+            .adjustment(x ->
+                    x.setMinimum(x.getMinimum().setScale(1, BigDecimal.ROUND_HALF_EVEN)))
+            .valid(true));
     return ret;
   }
-
 }
\ No newline at end of file
diff --git a/component-test/src/main/java/io/mifos/portfolio/AbstractPortfolioTest.java b/component-test/src/main/java/io/mifos/portfolio/AbstractPortfolioTest.java
index 0758a33..4498f46 100644
--- a/component-test/src/main/java/io/mifos/portfolio/AbstractPortfolioTest.java
+++ b/component-test/src/main/java/io/mifos/portfolio/AbstractPortfolioTest.java
@@ -25,9 +25,10 @@
 import io.mifos.core.test.listener.EnableEventRecording;
 import io.mifos.core.test.listener.EventRecorder;
 import io.mifos.individuallending.api.v1.client.IndividualLending;
+import io.mifos.individuallending.api.v1.domain.workflow.Action;
+import io.mifos.individuallending.api.v1.events.IndividualLoanCommandEvent;
 import io.mifos.portfolio.api.v1.client.PortfolioManager;
-import io.mifos.portfolio.api.v1.domain.Case;
-import io.mifos.portfolio.api.v1.domain.Product;
+import io.mifos.portfolio.api.v1.domain.*;
 import io.mifos.portfolio.api.v1.events.CaseEvent;
 import io.mifos.portfolio.api.v1.events.EventConstants;
 import io.mifos.portfolio.service.config.PortfolioServiceConfiguration;
@@ -55,8 +56,12 @@
 import javax.validation.Validation;
 import javax.validation.Validator;
 import javax.validation.ValidatorFactory;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
 import java.util.Set;
 import java.util.function.Consumer;
+import java.util.stream.Collectors;
 
 import static org.mockito.Matchers.any;
 import static org.mockito.Mockito.doReturn;
@@ -128,10 +133,13 @@
   @MockBean
   RhythmAdapter rhythmAdapter;
 
+  @MockBean
+  LedgerManager ledgerManager;
+
   @Before
   public void prepTest() {
     userContext = this.tenantApplicationSecurityEnvironment.createAutoUserContext(TEST_USER);
-    setupMockAccountingAdapter();
+    AccountingFixture.mockAccountingPrereqs(ledgerManager);
   }
 
   @After
@@ -148,9 +156,6 @@
     }
   }
 
-  private void setupMockAccountingAdapter() {
-  }
-
   Product createProduct() throws InterruptedException {
     return createAdjustedProduct(x -> {});
   }
@@ -188,4 +193,46 @@
 
     return caseInstance;
   }
+
+  void checkStateTransfer(final String productIdentifier,
+                          final String caseIdentifier,
+                          final Action action,
+                          final List<AccountAssignment> oneTimeAccountAssignments,
+                          final String event,
+                          final Case.State nextState) throws InterruptedException {
+    final Command command = new Command();
+    command.setOneTimeAccountAssignments(oneTimeAccountAssignments);
+    portfolioManager.executeCaseCommand(productIdentifier, caseIdentifier, action.name(), command);
+
+    Assert.assertTrue(eventRecorder.waitForMatch(event,
+            (IndividualLoanCommandEvent x) -> individualLoanCommandEventMatches(x, productIdentifier, caseIdentifier)));
+
+    final Case customerCase = portfolioManager.getCase(productIdentifier, caseIdentifier);
+    Assert.assertEquals(customerCase.getCurrentState(), nextState.name());
+  }
+
+  boolean individualLoanCommandEventMatches(
+          final IndividualLoanCommandEvent event,
+          final String productIdentifier,
+          final String caseIdentifier)
+  {
+    return event.getProductIdentifier().equals(productIdentifier) &&
+            event.getCaseIdentifier().equals(caseIdentifier);
+  }
+
+  void checkNextActionsCorrect(final String productIdentifier, final String customerCaseIdentifier, final Action... nextActions)
+  {
+    final Set<String> actionList = Arrays.stream(nextActions).map(Enum::name).collect(Collectors.toSet());
+    Assert.assertEquals(actionList, portfolioManager.getActionsForCase(productIdentifier, customerCaseIdentifier));
+  }
+
+  void checkCostComponentForActionCorrect(final String productIdentifier,
+                                          final String customerCaseIdentifier,
+                                          final Action action,
+                                          final CostComponent... expectedCostComponents) {
+    final List<CostComponent> costComponents = portfolioManager.getCostComponentsForAction(productIdentifier, customerCaseIdentifier, action.name());
+    final Set<CostComponent> setOfCostComponents = new HashSet<>(costComponents);
+    final Set<CostComponent> setOfExpectedCostComponents = new HashSet<>(Arrays.asList(expectedCostComponents));
+    Assert.assertEquals(setOfExpectedCostComponents, setOfCostComponents);
+  }
 }
diff --git a/component-test/src/main/java/io/mifos/portfolio/AccountingFixture.java b/component-test/src/main/java/io/mifos/portfolio/AccountingFixture.java
new file mode 100644
index 0000000..3c401f3
--- /dev/null
+++ b/component-test/src/main/java/io/mifos/portfolio/AccountingFixture.java
@@ -0,0 +1,228 @@
+/*
+ * 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.portfolio;
+
+import io.mifos.accounting.api.v1.client.LedgerManager;
+import io.mifos.accounting.api.v1.domain.*;
+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.Set;
+import java.util.stream.Collectors;
+
+import static io.mifos.portfolio.Fixture.*;
+import static org.mockito.Matchers.argThat;
+
+/**
+ * @author Myrle Krantz
+ */
+class AccountingFixture {
+
+
+  private static Ledger cashLedger() {
+    final Ledger ret = new Ledger();
+    ret.setIdentifier(CASH_LEDGER_IDENTIFIER);
+    ret.setType(AccountType.ASSET.name());
+    return ret;
+  }
+
+  private static Ledger incomeLedger() {
+    final Ledger ret = new Ledger();
+    ret.setIdentifier(INCOME_LEDGER_IDENTIFIER);
+    ret.setType(AccountType.REVENUE.name());
+    return ret;
+  }
+
+  private static Ledger feesAndChargesLedger() {
+    final Ledger ret = new Ledger();
+    ret.setIdentifier(FEES_AND_CHARGES_LEDGER_IDENTIFIER);
+    ret.setParentLedgerIdentifier(INCOME_LEDGER_IDENTIFIER);
+    ret.setType(AccountType.REVENUE.name());
+    return ret;
+  }
+
+  private static Ledger pendingDisbursalLedger() {
+    final Ledger ret = new Ledger();
+    ret.setIdentifier(PENDING_DISBURSAL_LEDGER_IDENTIFIER);
+    ret.setParentLedgerIdentifier(CASH_LEDGER_IDENTIFIER);
+    ret.setType(AccountType.ASSET.name());
+    return ret;
+  }
+
+  private static Ledger customerLoanLedger() {
+    final Ledger ret = new Ledger();
+    ret.setIdentifier(CUSTOMER_LOAN_LEDGER_IDENTIFIER);
+    ret.setParentLedgerIdentifier(CASH_LEDGER_IDENTIFIER);
+    ret.setType(AccountType.ASSET.name());
+    return ret;
+  }
+
+  private static Account loanFundsSourceAccount() {
+    final Account ret = new Account();
+    ret.setIdentifier(LOAN_FUNDS_SOURCE_ACCOUNT_IDENTIFIER);
+    ret.setLedger(CASH_LEDGER_IDENTIFIER);
+    ret.setType(AccountType.ASSET.name());
+    return ret;
+  }
+
+  private static Account processingFeeIncomeAccount() {
+    final Account ret = new Account();
+    ret.setIdentifier(PROCESSING_FEE_INCOME_ACCOUNT_IDENTIFIER);
+    ret.setLedger(FEES_AND_CHARGES_LEDGER_IDENTIFIER);
+    ret.setType(AccountType.REVENUE.name());
+    return ret;
+  }
+
+  private static Account loanOriginationFeesIncomeAccount() {
+    final Account ret = new Account();
+    ret.setIdentifier(LOAN_ORIGINATION_FEES_ACCOUNT_IDENTIFIER);
+    ret.setLedger(FEES_AND_CHARGES_LEDGER_IDENTIFIER);
+    ret.setType(AccountType.REVENUE.name());
+    return ret;
+  }
+
+  private static Account tellerOneAccount() {
+    final Account ret = new Account();
+    ret.setIdentifier(TELLER_ONE_ACCOUNT_IDENTIFIER);
+    ret.setLedger(CASH_LEDGER_IDENTIFIER);
+    ret.setType(AccountType.ASSET.name());
+    return ret;
+  }
+
+  private static class AccountMatcher extends ArgumentMatcher<Account> {
+    private final String ledgerIdentifer;
+    private final AccountType type;
+    private Account checkedArgument;
+
+    private AccountMatcher(final String ledgerIdentifier, final AccountType type) {
+      this.ledgerIdentifer = ledgerIdentifier;
+      this.type = type;
+      this.checkedArgument = null; //Set when matches called.
+    }
+
+    @Override
+    public boolean matches(final Object argument) {
+      if (argument == null)
+        return false;
+      if (! (argument instanceof Account))
+        return false;
+
+      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) &&
+              checkedArgument.getType().equals(type.name()) &&
+              checkedArgument.getBalance() == 0.0;
+    }
+
+    Account getCheckedArgument() {
+      return checkedArgument;
+    }
+  }
+
+  private static class JournalEntryMatcher extends ArgumentMatcher<JournalEntry> {
+    private final String expectedFromAccountIdentifier;
+    private final String expectedToAccountIdentifier;
+    private final BigDecimal expectedAmount;
+    private JournalEntry checkedArgument;
+
+    private JournalEntryMatcher(final String expectedFromAccountIdentifier,
+                                final String expectedToAccountIdentifier,
+                                final BigDecimal amount) {
+      this.expectedFromAccountIdentifier = expectedFromAccountIdentifier;
+      this.expectedToAccountIdentifier = expectedToAccountIdentifier;
+      this.expectedAmount = amount;
+      this.checkedArgument = null; //Set when matches called.
+    }
+
+    @Override
+    public boolean matches(final Object argument) {
+      if (argument == null)
+        return false;
+      if (! (argument instanceof JournalEntry))
+        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())));
+
+      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()));
+    }
+
+    JournalEntry getCheckedArgument() {
+      return checkedArgument;
+    }
+
+    @Override
+    public String toString() {
+      return "JournalEntryMatcher{" +
+              "expectedFromAccountIdentifier='" + expectedFromAccountIdentifier + '\'' +
+              ", expectedToAccountIdentifier='" + expectedToAccountIdentifier + '\'' +
+              ", expectedAmount=" + expectedAmount +
+              ", checkedArgument=" + checkedArgument +
+              '}';
+    }
+  }
+
+  static void mockAccountingPrereqs(final LedgerManager ledgerManagerMock) {
+    Mockito.doReturn(incomeLedger()).when(ledgerManagerMock).findLedger(INCOME_LEDGER_IDENTIFIER);
+    Mockito.doReturn(feesAndChargesLedger()).when(ledgerManagerMock).findLedger(FEES_AND_CHARGES_LEDGER_IDENTIFIER);
+    Mockito.doReturn(cashLedger()).when(ledgerManagerMock).findLedger(CASH_LEDGER_IDENTIFIER);
+    Mockito.doReturn(pendingDisbursalLedger()).when(ledgerManagerMock).findLedger(PENDING_DISBURSAL_LEDGER_IDENTIFIER);
+    Mockito.doReturn(customerLoanLedger()).when(ledgerManagerMock).findLedger(CUSTOMER_LOAN_LEDGER_IDENTIFIER);
+    Mockito.doReturn(loanFundsSourceAccount()).when(ledgerManagerMock).findAccount(LOAN_FUNDS_SOURCE_ACCOUNT_IDENTIFIER);
+    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);
+  }
+
+  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));
+    return specifiesCorrectAccount.getCheckedArgument().getIdentifier();
+  }
+
+  static void verifyTransfer(final LedgerManager ledgerManager,
+                             final String fromAccountIdentifier,
+                             final String toAccountIdentifier,
+                             final BigDecimal amount) {
+    final JournalEntryMatcher specifiesCorrectJournalEntry = new JournalEntryMatcher(fromAccountIdentifier, toAccountIdentifier, amount);
+    Mockito.verify(ledgerManager).createJournalEntry(argThat(specifiesCorrectJournalEntry));
+  }
+}
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 40b60c2..b9f0893 100644
--- a/component-test/src/main/java/io/mifos/portfolio/Fixture.java
+++ b/component-test/src/main/java/io/mifos/portfolio/Fixture.java
@@ -36,6 +36,16 @@
 
 @SuppressWarnings({"WeakerAccess", "unused"})
 public class Fixture {
+  static final String INCOME_LEDGER_IDENTIFIER = "1000";
+  static final String FEES_AND_CHARGES_LEDGER_IDENTIFIER = "1300";
+  static final String CASH_LEDGER_IDENTIFIER = "7300";
+  static final String PENDING_DISBURSAL_LEDGER_IDENTIFIER = "7320";
+  static final String CUSTOMER_LOAN_LEDGER_IDENTIFIER = "7353";
+  static final String LOAN_FUNDS_SOURCE_ACCOUNT_IDENTIFIER = "7310";
+  static final String LOAN_ORIGINATION_FEES_ACCOUNT_IDENTIFIER = "1310";
+  static final String PROCESSING_FEE_INCOME_ACCOUNT_IDENTIFIER = "1312";
+  static final String TELLER_ONE_ACCOUNT_IDENTIFIER = "7352";
+
   private static int uniquenessSuffix = 0;
 
   static public Product getTestProduct() {
@@ -53,18 +63,22 @@
     product.setMinorCurrencyUnitDigits(2);
 
     final Set<AccountAssignment> accountAssignments = new HashSet<>();
-    accountAssignments.add(new AccountAssignment(PENDING_DISBURSAL, "001-003"));
-    accountAssignments.add(new AccountAssignment(PROCESSING_FEE_INCOME, "001-004"));
-    accountAssignments.add(new AccountAssignment(ORIGINATION_FEE_INCOME, "001-004"));
+    accountAssignments.add(new AccountAssignment(PENDING_DISBURSAL, PENDING_DISBURSAL_LEDGER_IDENTIFIER));
+    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"));
     accountAssignments.add(new AccountAssignment(INTEREST_INCOME, "001-005"));
     accountAssignments.add(new AccountAssignment(INTEREST_ACCRUAL, "001-007"));
     accountAssignments.add(new AccountAssignment(LATE_FEE_INCOME, "001-008"));
     accountAssignments.add(new AccountAssignment(LATE_FEE_ACCRUAL, "001-009"));
     accountAssignments.add(new AccountAssignment(ARREARS_ALLOWANCE, "001-010"));
-    accountAssignments.add(new AccountAssignment(ENTRY, "001-011"));
-    accountAssignments.add(new AccountAssignment(LOAN_FUNDS_SOURCE, "001-012"));
-    accountAssignments.add(new AccountAssignment(CUSTOMER_LOAN, "001-013"));
+    //accountAssignments.add(new AccountAssignment(ENTRY, ...));
+    // Don't assign entry account in test since it usually will not be assigned IRL.
+    accountAssignments.add(new AccountAssignment(LOAN_FUNDS_SOURCE, LOAN_FUNDS_SOURCE_ACCOUNT_IDENTIFIER));
+    final AccountAssignment customerLoanAccountAssignment = new AccountAssignment();
+    customerLoanAccountAssignment.setDesignator(CUSTOMER_LOAN);
+    customerLoanAccountAssignment.setLedgerIdentifier(CUSTOMER_LOAN_LEDGER_IDENTIFIER);
+    accountAssignments.add(customerLoanAccountAssignment);
     product.setAccountAssignments(accountAssignments);
 
     final ProductParameters productParameters = new ProductParameters();
diff --git a/component-test/src/main/java/io/mifos/portfolio/TestAccountingInteraction.java b/component-test/src/main/java/io/mifos/portfolio/TestAccountingInteraction.java
new file mode 100644
index 0000000..c471655
--- /dev/null
+++ b/component-test/src/main/java/io/mifos/portfolio/TestAccountingInteraction.java
@@ -0,0 +1,91 @@
+/*
+ * 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.portfolio;
+
+import com.google.gson.Gson;
+import io.mifos.accounting.api.v1.domain.AccountType;
+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.*;
+import io.mifos.portfolio.api.v1.events.ChargeDefinitionEvent;
+import io.mifos.portfolio.api.v1.events.EventConstants;
+import org.junit.Assert;
+import org.junit.Test;
+
+import java.math.BigDecimal;
+import java.util.Collections;
+
+import static io.mifos.individuallending.api.v1.domain.product.ChargeIdentifiers.*;
+import static io.mifos.individuallending.api.v1.events.IndividualLoanEventConstants.APPROVE_INDIVIDUALLOAN_CASE;
+import static io.mifos.individuallending.api.v1.events.IndividualLoanEventConstants.OPEN_INDIVIDUALLOAN_CASE;
+import static io.mifos.portfolio.Fixture.*;
+
+/**
+ * @author Myrle Krantz
+ */
+public class TestAccountingInteraction extends AbstractPortfolioTest {
+
+  @Test
+  public void testLoanApproval() throws InterruptedException {
+    //Create product and set charges to fixed fees.
+    final Product product = createProduct();
+
+    final ChargeDefinition processingFee = portfolioManager.getChargeDefinition(product.getIdentifier(), PROCESSING_FEE_ID);
+    processingFee.setChargeMethod(ChargeDefinition.ChargeMethod.FIXED);
+    processingFee.setAmount(BigDecimal.valueOf(10_0000, 4));
+    portfolioManager.changeChargeDefinition(product.getIdentifier(), PROCESSING_FEE_ID, processingFee);
+    Assert.assertTrue(this.eventRecorder.wait(EventConstants.PUT_CHARGE_DEFINITION,
+            new ChargeDefinitionEvent(product.getIdentifier(), PROCESSING_FEE_ID)));
+
+    final ChargeDefinition loanOriginationFee = portfolioManager.getChargeDefinition(product.getIdentifier(), LOAN_ORIGINATION_FEE_ID);
+    loanOriginationFee.setChargeMethod(ChargeDefinition.ChargeMethod.FIXED);
+    loanOriginationFee.setAmount(BigDecimal.valueOf(100_0000, 4));
+    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)));
+
+    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);
+    final Case customerCase = createAdjustedCase(product.getIdentifier(), x -> x.setParameters(caseParametersAsString));
+
+    //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()));
+
+    final AccountAssignment openCommandProcessingFeeAccountAssignment = new AccountAssignment();
+    openCommandProcessingFeeAccountAssignment.setDesignator(processingFee.getFromAccountDesignator());
+    openCommandProcessingFeeAccountAssignment.setAccountIdentifier(TELLER_ONE_ACCOUNT_IDENTIFIER);
+
+    checkStateTransfer(product.getIdentifier(), customerCase.getIdentifier(), Action.OPEN,
+            Collections.singletonList(openCommandProcessingFeeAccountAssignment),
+            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()));
+
+    AccountingFixture.verifyTransfer(ledgerManager,
+            TELLER_ONE_ACCOUNT_IDENTIFIER, PROCESSING_FEE_INCOME_ACCOUNT_IDENTIFIER,
+            processingFee.getAmount()
+            );
+  }
+}
diff --git a/component-test/src/main/java/io/mifos/portfolio/TestCommands.java b/component-test/src/main/java/io/mifos/portfolio/TestCommands.java
index e2e9122..08186c7 100644
--- a/component-test/src/main/java/io/mifos/portfolio/TestCommands.java
+++ b/component-test/src/main/java/io/mifos/portfolio/TestCommands.java
@@ -23,9 +23,7 @@
 import org.junit.Assert;
 import org.junit.Test;
 
-import java.util.Arrays;
-import java.util.Set;
-import java.util.stream.Collectors;
+import java.util.Collections;
 
 import static io.mifos.individuallending.api.v1.events.IndividualLoanEventConstants.*;
 
@@ -41,29 +39,29 @@
     checkNextActionsCorrect(product.getIdentifier(), customerCase.getIdentifier(), Action.OPEN);
 
 
-    checkStateTransfer(product.getIdentifier(), customerCase.getIdentifier(), Action.OPEN, OPEN_INDIVIDUALLOAN_CASE, Case.State.PENDING);
+    checkStateTransfer(product.getIdentifier(), customerCase.getIdentifier(), Action.OPEN, Collections.emptyList(), OPEN_INDIVIDUALLOAN_CASE, Case.State.PENDING);
     checkNextActionsCorrect(product.getIdentifier(), customerCase.getIdentifier(), Action.APPROVE, Action.DENY);
 
 
-    checkStateTransfer(product.getIdentifier(), customerCase.getIdentifier(), Action.APPROVE, APPROVE_INDIVIDUALLOAN_CASE, Case.State.APPROVED);
+    checkStateTransfer(product.getIdentifier(), customerCase.getIdentifier(), Action.APPROVE, Collections.emptyList(), APPROVE_INDIVIDUALLOAN_CASE, Case.State.APPROVED);
     checkNextActionsCorrect(product.getIdentifier(), customerCase.getIdentifier(), Action.DISBURSE, Action.CLOSE);
 
 
-    checkStateTransfer(product.getIdentifier(), customerCase.getIdentifier(), Action.DISBURSE, DISBURSE_INDIVIDUALLOAN_CASE, Case.State.ACTIVE);
+    checkStateTransfer(product.getIdentifier(), customerCase.getIdentifier(), Action.DISBURSE, Collections.emptyList(), DISBURSE_INDIVIDUALLOAN_CASE, Case.State.ACTIVE);
     checkNextActionsCorrect(product.getIdentifier(), customerCase.getIdentifier(),
             Action.APPLY_INTEREST, Action.MARK_LATE, Action.ACCEPT_PAYMENT, Action.DISBURSE, Action.WRITE_OFF, Action.CLOSE);
 
 
-    checkStateTransfer(product.getIdentifier(), customerCase.getIdentifier(), Action.ACCEPT_PAYMENT, ACCEPT_PAYMENT_INDIVIDUALLOAN_CASE, Case.State.ACTIVE);
+    checkStateTransfer(product.getIdentifier(), customerCase.getIdentifier(), Action.ACCEPT_PAYMENT, Collections.emptyList(), ACCEPT_PAYMENT_INDIVIDUALLOAN_CASE, Case.State.ACTIVE);
     checkNextActionsCorrect(product.getIdentifier(), customerCase.getIdentifier(),
             Action.APPLY_INTEREST, Action.MARK_LATE, Action.ACCEPT_PAYMENT, Action.DISBURSE, Action.WRITE_OFF, Action.CLOSE);
 
 
-    checkStateTransfer(product.getIdentifier(), customerCase.getIdentifier(), Action.ACCEPT_PAYMENT, ACCEPT_PAYMENT_INDIVIDUALLOAN_CASE, Case.State.ACTIVE);
+    checkStateTransfer(product.getIdentifier(), customerCase.getIdentifier(), Action.ACCEPT_PAYMENT, Collections.emptyList(), ACCEPT_PAYMENT_INDIVIDUALLOAN_CASE, Case.State.ACTIVE);
     checkNextActionsCorrect(product.getIdentifier(), customerCase.getIdentifier(),
             Action.APPLY_INTEREST, Action.MARK_LATE, Action.ACCEPT_PAYMENT, Action.DISBURSE, Action.WRITE_OFF, Action.CLOSE);
 
-    checkStateTransfer(product.getIdentifier(), customerCase.getIdentifier(), Action.CLOSE, CLOSE_INDIVIDUALLOAN_CASE, Case.State.CLOSED);
+    checkStateTransfer(product.getIdentifier(), customerCase.getIdentifier(), Action.CLOSE, Collections.emptyList(), CLOSE_INDIVIDUALLOAN_CASE, Case.State.CLOSED);
     checkNextActionsCorrect(product.getIdentifier(), customerCase.getIdentifier());
   }
 
@@ -75,19 +73,19 @@
     checkNextActionsCorrect(product.getIdentifier(), customerCase.getIdentifier(), Action.OPEN);
 
 
-    checkStateTransfer(product.getIdentifier(), customerCase.getIdentifier(), Action.OPEN, OPEN_INDIVIDUALLOAN_CASE, Case.State.PENDING);
+    checkStateTransfer(product.getIdentifier(), customerCase.getIdentifier(), Action.OPEN, Collections.emptyList(), OPEN_INDIVIDUALLOAN_CASE, Case.State.PENDING);
     checkNextActionsCorrect(product.getIdentifier(), customerCase.getIdentifier(), Action.APPROVE, Action.DENY);
 
 
-    checkStateTransfer(product.getIdentifier(), customerCase.getIdentifier(), Action.APPROVE, APPROVE_INDIVIDUALLOAN_CASE, Case.State.APPROVED);
+    checkStateTransfer(product.getIdentifier(), customerCase.getIdentifier(), Action.APPROVE, Collections.emptyList(), APPROVE_INDIVIDUALLOAN_CASE, Case.State.APPROVED);
     checkNextActionsCorrect(product.getIdentifier(), customerCase.getIdentifier(), Action.DISBURSE, Action.CLOSE);
 
 
-    checkStateTransfer(product.getIdentifier(), customerCase.getIdentifier(), Action.DISBURSE, DISBURSE_INDIVIDUALLOAN_CASE, Case.State.ACTIVE);
+    checkStateTransfer(product.getIdentifier(), customerCase.getIdentifier(), Action.DISBURSE, Collections.emptyList(), DISBURSE_INDIVIDUALLOAN_CASE, Case.State.ACTIVE);
     checkNextActionsCorrect(product.getIdentifier(), customerCase.getIdentifier(),
             Action.APPLY_INTEREST, Action.MARK_LATE, Action.ACCEPT_PAYMENT, Action.DISBURSE, Action.WRITE_OFF, Action.CLOSE);
 
-    checkStateTransfer(product.getIdentifier(), customerCase.getIdentifier(), Action.WRITE_OFF, WRITE_OFF_INDIVIDUALLOAN_CASE, Case.State.CLOSED);
+    checkStateTransfer(product.getIdentifier(), customerCase.getIdentifier(), Action.WRITE_OFF, Collections.emptyList(), WRITE_OFF_INDIVIDUALLOAN_CASE, Case.State.CLOSED);
     checkNextActionsCorrect(product.getIdentifier(), customerCase.getIdentifier());
   }
 
@@ -96,26 +94,11 @@
     final Product product = createAndEnableProduct();
     final Case customerCase = createCase(product.getIdentifier());
 
-    checkStateTransfer(product.getIdentifier(), customerCase.getIdentifier(), Action.OPEN, OPEN_INDIVIDUALLOAN_CASE, Case.State.PENDING);
+    checkStateTransfer(product.getIdentifier(), customerCase.getIdentifier(), Action.OPEN, Collections.emptyList(), OPEN_INDIVIDUALLOAN_CASE, Case.State.PENDING);
 
     checkStateTransferFails(product.getIdentifier(), customerCase.getIdentifier(), Action.DISBURSE, DISBURSE_INDIVIDUALLOAN_CASE, Case.State.PENDING);
   }
 
-  public void checkStateTransfer(final String productIdentifier,
-                                 final String caseIdentifier,
-                                 final Action action,
-                                 final String event,
-                                 final Case.State nextState) throws InterruptedException {
-    final Command command = new Command();
-    portfolioManager.executeCaseCommand(productIdentifier, caseIdentifier, action.name(), command);
-
-    Assert.assertTrue(eventRecorder.waitForMatch(event,
-            (IndividualLoanCommandEvent x) -> individualLoanCommandEventMatches(x, productIdentifier, caseIdentifier)));
-
-    final Case customerCase = portfolioManager.getCase(productIdentifier, caseIdentifier);
-    Assert.assertEquals(customerCase.getCurrentState(), nextState.name());
-  }
-
   public void checkStateTransferFails(final String productIdentifier,
                                  final String caseIdentifier,
                                  final Action action,
@@ -134,19 +117,4 @@
     final Case customerCase = portfolioManager.getCase(productIdentifier, caseIdentifier);
     Assert.assertEquals(customerCase.getCurrentState(), initialState.name());
   }
-
-  private void checkNextActionsCorrect(final String productIdentifier, final String customerCaseIdentifier, final Action... nextActions)
-  {
-    final Set<String> actionList = Arrays.stream(nextActions).map(Enum::name).collect(Collectors.toSet());
-    Assert.assertEquals(actionList, portfolioManager.getActionsForCase(productIdentifier, customerCaseIdentifier));
-  }
-
-  private boolean individualLoanCommandEventMatches(
-          final IndividualLoanCommandEvent event,
-          final String productIdentifier,
-          final String caseIdentifier)
-  {
-    return event.getProductIdentifier().equals(productIdentifier) &&
-            event.getCaseIdentifier().equals(caseIdentifier);
-  }
 }
\ No newline at end of file
diff --git a/service/src/main/java/io/mifos/individuallending/IndividualLendingCommandDispatcher.java b/service/src/main/java/io/mifos/individuallending/IndividualLendingCommandDispatcher.java
index 5b685b3..0faf9a9 100644
--- a/service/src/main/java/io/mifos/individuallending/IndividualLendingCommandDispatcher.java
+++ b/service/src/main/java/io/mifos/individuallending/IndividualLendingCommandDispatcher.java
@@ -42,31 +42,31 @@
     final Action action = Action.valueOf(actionIdentifier);
     switch (action) {
       case OPEN:
-        this.commandGateway.process(new OpenCommand(productIdentifier, caseIdentifier));
+        this.commandGateway.process(new OpenCommand(productIdentifier, caseIdentifier, command));
         break;
       case DENY:
-        this.commandGateway.process(new DenyCommand(productIdentifier, caseIdentifier));
+        this.commandGateway.process(new DenyCommand(productIdentifier, caseIdentifier, command));
         break;
       case APPROVE:
-        this.commandGateway.process(new ApproveCommand(productIdentifier, caseIdentifier));
+        this.commandGateway.process(new ApproveCommand(productIdentifier, caseIdentifier, command));
         break;
       case DISBURSE:
-        this.commandGateway.process(new DisburseCommand(productIdentifier, caseIdentifier));
+        this.commandGateway.process(new DisburseCommand(productIdentifier, caseIdentifier, command));
         break;
       case ACCEPT_PAYMENT:
-        this.commandGateway.process(new AcceptPaymentCommand(productIdentifier, caseIdentifier));
+        this.commandGateway.process(new AcceptPaymentCommand(productIdentifier, caseIdentifier, command));
         break;
       case WRITE_OFF:
-        this.commandGateway.process(new WriteOffCommand(productIdentifier, caseIdentifier));
+        this.commandGateway.process(new WriteOffCommand(productIdentifier, caseIdentifier, command));
         break;
       case CLOSE:
-        this.commandGateway.process(new CloseCommand(productIdentifier, caseIdentifier));
+        this.commandGateway.process(new CloseCommand(productIdentifier, caseIdentifier, command));
         break;
       case RECOVER:
-        this.commandGateway.process(new RecoverCommand(productIdentifier, caseIdentifier));
+        this.commandGateway.process(new RecoverCommand(productIdentifier, caseIdentifier, command));
         break;
       default:
-        throw ServiceException.badRequest("Action ''{0}'' cannot be taken from current state.", actionIdentifier);
+        throw ServiceException.badRequest("Action ''{0}'' is not implemented for individual loans.", actionIdentifier);
     }
   }
 }
diff --git a/service/src/main/java/io/mifos/individuallending/internal/command/AcceptPaymentCommand.java b/service/src/main/java/io/mifos/individuallending/internal/command/AcceptPaymentCommand.java
index 00916ff..e7c8fd9 100644
--- a/service/src/main/java/io/mifos/individuallending/internal/command/AcceptPaymentCommand.java
+++ b/service/src/main/java/io/mifos/individuallending/internal/command/AcceptPaymentCommand.java
@@ -15,16 +15,20 @@
  */
 package io.mifos.individuallending.internal.command;
 
+import io.mifos.portfolio.api.v1.domain.Command;
+
 /**
  * @author Myrle Krantz
  */
 public class AcceptPaymentCommand {
   private final String productIdentifier;
   private final String caseIdentifier;
+  private final Command command;
 
-  public AcceptPaymentCommand(final String productIdentifier, final String caseIdentifier) {
+  public AcceptPaymentCommand(final String productIdentifier, final String caseIdentifier, final Command command) {
     this.productIdentifier = productIdentifier;
     this.caseIdentifier = caseIdentifier;
+    this.command = command;
   }
 
   public String getProductIdentifier() {
@@ -35,6 +39,10 @@
     return caseIdentifier;
   }
 
+  public Command getCommand() {
+    return command;
+  }
+
   @Override
   public String toString() {
     return "AcceptPaymentCommand{" +
diff --git a/service/src/main/java/io/mifos/individuallending/internal/command/ApproveCommand.java b/service/src/main/java/io/mifos/individuallending/internal/command/ApproveCommand.java
index 25b2613..4765cf2 100644
--- a/service/src/main/java/io/mifos/individuallending/internal/command/ApproveCommand.java
+++ b/service/src/main/java/io/mifos/individuallending/internal/command/ApproveCommand.java
@@ -15,16 +15,20 @@
  */
 package io.mifos.individuallending.internal.command;
 
+import io.mifos.portfolio.api.v1.domain.Command;
+
 /**
  * @author Myrle Krantz
  */
 public class ApproveCommand {
   private final String productIdentifier;
   private final String caseIdentifier;
+  private final Command command;
 
-  public ApproveCommand(final String productIdentifier, final String caseIdentifier) {
+  public ApproveCommand(final String productIdentifier, final String caseIdentifier, final Command command) {
     this.productIdentifier = productIdentifier;
     this.caseIdentifier = caseIdentifier;
+    this.command = command;
   }
 
   public String getProductIdentifier() {
@@ -35,6 +39,10 @@
     return caseIdentifier;
   }
 
+  public Command getCommand() {
+    return command;
+  }
+
   @Override
   public String toString() {
     return "ApproveCommand{" +
diff --git a/service/src/main/java/io/mifos/individuallending/internal/command/CloseCommand.java b/service/src/main/java/io/mifos/individuallending/internal/command/CloseCommand.java
index 3829eeb..9fb578f 100644
--- a/service/src/main/java/io/mifos/individuallending/internal/command/CloseCommand.java
+++ b/service/src/main/java/io/mifos/individuallending/internal/command/CloseCommand.java
@@ -15,16 +15,20 @@
  */
 package io.mifos.individuallending.internal.command;
 
+import io.mifos.portfolio.api.v1.domain.Command;
+
 /**
  * @author Myrle Krantz
  */
 public class CloseCommand {
   private final String productIdentifier;
   private final String caseIdentifier;
+  private final Command command;
 
-  public CloseCommand(final String productIdentifier, final String caseIdentifier) {
+  public CloseCommand(final String productIdentifier, final String caseIdentifier, final Command command) {
     this.productIdentifier = productIdentifier;
     this.caseIdentifier = caseIdentifier;
+    this.command = command;
   }
 
   public String getProductIdentifier() {
@@ -35,6 +39,10 @@
     return caseIdentifier;
   }
 
+  public Command getCommand() {
+    return command;
+  }
+
   @Override
   public String toString() {
     return "CloseCommand{" +
diff --git a/service/src/main/java/io/mifos/individuallending/internal/command/DenyCommand.java b/service/src/main/java/io/mifos/individuallending/internal/command/DenyCommand.java
index 1592e21..f01dae9 100644
--- a/service/src/main/java/io/mifos/individuallending/internal/command/DenyCommand.java
+++ b/service/src/main/java/io/mifos/individuallending/internal/command/DenyCommand.java
@@ -15,16 +15,20 @@
  */
 package io.mifos.individuallending.internal.command;
 
+import io.mifos.portfolio.api.v1.domain.Command;
+
 /**
  * @author Myrle Krantz
  */
 public class DenyCommand {
   private final String productIdentifier;
   private final String caseIdentifier;
+  private final Command command;
 
-  public DenyCommand(final String productIdentifier, final String caseIdentifier) {
+  public DenyCommand(final String productIdentifier, final String caseIdentifier, final Command command) {
     this.productIdentifier = productIdentifier;
     this.caseIdentifier = caseIdentifier;
+    this.command = command;
   }
 
   public String getProductIdentifier() {
@@ -35,6 +39,10 @@
     return caseIdentifier;
   }
 
+  public Command getCommand() {
+    return command;
+  }
+
   @Override
   public String toString() {
     return "DenyCommand{" +
diff --git a/service/src/main/java/io/mifos/individuallending/internal/command/DisburseCommand.java b/service/src/main/java/io/mifos/individuallending/internal/command/DisburseCommand.java
index c251232..5356c4c 100644
--- a/service/src/main/java/io/mifos/individuallending/internal/command/DisburseCommand.java
+++ b/service/src/main/java/io/mifos/individuallending/internal/command/DisburseCommand.java
@@ -15,16 +15,20 @@
  */
 package io.mifos.individuallending.internal.command;
 
+import io.mifos.portfolio.api.v1.domain.Command;
+
 /**
  * @author Myrle Krantz
  */
 public class DisburseCommand {
   private final String productIdentifier;
   private final String caseIdentifier;
+  private final Command command;
 
-  public DisburseCommand(final String productIdentifier, final String caseIdentifier) {
+  public DisburseCommand(final String productIdentifier, final String caseIdentifier, final Command command) {
     this.productIdentifier = productIdentifier;
     this.caseIdentifier = caseIdentifier;
+    this.command = command;
   }
 
   public String getProductIdentifier() {
@@ -35,6 +39,10 @@
     return caseIdentifier;
   }
 
+  public Command getCommand() {
+    return command;
+  }
+
   @Override
   public String toString() {
     return "DisburseCommand{" +
diff --git a/service/src/main/java/io/mifos/individuallending/internal/command/OpenCommand.java b/service/src/main/java/io/mifos/individuallending/internal/command/OpenCommand.java
index 1330537..4ebd01d 100644
--- a/service/src/main/java/io/mifos/individuallending/internal/command/OpenCommand.java
+++ b/service/src/main/java/io/mifos/individuallending/internal/command/OpenCommand.java
@@ -15,16 +15,20 @@
  */
 package io.mifos.individuallending.internal.command;
 
+import io.mifos.portfolio.api.v1.domain.Command;
+
 /**
  * @author Myrle Krantz
  */
 public class OpenCommand {
   private final String productIdentifier;
   private final String caseIdentifier;
+  private final Command command;
 
-  public OpenCommand(final String productIdentifier, final String caseIdentifier) {
+  public OpenCommand(final String productIdentifier, final String caseIdentifier, final Command command) {
     this.productIdentifier = productIdentifier;
     this.caseIdentifier = caseIdentifier;
+    this.command = command;
   }
 
   public String getProductIdentifier() {
@@ -35,6 +39,10 @@
     return caseIdentifier;
   }
 
+  public Command getCommand() {
+    return command;
+  }
+
   @Override
   public String toString() {
     return "OpenCommand{" +
diff --git a/service/src/main/java/io/mifos/individuallending/internal/command/RecoverCommand.java b/service/src/main/java/io/mifos/individuallending/internal/command/RecoverCommand.java
index 2eecd8c..7c333ef 100644
--- a/service/src/main/java/io/mifos/individuallending/internal/command/RecoverCommand.java
+++ b/service/src/main/java/io/mifos/individuallending/internal/command/RecoverCommand.java
@@ -15,16 +15,20 @@
  */
 package io.mifos.individuallending.internal.command;
 
+import io.mifos.portfolio.api.v1.domain.Command;
+
 /**
  * @author Myrle Krantz
  */
 public class RecoverCommand {
   private final String productIdentifier;
   private final String caseIdentifier;
+  private final Command command;
 
-  public RecoverCommand(final String productIdentifier, final String caseIdentifier) {
+  public RecoverCommand(final String productIdentifier, final String caseIdentifier, final Command command) {
     this.productIdentifier = productIdentifier;
     this.caseIdentifier = caseIdentifier;
+    this.command = command;
   }
 
   public String getProductIdentifier() {
@@ -35,6 +39,10 @@
     return caseIdentifier;
   }
 
+  public Command getCommand() {
+    return command;
+  }
+
   @Override
   public String toString() {
     return "RecoverCommand{" +
diff --git a/service/src/main/java/io/mifos/individuallending/internal/command/WriteOffCommand.java b/service/src/main/java/io/mifos/individuallending/internal/command/WriteOffCommand.java
index 3859035..1f494c9 100644
--- a/service/src/main/java/io/mifos/individuallending/internal/command/WriteOffCommand.java
+++ b/service/src/main/java/io/mifos/individuallending/internal/command/WriteOffCommand.java
@@ -15,16 +15,20 @@
  */
 package io.mifos.individuallending.internal.command;
 
+import io.mifos.portfolio.api.v1.domain.Command;
+
 /**
  * @author Myrle Krantz
  */
 public class WriteOffCommand {
   private final String productIdentifier;
   private final String caseIdentifier;
+  private final Command command;
 
-  public WriteOffCommand(final String productIdentifier, final String caseIdentifier) {
+  public WriteOffCommand(final String productIdentifier, final String caseIdentifier, final Command command) {
     this.productIdentifier = productIdentifier;
     this.caseIdentifier = caseIdentifier;
+    this.command = command;
   }
 
   public String getProductIdentifier() {
@@ -35,6 +39,10 @@
     return caseIdentifier;
   }
 
+  public Command getCommand() {
+    return command;
+  }
+
   @Override
   public String toString() {
     return "WriteOffCommand{" +
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 c72e65e..4d42435 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
@@ -16,23 +16,39 @@
 package io.mifos.individuallending.internal.command.handler;
 
 
+import io.mifos.core.command.annotation.Aggregate;
+import io.mifos.core.command.annotation.CommandHandler;
 import io.mifos.core.command.annotation.CommandLogLevel;
+import io.mifos.core.command.annotation.EventEmitter;
+import io.mifos.core.lang.ServiceException;
+import io.mifos.individuallending.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.IndividualLendingPatternFactory;
 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.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.repository.CaseEntity;
-import io.mifos.portfolio.service.internal.repository.CaseRepository;
-import io.mifos.core.command.annotation.Aggregate;
-import io.mifos.core.command.annotation.CommandHandler;
-import io.mifos.core.command.annotation.EventEmitter;
-import io.mifos.core.lang.ServiceException;
+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;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.transaction.annotation.Transactional;
 
+import java.math.BigDecimal;
+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
  */
@@ -40,24 +56,86 @@
 @Aggregate
 public class IndividualLoanCommandHandler {
 
+  private final ProductRepository productRepository;
   private final CaseRepository caseRepository;
+  private final CaseParametersRepository caseParametersRepository;
+  private final AccountingAdapter accountingAdapter;
+  private final IndividualLoanService individualLoanService;
 
   @Autowired
-  public IndividualLoanCommandHandler(final CaseRepository caseRepository) {
+  public IndividualLoanCommandHandler(final ProductRepository productRepository,
+                                      final CaseRepository caseRepository,
+                                      final CaseParametersRepository caseParametersRepository,
+                                      final AccountingAdapter accountingAdapter,
+                                      final IndividualLoanService individualLoanService) {
+    this.productRepository = productRepository;
     this.caseRepository = caseRepository;
+    this.caseParametersRepository = caseParametersRepository;
+    this.accountingAdapter = accountingAdapter;
+    this.individualLoanService = individualLoanService;
   }
 
   @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 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 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());
+    //TODO: Accrual
+
+    accountingAdapter.bookCharges(chargesNamedViaAccountIdentifier,
+            command.getCommand().getNote(),
+            command.getProductIdentifier() + "." + command.getCaseIdentifier() + "." + Action.OPEN.name(),
+            Action.OPEN.getTransactionType());
+    //Only move to pending if book charges command was accepted.
     updateCaseState(customerCase, 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));
+  }
+
   @Transactional
   @CommandHandler(logStart = CommandLogLevel.INFO, logFinish = CommandLogLevel.INFO)
   @EventEmitter(selectorName = EventConstants.SELECTOR_NAME, selectorValue = IndividualLoanEventConstants.DENY_INDIVIDUALLOAN_CASE)
@@ -130,7 +208,12 @@
 
   private CaseEntity getCaseOrThrow(final String productIdentifier, final String caseIdentifier) {
     return caseRepository.findByProductIdentifierAndIdentifier(productIdentifier, caseIdentifier)
-              .orElseThrow(() -> ServiceException.notFound("case not found {0}.{1}", 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 void checkActionCanBeExecuted(final Case.State state, final Action action) {
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 a10a100..e3ec87b 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
@@ -27,6 +27,7 @@
 import io.mifos.individuallending.api.v1.domain.workflow.Action;
 import io.mifos.portfolio.api.v1.domain.ChargeDefinition;
 import io.mifos.portfolio.api.v1.domain.Product;
+import io.mifos.portfolio.service.internal.util.ChargeInstance;
 import org.javamoney.calc.common.Rate;
 import org.javamoney.moneta.Money;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -80,7 +81,7 @@
             .orElseThrow(() -> new IllegalArgumentException("Non-existent product identifier."));
     final int minorCurrencyUnitDigits = product.getMinorCurrencyUnitDigits();
 
-    final List<ScheduledAction> scheduledActions = scheduledActionService.getScheduledActions(initialDisbursalDate, caseParameters);
+    final List<ScheduledAction> scheduledActions = scheduledActionService.getHypotheticalScheduledActions(initialDisbursalDate, caseParameters);
 
     final List<ScheduledCharge> scheduledCharges = getScheduledCharges(productIdentifier, minorCurrencyUnitDigits, caseParameters.getMaximumBalance(), scheduledActions);
 
@@ -90,6 +91,14 @@
             .map(IndividualLoanService::chargeNameFromChargeDefinition)
             .collect(Collectors.toSet());
 
+    return constructPage(pageIndex, size, plannedPaymentsElements, chargeNames);
+  }
+
+  private static PlannedPaymentPage constructPage(
+          final int pageIndex,
+          final int size,
+          final List<PlannedPayment> plannedPaymentsElements,
+          final Set<ChargeName> chargeNames) {
     final int fromIndex = size*pageIndex;
     final int toIndex = Math.min(size*(pageIndex+1), plannedPaymentsElements.size());
     final List<PlannedPayment> elements = plannedPaymentsElements.subList(fromIndex, toIndex);
@@ -104,6 +113,29 @@
     return ret;
   }
 
+  public List<ChargeInstance> getChargeInstances(final String productIdentifier,
+                                                 final CaseParameters caseParameters,
+                                                 final BigDecimal currentBalance,
+                                                 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 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());
+  }
+
   private static ChargeName chargeNameFromChargeDefinition(final ScheduledCharge scheduledCharge) {
     return new ChargeName(scheduledCharge.getChargeDefinition().getIdentifier(), scheduledCharge.getChargeDefinition().getName());
   }
@@ -126,12 +158,17 @@
             chargeDefinitionsMappedByChargeAction,
             chargeDefinitionsMappedByAccrueAction,
             acceptPaymentDefinition);
-    int digitsInInitialeBalance = initialBalance.precision();
-    final Map<Period, BigDecimal> ratesByPeriod = periodChargeCalculator.getPeriodRates(scheduledCharges, digitsInInitialeBalance + minorCurrencyUnitDigits + EXTRA_PRECISION);
+    int digitsInInitialBalance = initialBalance.precision();
+    final Map<Period, BigDecimal> accrualRatesByPeriod
+            = periodChargeCalculator.getPeriodAccrualRates(scheduledCharges,
+            digitsInInitialBalance + minorCurrencyUnitDigits + EXTRA_PRECISION);
 
-    final BigDecimal geometricMean = ratesByPeriod.values().stream().collect(RateCollectors.geometricMean(digitsInInitialeBalance + minorCurrencyUnitDigits + EXTRA_PRECISION));
-
-    acceptPaymentDefinition.setAmount(loanPayment(initialBalance, ratesByPeriod.size(), geometricMean));
+    if (accrualRatesByPeriod.size() != 0) {
+      final BigDecimal geometricMeanAccrualRate = accrualRatesByPeriod.values().stream().collect(RateCollectors.geometricMean(digitsInInitialBalance + minorCurrencyUnitDigits + EXTRA_PRECISION));
+      acceptPaymentDefinition.setAmount(loanPaymentInContextOfAccruedInterest(initialBalance, accrualRatesByPeriod.size(), geometricMeanAccrualRate));
+    }
+    else
+      acceptPaymentDefinition.setAmount(initialBalance);
     return scheduledCharges;
   }
 
@@ -182,29 +219,14 @@
     final List<PlannedPayment> plannedPayments = new ArrayList<>();
     for (final Period repaymentPeriod : sortedRepaymentPeriods)
     {
-      final Map<String, CostComponent> costComponentMap = new HashMap<>();
       final SortedSet<ScheduledCharge> scheduledChargesInPeriod = orderedScheduledChargesGroupedByPeriod.get(repaymentPeriod);
-      for (final ScheduledCharge scheduledCharge : scheduledChargesInPeriod)
-      {
-        final CostComponent costComponent = costComponentMap
-                .computeIfAbsent(scheduledCharge.getChargeDefinition().getIdentifier(),
-                chargeIdentifier -> {
-                  final CostComponent ret = new CostComponent();
-                  ret.setChargeIdentifier(scheduledCharge.getChargeDefinition().getIdentifier());
-                  ret.setAmount(BigDecimal.ZERO);
-                  return ret;
-                });
+      final CostComponentsForRepaymentPeriod costComponentsForRepaymentPeriod =
+              getCostComponentsForScheduledCharges(scheduledChargesInPeriod, balance, minorCurrencyUnitDigits);
 
-        final BigDecimal chargeAmount = howToApplyScheduledChargeToBalance(scheduledCharge, 8)
-                .apply(balance)
-                .setScale(minorCurrencyUnitDigits, BigDecimal.ROUND_HALF_EVEN);
-        if (chargeDefinitionTouchesCustomerLoanAccount(scheduledCharge.getChargeDefinition()))
-          balance = balance.add(chargeAmount);
-        costComponent.setAmount(costComponent.getAmount().add(chargeAmount));
-      }
       final PlannedPayment plannedPayment = new PlannedPayment();
-      plannedPayment.setCostComponents(costComponentMap.values().stream().collect(Collectors.toList()));
+      plannedPayment.setCostComponents(costComponentsForRepaymentPeriod.costComponents.values().stream().collect(Collectors.toList()));
       plannedPayment.setDate(DateConverter.toIsoString(repaymentPeriod.getEndDate()));
+      balance = balance.add(costComponentsForRepaymentPeriod.balanceAdjustment);
       plannedPayment.setRemainingPrincipal(balance);
       plannedPayments.add(plannedPayment);
     }
@@ -221,6 +243,49 @@
     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 int minorCurrencyUnitDigits) {
+    BigDecimal balanceAdjustment = BigDecimal.ZERO;
+
+    final Map<ChargeDefinition, CostComponent> costComponentMap = new HashMap<>();
+    for (final ScheduledCharge scheduledCharge : scheduledCharges)
+    {
+      final CostComponent costComponent = costComponentMap
+              .computeIfAbsent(scheduledCharge.getChargeDefinition(),
+                      chargeIdentifier -> {
+                        final CostComponent ret = new CostComponent();
+                        ret.setChargeIdentifier(scheduledCharge.getChargeDefinition().getIdentifier());
+                        ret.setAmount(BigDecimal.ZERO);
+                        return ret;
+                      });
+
+      final BigDecimal chargeAmount = howToApplyScheduledChargeToBalance(scheduledCharge, 8)
+              .apply(balance)
+              .setScale(minorCurrencyUnitDigits, BigDecimal.ROUND_HALF_EVEN);
+      if (chargeDefinitionTouchesCustomerLoanAccount(scheduledCharge.getChargeDefinition()))
+        balanceAdjustment = balanceAdjustment.add(chargeAmount);
+      costComponent.setAmount(costComponent.getAmount().add(chargeAmount));
+    }
+
+    return new CostComponentsForRepaymentPeriod(
+            costComponentMap,
+            balanceAdjustment);
+  }
+
   private static boolean chargeDefinitionTouchesCustomerLoanAccount(final ChargeDefinition chargeDefinition)
   {
     return chargeDefinition.getToAccountDesignator().equals(AccountDesignators.CUSTOMER_LOAN) ||
@@ -243,14 +308,14 @@
     }
   }
 
-  private BigDecimal loanPayment(
+  private BigDecimal loanPaymentInContextOfAccruedInterest(
           final BigDecimal initialBalance,
           final int periodCount,
-          final BigDecimal geometricMean) {
+          final BigDecimal geometricMeanOfInterest) {
     if (periodCount == 0)
-      throw new IllegalStateException();
+      throw new IllegalStateException("To calculate a loan payment there must be at least one payment period.");
 
-    final MonetaryAmount presentValue = AnnuityPayment.calculate(Money.of(initialBalance, "XXX"), Rate.of(geometricMean), periodCount);
+    final MonetaryAmount presentValue = AnnuityPayment.calculate(Money.of(initialBalance, "XXX"), Rate.of(geometricMeanOfInterest), periodCount);
     return BigDecimal.valueOf(presentValue.getNumber().doubleValueExact()).negate();
   }
 
diff --git a/service/src/main/java/io/mifos/individuallending/internal/service/Period.java b/service/src/main/java/io/mifos/individuallending/internal/service/Period.java
index e9ec4bb..aa4f170 100644
--- a/service/src/main/java/io/mifos/individuallending/internal/service/Period.java
+++ b/service/src/main/java/io/mifos/individuallending/internal/service/Period.java
@@ -28,9 +28,9 @@
   final private LocalDate beginDate;
   final private LocalDate endDate;
 
-  Period(final LocalDate beginDate, final LocalDate endDate) {
+  Period(final LocalDate beginDate, final LocalDate endDateExclusive) {
     this.beginDate = beginDate;
-    this.endDate = endDate;
+    this.endDate = endDateExclusive;
   }
 
   Period(final LocalDate beginDate, final int periodLength) {
@@ -51,6 +51,10 @@
     return ChronoUnit.DAYS.getDuration().multipliedBy(days);
   }
 
+  boolean containsDate(final LocalDate date) {
+    return this.getBeginDate().compareTo(date) <= 0 && this.getEndDate().compareTo(date) > 0;
+  }
+
   @Override
   public boolean equals(Object o) {
     if (this == o) return true;
diff --git a/service/src/main/java/io/mifos/individuallending/internal/service/PeriodChargeCalculator.java b/service/src/main/java/io/mifos/individuallending/internal/service/PeriodChargeCalculator.java
index decafe2..3466c21 100644
--- a/service/src/main/java/io/mifos/individuallending/internal/service/PeriodChargeCalculator.java
+++ b/service/src/main/java/io/mifos/individuallending/internal/service/PeriodChargeCalculator.java
@@ -34,7 +34,7 @@
   {
   }
 
-  Map<Period, BigDecimal> getPeriodRates(final List<ScheduledCharge> scheduledCharges, final int precision) {
+  Map<Period, BigDecimal> getPeriodAccrualRates(final List<ScheduledCharge> scheduledCharges, final int precision) {
     return scheduledCharges.stream()
             .filter(PeriodChargeCalculator::accruedCharge)
             .collect(Collectors.groupingBy(scheduledCharge -> scheduledCharge.getScheduledAction().repaymentPeriod,
diff --git a/service/src/main/java/io/mifos/individuallending/internal/service/ScheduledActionService.java b/service/src/main/java/io/mifos/individuallending/internal/service/ScheduledActionService.java
index 19fe462..0e3c3f6 100644
--- a/service/src/main/java/io/mifos/individuallending/internal/service/ScheduledActionService.java
+++ b/service/src/main/java/io/mifos/individuallending/internal/service/ScheduledActionService.java
@@ -38,8 +38,14 @@
 @Service
 public class ScheduledActionService {
 
-  List<ScheduledAction> getScheduledActions(final @Nonnull LocalDate initialDisbursalDate,
-                                            final @Nonnull CaseParameters caseParameters)
+  List<ScheduledAction> getHypotheticalScheduledActions(final @Nonnull LocalDate initialDisbursalDate,
+                                                                final @Nonnull CaseParameters caseParameters)
+  {
+    return getHypotheticalScheduledActionsHelper(initialDisbursalDate, caseParameters).collect(Collectors.toList());
+  }
+
+  private Stream<ScheduledAction> getHypotheticalScheduledActionsHelper(final @Nonnull LocalDate initialDisbursalDate,
+                                                        final @Nonnull CaseParameters caseParameters)
   {
     //'Rough' end date, because if the repayment period takes the last period after that end date, then the repayment
     // period will 'win'.
@@ -49,10 +55,11 @@
     final Period firstPeriod = repaymentPeriods.first();
     final Period lastPeriod = repaymentPeriods.last();
 
-    return Stream.concat(Stream.of(new ScheduledAction(Action.APPROVE, initialDisbursalDate, firstPeriod, firstPeriod)),
+    return Stream.concat(Stream.of(
+            new ScheduledAction(Action.OPEN, initialDisbursalDate, firstPeriod, firstPeriod),
+            new ScheduledAction(Action.APPROVE, initialDisbursalDate, firstPeriod, firstPeriod)),
             Stream.concat(repaymentPeriods.stream().flatMap(this::generateScheduledActionsForRepaymentPeriod),
-                    Stream.of(new ScheduledAction(Action.CLOSE, lastPeriod.getEndDate(), lastPeriod, lastPeriod))))
-            .collect(Collectors.toList());
+                    Stream.of(new ScheduledAction(Action.CLOSE, lastPeriod.getEndDate(), lastPeriod, lastPeriod))));
   }
 
   private LocalDate getEndDate(final @Nonnull CaseParameters caseParameters,
@@ -238,4 +245,14 @@
     final int maxDay = YearMonth.of(paymentDate.getYear(), paymentDate.getMonth()).lengthOfMonth()-1;
     return paymentDate.plusDays(Math.min(maxDay, alignmentDay));
   }
+
+  public List<ScheduledAction> getScheduledActions(final @Nonnull LocalDate initialDisbursalDate,
+                                                   final CaseParameters caseParameters,
+                                                   final Action action,
+                                                   final LocalDate time) {
+    return getHypotheticalScheduledActionsHelper(initialDisbursalDate, caseParameters)
+            .filter(x -> x.actionPeriod.containsDate(time))
+            .filter(x -> x.action.equals(action))
+            .collect(Collectors.toList());
+  }
 }
\ No newline at end of file
diff --git a/service/src/main/java/io/mifos/portfolio/service/internal/command/handler/InitializeCommandHandler.java b/service/src/main/java/io/mifos/portfolio/service/internal/command/handler/InitializeCommandHandler.java
index 93299b6..ab3eff1 100644
--- a/service/src/main/java/io/mifos/portfolio/service/internal/command/handler/InitializeCommandHandler.java
+++ b/service/src/main/java/io/mifos/portfolio/service/internal/command/handler/InitializeCommandHandler.java
@@ -15,18 +15,15 @@
  */
 package io.mifos.portfolio.service.internal.command.handler;
 
-import io.mifos.core.command.annotation.CommandLogLevel;
-import io.mifos.portfolio.api.v1.events.EventConstants;
-import io.mifos.portfolio.service.ServiceConstants;
-import io.mifos.portfolio.service.internal.command.InitializeServiceCommand;
 import io.mifos.core.command.annotation.Aggregate;
 import io.mifos.core.command.annotation.CommandHandler;
+import io.mifos.core.command.annotation.CommandLogLevel;
 import io.mifos.core.command.annotation.EventEmitter;
 import io.mifos.core.mariadb.domain.FlywayFactoryBean;
+import io.mifos.portfolio.api.v1.events.EventConstants;
+import io.mifos.portfolio.service.internal.command.InitializeServiceCommand;
 import io.mifos.portfolio.service.internal.util.RhythmAdapter;
-import org.slf4j.Logger;
 import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.beans.factory.annotation.Qualifier;
 
 import javax.sql.DataSource;
 
diff --git a/service/src/main/java/io/mifos/portfolio/service/internal/mapper/CaseMapper.java b/service/src/main/java/io/mifos/portfolio/service/internal/mapper/CaseMapper.java
index bc966d7..10c8452 100644
--- a/service/src/main/java/io/mifos/portfolio/service/internal/mapper/CaseMapper.java
+++ b/service/src/main/java/io/mifos/portfolio/service/internal/mapper/CaseMapper.java
@@ -48,7 +48,7 @@
     return ret;
   }
 
-  private static AccountAssignment mapAccountAssignmentEntity(final CaseAccountAssignmentEntity instance) {
+  public static AccountAssignment mapAccountAssignmentEntity(final CaseAccountAssignmentEntity instance) {
     final AccountAssignment ret = new AccountAssignment();
 
     ret.setDesignator(instance.getDesignator());
diff --git a/service/src/main/java/io/mifos/portfolio/service/internal/mapper/ProductMapper.java b/service/src/main/java/io/mifos/portfolio/service/internal/mapper/ProductMapper.java
index d0fa6ed..27389f1 100644
--- a/service/src/main/java/io/mifos/portfolio/service/internal/mapper/ProductMapper.java
+++ b/service/src/main/java/io/mifos/portfolio/service/internal/mapper/ProductMapper.java
@@ -50,7 +50,7 @@
     product.setCurrencyCode(productEntity.getCurrencyCode());
     product.setMinorCurrencyUnitDigits(productEntity.getMinorCurrencyUnitDigits());
     product.setAccountAssignments(productEntity.getAccountAssignments()
-            .stream().map(ProductMapper::map).collect(Collectors.toSet()));
+            .stream().map(ProductMapper::mapAccountAssignmentEntity).collect(Collectors.toSet()));
     product.setParameters(productEntity.getParameters());
     product.setCreatedBy(productEntity.getCreatedBy());
     product.setCreatedOn(DateConverter.toIsoString(productEntity.getCreatedOn()));
@@ -111,7 +111,7 @@
     return ret;
   }
 
-  private static AccountAssignment map (final ProductAccountAssignmentEntity productAccountAssignmentEntity)
+  public static AccountAssignment mapAccountAssignmentEntity (final ProductAccountAssignmentEntity productAccountAssignmentEntity)
   {
     final AccountAssignment ret = new AccountAssignment();
     ret.setDesignator(productAccountAssignmentEntity.getDesignator());
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 c27be3e..11368c1 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,6 +18,8 @@
 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;
 import io.mifos.portfolio.service.internal.repository.CaseEntity;
@@ -33,10 +35,7 @@
 import org.springframework.data.domain.Sort;
 import org.springframework.stereotype.Service;
 
-import java.util.Arrays;
-import java.util.List;
-import java.util.Optional;
-import java.util.Set;
+import java.util.*;
 import java.util.stream.Collectors;
 import java.util.stream.Stream;
 
@@ -48,15 +47,18 @@
   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 CaseRepository caseRepository,
+          final ChargeDefinitionService chargeDefinitionService) {
     this.patternFactoryRegistry = patternFactoryRegistry;
     this.productRepository = productRepository;
     this.caseRepository = caseRepository;
+    this.chargeDefinitionService = chargeDefinitionService;
   }
 
   public CasePage findAllEntities(final String productIdentifier,
@@ -121,4 +123,18 @@
   public boolean existsByProductIdentifier(final String productIdentifier) {
     return caseRepository.existsByProductIdentifier(productIdentifier);
   }
+
+  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());
+
+  }
 }
\ 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 9d33487..f802c51 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,17 +18,29 @@
 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.core.api.util.UserContextHolder;
+import io.mifos.core.lang.DateConverter;
 import io.mifos.portfolio.api.v1.domain.AccountAssignment;
 import io.mifos.portfolio.api.v1.domain.ChargeDefinition;
+import org.apache.commons.lang.RandomStringUtils;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Component;
 
+import java.math.BigDecimal;
+import java.time.LocalDateTime;
 import java.util.Collection;
 import java.util.List;
+import java.util.Optional;
 import java.util.Set;
 import java.util.stream.Collectors;
 import java.util.stream.Stream;
 
+import static io.mifos.individuallending.api.v1.domain.product.AccountDesignators.ENTRY;
+
 /**
  * @author Myrle Krantz
  */
@@ -44,6 +56,59 @@
     this.ledgerManager = ledgerManager;
   }
 
+  public void bookCharges(final List<ChargeInstance> costComponents,
+                          final String note,
+                          final String message,
+                          final String transactionType) {
+    final Set<Creditor> creditors = costComponents.stream()
+            .map(AccountingAdapter::mapToCreditor)
+            .filter(Optional::isPresent)
+            .map(Optional::get)
+            .collect(Collectors.toSet());
+    final Set<Debtor> debtors = costComponents.stream()
+            .map(AccountingAdapter::mapToDebtor)
+            .filter(Optional::isPresent)
+            .map(Optional::get)
+            .collect(Collectors.toSet());
+
+    final JournalEntry journalEntry = new JournalEntry();
+    journalEntry.setCreditors(creditors);
+    journalEntry.setDebtors(debtors);
+    journalEntry.setClerk(UserContextHolder.checkedGetUser());
+    journalEntry.setTransactionDate(DateConverter.toIsoString(LocalDateTime.now()));
+    journalEntry.setMessage(message);
+    journalEntry.setTransactionType(transactionType);
+    journalEntry.setNote(note);
+    journalEntry.setTransactionIdentifier("bastet" + RandomStringUtils.random(26, true, true));
+
+    ledgerManager.createJournalEntry(journalEntry);
+  }
+
+  private static Optional<Debtor> mapToDebtor(final ChargeInstance chargeInstance) {
+    if (chargeInstance.getAmount().compareTo(BigDecimal.ZERO) == 0)
+      return Optional.empty();
+
+    final Debtor ret = new Debtor();
+    ret.setAccountNumber(chargeInstance.getFromAccount());
+    ret.setAmount(chargeInstance.getAmount().toPlainString());
+    return Optional.of(ret);
+  }
+
+  private static Optional<Creditor> mapToCreditor(final ChargeInstance chargeInstance) {
+    if (chargeInstance.getAmount().compareTo(BigDecimal.ZERO) == 0)
+      return Optional.empty();
+
+    final Creditor ret = new Creditor();
+    ret.setAccountNumber(chargeInstance.getToAccount());
+    ret.setAmount(chargeInstance.getAmount().toPlainString());
+    return Optional.of(ret);
+  }
+
+  public BigDecimal getCurrentBalance(final String accountIdentifier) {
+    final Account account = ledgerManager.findAccount(accountIdentifier);
+    return BigDecimal.valueOf(account.getBalance());
+  }
+
 
   public static boolean accountAssignmentsCoverChargeDefinitions(
           final Set<AccountAssignment> accountAssignments,
@@ -57,11 +122,26 @@
 
   public static Set<String> getRequiredAccountDesignators(final Collection<ChargeDefinition> chargeDefinitionEntities) {
     return chargeDefinitionEntities.stream()
-            .flatMap(x -> Stream.of(x.getAccrualAccountDesignator(), x.getFromAccountDesignator(), x.getToAccountDesignator()))
+            .flatMap(AccountingAdapter::getAutomaticActionAccountDesignators)
             .filter(x -> x != null)
             .collect(Collectors.toSet());
   }
 
+  private static Stream<String> getAutomaticActionAccountDesignators(final ChargeDefinition chargeDefinition) {
+    final Stream.Builder<String> retBuilder = Stream.builder();
+
+    checkAddDesignator(chargeDefinition.getFromAccountDesignator(), retBuilder);
+    checkAddDesignator(chargeDefinition.getAccrualAccountDesignator(), retBuilder);
+    checkAddDesignator(chargeDefinition.getToAccountDesignator(), retBuilder);
+
+    return retBuilder.build();
+  }
+
+  private static void checkAddDesignator(final String accountDesignator, final Stream.Builder<String> retBuilder) {
+    if (accountDesignator != null && !accountDesignator.equals(ENTRY))
+      retBuilder.add(accountDesignator);
+  }
+
   public boolean accountAssignmentsRepresentRealAccounts(final Set<AccountAssignment> accountAssignments)
   {
     return accountAssignments.stream().allMatch(this::accountAssignmentRepresentsRealAccount);
diff --git a/service/src/main/java/io/mifos/portfolio/service/internal/util/ChargeInstance.java b/service/src/main/java/io/mifos/portfolio/service/internal/util/ChargeInstance.java
new file mode 100644
index 0000000..0056c84
--- /dev/null
+++ b/service/src/main/java/io/mifos/portfolio/service/internal/util/ChargeInstance.java
@@ -0,0 +1,72 @@
+/*
+ * 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.portfolio.service.internal.util;
+
+import java.math.BigDecimal;
+import java.util.Objects;
+
+/**
+ * @author Myrle Krantz
+ */
+public class ChargeInstance {
+  private final String fromAccount;
+  private final String toAccount;
+  private final BigDecimal amount;
+
+  public ChargeInstance(final String fromAccount,
+                        final String toAccount,
+                        final BigDecimal amount) {
+    this.fromAccount = fromAccount;
+    this.toAccount = toAccount;
+    this.amount = amount;
+  }
+
+  public String getFromAccount() {
+    return fromAccount;
+  }
+
+  public String getToAccount() {
+    return toAccount;
+  }
+
+  public BigDecimal getAmount() {
+    return amount;
+  }
+
+  @Override
+  public boolean equals(Object o) {
+    if (this == o) return true;
+    if (o == null || getClass() != o.getClass()) return false;
+    ChargeInstance that = (ChargeInstance) o;
+    return Objects.equals(fromAccount, that.fromAccount) &&
+            Objects.equals(toAccount, that.toAccount) &&
+            Objects.equals(amount, that.amount);
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hash(fromAccount, toAccount, amount);
+  }
+
+  @Override
+  public String toString() {
+    return "ChargeInstance{" +
+            "fromAccount='" + fromAccount + '\'' +
+            ", toAccount='" + toAccount + '\'' +
+            ", amount=" + amount +
+            '}';
+  }
+}
diff --git a/service/src/main/java/io/mifos/portfolio/service/rest/CaseRestController.java b/service/src/main/java/io/mifos/portfolio/service/rest/CaseRestController.java
index 22e5a75..8b0eeba 100644
--- a/service/src/main/java/io/mifos/portfolio/service/rest/CaseRestController.java
+++ b/service/src/main/java/io/mifos/portfolio/service/rest/CaseRestController.java
@@ -37,7 +37,6 @@
 import org.springframework.web.bind.annotation.*;
 
 import javax.validation.Valid;
-import java.util.Collections;
 import java.util.List;
 import java.util.Optional;
 import java.util.Set;
@@ -191,8 +190,7 @@
   {
     checkThatCaseExists(productIdentifier, caseIdentifier);
 
-    return Collections.emptyList();
-    //return caseService.getActionCostComponentsForCase(productIdentifier, caseIdentifier);
+    return caseService.getActionCostComponentsForCase(productIdentifier, caseIdentifier, actionIdentifier);
   }
 
   @Permittable(value = AcceptedTokenType.TENANT, groupId = PermittableGroupIds.CASE_MANAGEMENT)
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 59032e6..08f2c5c 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
@@ -15,7 +15,7 @@
  */
 package io.mifos.individuallending.internal.service;
 
-import io.mifos.portfolio.api.v1.domain.*;
+import io.mifos.individuallending.IndividualLendingPatternFactory;
 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;
@@ -23,9 +23,10 @@
 import io.mifos.individuallending.api.v1.domain.product.AccountDesignators;
 import io.mifos.individuallending.api.v1.domain.product.ChargeIdentifiers;
 import io.mifos.individuallending.api.v1.domain.workflow.Action;
-import io.mifos.individuallending.IndividualLendingPatternFactory;
+import io.mifos.portfolio.api.v1.domain.*;
 import io.mifos.portfolio.service.internal.service.ChargeDefinitionService;
 import io.mifos.portfolio.service.internal.service.ProductService;
+import io.mifos.portfolio.service.internal.util.ChargeInstance;
 import org.junit.Assert;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -39,11 +40,55 @@
 import java.util.stream.Collectors;
 import java.util.stream.Stream;
 
+import static io.mifos.individuallending.api.v1.domain.product.ChargeIdentifiers.PROCESSING_FEE_ID;
+
 /**
  * @author Myrle Krantz
  */
 @RunWith(Parameterized.class)
 public class IndividualLoanServiceTest {
+
+  private static class ActionDatePair {
+    final Action action;
+    final LocalDate localDate;
+
+    ActionDatePair(final Action action, final LocalDate localDate) {
+      this.action = action;
+      this.localDate = localDate;
+    }
+
+    Action getAction() {
+      return action;
+    }
+
+    LocalDate getLocalDate() {
+      return localDate;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+      if (this == o) return true;
+      if (o == null || getClass() != o.getClass()) return false;
+      ActionDatePair that = (ActionDatePair) o;
+      return action == that.action &&
+              Objects.equals(localDate, that.localDate);
+    }
+
+    @Override
+    public int hashCode() {
+      return Objects.hash(action, localDate);
+    }
+
+    @Override
+    public String toString() {
+      return "ActionDatePair{" +
+              "action=" + action +
+              ", localDate=" + localDate +
+              '}';
+    }
+  }
+
+
   private static class TestCase {
     private final String description;
     private String productIdentifier = "blah";
@@ -52,6 +97,7 @@
     private LocalDate initialDisbursementDate;
     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<>();
 
     TestCase(final String description) {
       this.description = description;
@@ -82,6 +128,18 @@
       return this;
     }
 
+    TestCase expectAdditionalChargeIdentifier(final String newVal) {
+      this.expectedChargeIdentifiers.add(newVal);
+      return this;
+    }
+
+    TestCase expectChargeInstancesForActionDatePair(final Action action,
+                                                    final LocalDate forDate,
+                                                    final List<ChargeInstance> chargeInstances) {
+      this.chargeInstancesForActions.put(new ActionDatePair(action, forDate), chargeInstances);
+      return this;
+    }
+
     @Override
     public String toString() {
       return "TestCase{" +
@@ -98,7 +156,10 @@
     ret.add(chargeDefaultsCase());
     return ret;
   }
+
   private final TestCase testCase;
+  private final IndividualLoanService testSubject;
+  private final Product product;
 
 
   private static TestCase simpleCase()
@@ -113,12 +174,20 @@
 
     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));
 
     return new TestCase("simpleCase")
             .minorCurrencyUnitDigits(2)
             .caseParameters(caseParameters)
             .initialDisbursementDate(initialDisbursementDate)
-            .chargeDefinitionsMappedByAction(chargeDefinitionsMappedByAction);
+            .chargeDefinitionsMappedByAction(chargeDefinitionsMappedByAction)
+            .expectAdditionalChargeIdentifier(PROCESSING_FEE_ID)
+            .expectChargeInstancesForActionDatePair(Action.OPEN, initialDisbursementDate,
+                    Collections.singletonList(new ChargeInstance(
+                            AccountDesignators.ENTRY,
+                            AccountDesignators.PROCESSING_FEE_INCOME,
+                            BigDecimal.valueOf(10).setScale(2, BigDecimal.ROUND_UNNECESSARY))));
   }
 
   private static TestCase yearLoanTestCase()
@@ -159,7 +228,7 @@
             .caseParameters(caseParameters)
             .initialDisbursementDate(initialDisbursementDate)
             .chargeDefinitionsMappedByAction(chargeDefinitionsMappedByAction)
-            .expectedChargeIdentifiers(new HashSet<>(Arrays.asList(ChargeIdentifiers.RETURN_DISBURSEMENT_ID, ChargeIdentifiers.LOAN_ORIGINATION_FEE_ID, ChargeIdentifiers.INTEREST_ID, ChargeIdentifiers.PAYMENT_ID)));
+            .expectedChargeIdentifiers(new HashSet<>(Arrays.asList(PROCESSING_FEE_ID, ChargeIdentifiers.RETURN_DISBURSEMENT_ID, ChargeIdentifiers.LOAN_ORIGINATION_FEE_ID, ChargeIdentifiers.INTEREST_ID, ChargeIdentifiers.PAYMENT_ID)));
   }
 
   private static List<ChargeDefinition> getInterestChargeDefinition(final double amount, final ChronoUnit forCycleSizeUnit) {
@@ -176,21 +245,39 @@
     return Collections.singletonList(ret);
   }
 
+  private static List<ChargeDefinition> getFixedSingleChargeDefinition(
+          final double amount,
+          final Action action,
+          final String chargeIdentifier,
+          final String feeAccountDesignator) {
+    final ChargeDefinition ret = new ChargeDefinition();
+    ret.setAmount(BigDecimal.valueOf(amount));
+    ret.setIdentifier(chargeIdentifier);
+    ret.setAccrueAction(null);
+    ret.setChargeAction(action.name());
+    ret.setChargeMethod(ChargeDefinition.ChargeMethod.FIXED);
+    ret.setFromAccountDesignator(AccountDesignators.ENTRY);
+    ret.setToAccountDesignator(feeAccountDesignator);
+    ret.setForCycleSizeUnit(null);
+    return Collections.singletonList(ret);
+  }
+
   public IndividualLoanServiceTest(final TestCase testCase)
   {
     this.testCase = testCase;
-  }
 
-  @Test
-  public void getPlannedPayments() throws Exception {
     final ProductService productServiceMock = Mockito.mock(ProductService.class);
     final ChargeDefinitionService chargeDefinitionServiceMock = Mockito.mock(ChargeDefinitionService.class);
-    final Product product = new Product();
+    product = new Product();
     product.setMinorCurrencyUnitDigits(testCase.minorCurrencyUnitDigits);
     Mockito.doReturn(Optional.of(product)).when(productServiceMock).findByIdentifier(testCase.productIdentifier);
     Mockito.doReturn(testCase.chargeDefinitionsMappedByAction).when(chargeDefinitionServiceMock).getChargeDefinitionsMappedByChargeAction(testCase.productIdentifier);
 
-    final IndividualLoanService testSubject = new IndividualLoanService(productServiceMock, chargeDefinitionServiceMock, new ScheduledActionService(), new PeriodChargeCalculator());
+    testSubject = new IndividualLoanService(productServiceMock, chargeDefinitionServiceMock, new ScheduledActionService(), new PeriodChargeCalculator());
+  }
+
+  @Test
+  public void getPlannedPayments() throws Exception {
     final PlannedPaymentPage firstPage = testSubject.getPlannedPaymentsPage(testCase.productIdentifier,
             testCase.caseParameters,
             0,
@@ -246,6 +333,19 @@
     Assert.assertEquals(testCase.expectedChargeIdentifiers, resultChargeIdentifiers);
   }
 
+  @Test
+  public void createChargeInstances() {
+    testCase.chargeInstancesForActions.entrySet().forEach(entry ->
+            Assert.assertEquals(
+                    entry.getValue(),
+                    testSubject.getChargeInstances(
+                            testCase.productIdentifier,
+                            testCase.caseParameters,
+                            testCase.caseParameters.getMaximumBalance(),
+                            entry.getKey().getAction(),
+                            testCase.initialDisbursementDate, entry.getKey().getLocalDate())));
+  }
+
   private double percentDifference(final BigDecimal maxPayment, final BigDecimal minPayment) {
     final BigDecimal difference = maxPayment.subtract(minPayment);
     final BigDecimal percentDifference = difference.divide(maxPayment, 4, BigDecimal.ROUND_UP);
diff --git a/service/src/test/java/io/mifos/individuallending/internal/service/PeriodChargeCalculatorTest.java b/service/src/test/java/io/mifos/individuallending/internal/service/PeriodChargeCalculatorTest.java
index 3850062..8571638 100644
--- a/service/src/test/java/io/mifos/individuallending/internal/service/PeriodChargeCalculatorTest.java
+++ b/service/src/test/java/io/mifos/individuallending/internal/service/PeriodChargeCalculatorTest.java
@@ -154,7 +154,7 @@
   public void test()
   {
     final PeriodChargeCalculator testSubject = new PeriodChargeCalculator();
-    final Map<Period, BigDecimal> periodRates = testSubject.getPeriodRates(testCase.scheduledCharges, testCase.precision);
+    final Map<Period, BigDecimal> periodRates = testSubject.getPeriodAccrualRates(testCase.scheduledCharges, testCase.precision);
     Assert.assertEquals(testCase.expectedPeriodRates, periodRates);
   }
 }
diff --git a/service/src/test/java/io/mifos/individuallending/internal/service/PeriodTest.java b/service/src/test/java/io/mifos/individuallending/internal/service/PeriodTest.java
new file mode 100644
index 0000000..0235e48
--- /dev/null
+++ b/service/src/test/java/io/mifos/individuallending/internal/service/PeriodTest.java
@@ -0,0 +1,73 @@
+/*
+ * 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 org.junit.Assert;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import java.time.LocalDate;
+import java.time.ZoneId;
+
+/**
+ * @author Myrle Krantz
+ */
+public class PeriodTest {
+  private static LocalDate today;
+  private static LocalDate yesterday;
+  private static LocalDate tommorrow;
+  private static LocalDate dayAfterTommorrow;
+
+  @BeforeClass
+  public static void prepare() {
+    today = LocalDate.now(ZoneId.of("UTC"));
+    yesterday = today.minusDays(1);
+    tommorrow = today.plusDays(1);
+    dayAfterTommorrow = tommorrow.plusDays(1);
+  }
+
+  @Test
+  public void getDuration() throws Exception {
+    final Period testSubjectByDates = new Period(today, dayAfterTommorrow);
+    Assert.assertEquals(2, testSubjectByDates.getDuration().toDays());
+
+    final Period testSubjectByDuration = new Period(today, 5);
+    Assert.assertEquals(5, testSubjectByDuration.getDuration().toDays());
+  }
+
+  @Test
+  public void containsDate() throws Exception {
+    final Period testSubject = new Period(today, 1);
+
+    Assert.assertTrue(testSubject.containsDate(today));
+    Assert.assertFalse(testSubject.containsDate(tommorrow));
+    Assert.assertFalse(testSubject.containsDate(yesterday));
+    Assert.assertFalse(testSubject.containsDate(dayAfterTommorrow));
+
+  }
+
+  @Test
+  public void compareTo() throws Exception {
+    final Period yesterdayPeriod = new Period(yesterday, today);
+    final Period todayPeriod = new Period(today, tommorrow);
+    final Period tommorrowPeriod = new Period(tommorrow, dayAfterTommorrow);
+
+    Assert.assertTrue(yesterdayPeriod.compareTo(todayPeriod) < 0);
+    Assert.assertTrue(todayPeriod.compareTo(todayPeriod) == 0);
+    Assert.assertTrue(tommorrowPeriod.compareTo(todayPeriod) > 0);
+  }
+
+}
\ No newline at end of file
diff --git a/service/src/test/java/io/mifos/individuallending/internal/service/ScheduledActionServiceTest.java b/service/src/test/java/io/mifos/individuallending/internal/service/ScheduledActionServiceTest.java
index 85cfa30..1cc2fb6 100644
--- a/service/src/test/java/io/mifos/individuallending/internal/service/ScheduledActionServiceTest.java
+++ b/service/src/test/java/io/mifos/individuallending/internal/service/ScheduledActionServiceTest.java
@@ -363,7 +363,7 @@
   @Test
   public void getScheduledActions() throws Exception {
     final ScheduledActionService testSubject = new ScheduledActionService();
-    final List<ScheduledAction> result = testSubject.getScheduledActions(testCase.initialDisbursementDate, testCase.caseParameters);
+    final List<ScheduledAction> result = testSubject.getHypotheticalScheduledActions(testCase.initialDisbursementDate, testCase.caseParameters);
 
     Assert.assertTrue(testCase.description, result.containsAll(testCase.expectedResultContents));
     result.forEach(x -> {