* Split costumer loan account into: principal, interest and fees.
* Adjusted charges accordingly.
* Fixed overflowing account and ledger identifiers by shortening account
  designators, and truncating customer name.
* Added some unit tests.
diff --git a/api/src/main/java/io/mifos/individuallending/api/v1/domain/product/AccountDesignators.java b/api/src/main/java/io/mifos/individuallending/api/v1/domain/product/AccountDesignators.java
index 912ef35..3fd48ff 100644
--- a/api/src/main/java/io/mifos/individuallending/api/v1/domain/product/AccountDesignators.java
+++ b/api/src/main/java/io/mifos/individuallending/api/v1/domain/product/AccountDesignators.java
@@ -20,15 +20,21 @@
  */
 @SuppressWarnings("unused")
 public interface AccountDesignators {
-  String CUSTOMER_LOAN = "customer-loan";
-  String LOAN_FUNDS_SOURCE = "loan-funds-source";
-  String PROCESSING_FEE_INCOME = "processing-fee-income";
-  String ORIGINATION_FEE_INCOME = "origination-fee-income";
-  String DISBURSEMENT_FEE_INCOME = "disbursement-fee-income";
-  String INTEREST_INCOME = "interest-income";
-  String INTEREST_ACCRUAL = "interest-accrual";
-  String LATE_FEE_INCOME = "late-fee-income";
-  String LATE_FEE_ACCRUAL = "late-fee-accrual";
-  String ARREARS_ALLOWANCE = "arrears-allowance";
-  String ENTRY = "entry";
+  //These are maximum 3 characters because they are used to create account and ledger identifiers.
+  //Account and ledger identifiers are limited to 34 characters, and 32 characters respectively.
+  //These accounting identifiers are composed of the customer identifier, this identifier, and a counter.
+  String CUSTOMER_LOAN_GROUP = "cll";
+  String CUSTOMER_LOAN_PRINCIPAL = "clp";
+  String CUSTOMER_LOAN_INTEREST = "cli";
+  String CUSTOMER_LOAN_FEES = "clf";
+  String LOAN_FUNDS_SOURCE = "ls";
+  String PROCESSING_FEE_INCOME = "pfi";
+  String ORIGINATION_FEE_INCOME = "ofi";
+  String DISBURSEMENT_FEE_INCOME = "dfi";
+  String INTEREST_INCOME = "ii";
+  String INTEREST_ACCRUAL = "ia";
+  String LATE_FEE_INCOME = "lfi";
+  String LATE_FEE_ACCRUAL = "lfa";
+  String ARREARS_ALLOWANCE = "aa";
+  String ENTRY = "ey";
 }
diff --git a/api/src/main/java/io/mifos/individuallending/api/v1/domain/product/ChargeIdentifiers.java b/api/src/main/java/io/mifos/individuallending/api/v1/domain/product/ChargeIdentifiers.java
index f9fbbd9..2f570ac 100644
--- a/api/src/main/java/io/mifos/individuallending/api/v1/domain/product/ChargeIdentifiers.java
+++ b/api/src/main/java/io/mifos/individuallending/api/v1/domain/product/ChargeIdentifiers.java
@@ -36,8 +36,12 @@
   String LOAN_ORIGINATION_FEE_ID = "loan-origination-fee";
   String PROCESSING_FEE_NAME = "Processing fee";
   String PROCESSING_FEE_ID = "processing-fee";
-  String REPAYMENT_NAME = "Repayment";
-  String REPAYMENT_ID = "repayment";
+  String REPAY_PRINCIPAL_NAME = "Repay principal";
+  String REPAY_PRINCIPAL_ID = "repay-principal";
+  String REPAY_INTEREST_NAME = "Repay interest";
+  String REPAY_INTEREST_ID = "repay-interest";
+  String REPAY_FEES_NAME = "Repay fees";
+  String REPAY_FEES_ID = "repay-fees";
 
   static String nameToIdentifier(String name) {
     return name.toLowerCase(Locale.US).replace(" ", "-");
diff --git a/api/src/main/java/io/mifos/portfolio/api/v1/domain/Pattern.java b/api/src/main/java/io/mifos/portfolio/api/v1/domain/Pattern.java
index 66e7314..55381ce 100644
--- a/api/src/main/java/io/mifos/portfolio/api/v1/domain/Pattern.java
+++ b/api/src/main/java/io/mifos/portfolio/api/v1/domain/Pattern.java
@@ -15,6 +15,7 @@
  */
 package io.mifos.portfolio.api.v1.domain;
 
+import java.util.Objects;
 import java.util.Set;
 
 /**
@@ -23,16 +24,12 @@
 @SuppressWarnings({"WeakerAccess", "unused"})
 public class Pattern {
   private String parameterPackage;
-  private Set<String> accountAssignmentsRequired;
+  private Set<String> accountAssignmentGroups;
+  private Set<RequiredAccountAssignment> accountAssignmentsRequired;
 
   public Pattern() {
   }
 
-  public Pattern(String parametersNameSpace, Set<String> accountAssignmentsRequired) {
-    this.parameterPackage = parametersNameSpace;
-    this.accountAssignmentsRequired = accountAssignmentsRequired;
-  }
-
   public String getParameterPackage() {
     return parameterPackage;
   }
@@ -41,11 +38,19 @@
     this.parameterPackage = parameterPackage;
   }
 
-  public Set<String> getAccountAssignmentsRequired() {
+  public Set<String> getAccountAssignmentGroups() {
+    return accountAssignmentGroups;
+  }
+
+  public void setAccountAssignmentGroups(Set<String> accountAssignmentGroups) {
+    this.accountAssignmentGroups = accountAssignmentGroups;
+  }
+
+  public Set<RequiredAccountAssignment> getAccountAssignmentsRequired() {
     return accountAssignmentsRequired;
   }
 
-  public void setAccountAssignmentsRequired(Set<String> accountAssignmentsRequired) {
+  public void setAccountAssignmentsRequired(Set<RequiredAccountAssignment> accountAssignmentsRequired) {
     this.accountAssignmentsRequired = accountAssignmentsRequired;
   }
 
@@ -53,25 +58,23 @@
   public boolean equals(Object o) {
     if (this == o) return true;
     if (o == null || getClass() != o.getClass()) return false;
-
     Pattern pattern = (Pattern) o;
-
-    return parameterPackage != null ? parameterPackage.equals(pattern.parameterPackage) : pattern.parameterPackage == null && (accountAssignmentsRequired != null ? accountAssignmentsRequired.equals(pattern.accountAssignmentsRequired) : pattern.accountAssignmentsRequired == null);
-
+    return Objects.equals(parameterPackage, pattern.parameterPackage) &&
+        Objects.equals(accountAssignmentGroups, pattern.accountAssignmentGroups) &&
+        Objects.equals(accountAssignmentsRequired, pattern.accountAssignmentsRequired);
   }
 
   @Override
   public int hashCode() {
-    int result = parameterPackage != null ? parameterPackage.hashCode() : 0;
-    result = 31 * result + (accountAssignmentsRequired != null ? accountAssignmentsRequired.hashCode() : 0);
-    return result;
+    return Objects.hash(parameterPackage, accountAssignmentGroups, accountAssignmentsRequired);
   }
 
   @Override
   public String toString() {
     return "Pattern{" +
-            "parameterPackage='" + parameterPackage + '\'' +
-            ", accountAssignmentsRequired=" + accountAssignmentsRequired +
-            '}';
+        "parameterPackage='" + parameterPackage + '\'' +
+        ", accountAssignmentGroups=" + accountAssignmentGroups +
+        ", accountAssignmentsRequired=" + accountAssignmentsRequired +
+        '}';
   }
 }
diff --git a/api/src/main/java/io/mifos/portfolio/api/v1/domain/RequiredAccountAssignment.java b/api/src/main/java/io/mifos/portfolio/api/v1/domain/RequiredAccountAssignment.java
new file mode 100644
index 0000000..1cea505
--- /dev/null
+++ b/api/src/main/java/io/mifos/portfolio/api/v1/domain/RequiredAccountAssignment.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright 2017 Kuelap, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.mifos.portfolio.api.v1.domain;
+
+import javax.annotation.Nullable;
+import java.util.Objects;
+
+@SuppressWarnings("unused")
+public class RequiredAccountAssignment {
+  private String accountDesignator;
+  private String accountType;
+  private @Nullable String group;
+
+  public RequiredAccountAssignment(String accountDesignator, String accountType) {
+    this.accountDesignator = accountDesignator;
+    this.accountType = accountType;
+    this.group = null;
+  }
+
+  public RequiredAccountAssignment(String accountDesignator, String accountType, String ledger) {
+    this.accountDesignator = accountDesignator;
+    this.accountType = accountType;
+    this.group = ledger;
+  }
+
+  public String getAccountDesignator() {
+    return accountDesignator;
+  }
+
+  public void setAccountDesignator(String accountDesignator) {
+    this.accountDesignator = accountDesignator;
+  }
+
+  public String getAccountType() {
+    return accountType;
+  }
+
+  public void setAccountType(String thothType) {
+    this.accountType = thothType;
+  }
+
+  public String getGroup() {
+    return group;
+  }
+
+  public void setGroup(String group) {
+    this.group = group;
+  }
+
+  @Override
+  public boolean equals(Object o) {
+    if (this == o) return true;
+    if (o == null || getClass() != o.getClass()) return false;
+    RequiredAccountAssignment that = (RequiredAccountAssignment) o;
+    return Objects.equals(accountDesignator, that.accountDesignator) &&
+        Objects.equals(accountType, that.accountType) &&
+        Objects.equals(group, that.group);
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hash(accountDesignator, accountType, group);
+  }
+
+  @Override
+  public String toString() {
+    return "RequiredAccountAssignment{" +
+        "accountDesignator='" + accountDesignator + '\'' +
+        ", accountType='" + accountType + '\'' +
+        ", group='" + group + '\'' +
+        '}';
+  }
+}
diff --git a/api/src/test/java/io/mifos/Fixture.java b/api/src/test/java/io/mifos/Fixture.java
index a6f1435..55fe883 100644
--- a/api/src/test/java/io/mifos/Fixture.java
+++ b/api/src/test/java/io/mifos/Fixture.java
@@ -67,7 +67,7 @@
     //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"));
+    accountAssignments.add(new AccountAssignment(CUSTOMER_LOAN_GROUP, "001-013"));
     product.setAccountAssignments(accountAssignments);
 
     final ProductParameters productParameters = new ProductParameters();
@@ -104,7 +104,7 @@
 
 
     final Set<AccountAssignment> accountAssignments = new HashSet<>();
-    accountAssignments.add(new AccountAssignment(CUSTOMER_LOAN, "001-011"));
+    accountAssignments.add(new AccountAssignment(CUSTOMER_LOAN_GROUP, "001-011"));
     accountAssignments.add(new AccountAssignment(ENTRY, "001-012"));
     ret.setAccountAssignments(accountAssignments);
     ret.setCurrentState(Case.State.CREATED.name());
diff --git a/component-test/src/main/java/io/mifos/portfolio/AccountingFixture.java b/component-test/src/main/java/io/mifos/portfolio/AccountingFixture.java
index 7df57bf..feaee49 100644
--- a/component-test/src/main/java/io/mifos/portfolio/AccountingFixture.java
+++ b/component-test/src/main/java/io/mifos/portfolio/AccountingFixture.java
@@ -21,6 +21,7 @@
 import io.mifos.core.lang.DateConverter;
 import io.mifos.individuallending.api.v1.domain.workflow.Action;
 import org.hamcrest.Description;
+import org.junit.Assert;
 import org.mockito.AdditionalMatchers;
 import org.mockito.ArgumentMatcher;
 import org.mockito.Matchers;
@@ -100,6 +101,15 @@
         .fetchAccountEntriesStream(Mockito.eq(account.getIdentifier()), Matchers.anyString(), Matchers.anyString(), AdditionalMatchers.or(Matchers.eq("DESC"), Matchers.eq("ASC")));
   }
 
+  private static void makeLedgerResponsive(
+      final Ledger ledger,
+      final LedgerManager ledgerManagerMock)
+  {
+    Mockito.doReturn(ledger).when(ledgerManagerMock).findLedger(ledger.getIdentifier());
+    Mockito.doReturn(emptyAccountsPage()).when(ledgerManagerMock).fetchAccountsOfLedger(Mockito.eq(ledger.getIdentifier()),
+        Mockito.any(), Mockito.any(), Mockito.any(), Mockito.any());
+  }
+
 
   private static Ledger cashLedger() {
     final Ledger ret = new Ledger();
@@ -231,7 +241,7 @@
   private static Account arrearsAllowanceAccount() {
     final Account ret = new Account();
     ret.setIdentifier(ARREARS_ALLOWANCE_ACCOUNT_IDENTIFIER);
-    //ret.setLedger(LOAN_INCOME_LEDGER_IDENTIFIER); //TODO: ??
+    //ret.setGroup(LOAN_INCOME_LEDGER_IDENTIFIER); //TODO: ??
     ret.setType(AccountType.LIABILITY.name()); //TODO: ??
     return ret;
   }
@@ -251,6 +261,14 @@
     return ret;
   }
 
+  private static AccountPage emptyAccountsPage() {
+    final AccountPage ret = new AccountPage();
+    ret.setTotalElements(0L);
+    ret.setTotalPages(1);
+    ret.setAccounts(Collections.emptyList());
+    return ret;
+  }
+
   private static <T> Valid<T> isValid() {
     return new Valid<>();
   }
@@ -269,11 +287,16 @@
 
   private static class AccountMatcher extends ArgumentMatcher<Account> {
     private final String ledgerIdentifer;
+    private final String accountDesignator;
     private final AccountType type;
     private Account matchedArgument;
 
-    private AccountMatcher(final String ledgerIdentifier, final AccountType type) {
+    private AccountMatcher(
+        final String ledgerIdentifier,
+        final String accountDesignator,
+        final AccountType type) {
       this.ledgerIdentifer = ledgerIdentifier;
+      this.accountDesignator = accountDesignator;
       this.type = type;
       this.matchedArgument = null; //Set when matches called and returns true.
     }
@@ -288,6 +311,7 @@
       final Account checkedArgument = (Account) argument;
 
       final boolean ret = checkedArgument.getLedger().equals(ledgerIdentifer) &&
+          checkedArgument.getIdentifier().contains(accountDesignator) &&
           checkedArgument.getType().equals(type.name()) &&
           checkedArgument.getBalance() == 0.0;
 
@@ -297,9 +321,75 @@
       return ret;
     }
 
+    @Override
+    public void describeTo(final Description description) {
+      description.appendText(this.toString());
+    }
+
     Account getMatchedArgument() {
       return matchedArgument;
     }
+
+    @Override
+    public String toString() {
+      return "AccountMatcher{" +
+          "ledgerIdentifer='" + ledgerIdentifer + '\'' +
+          ", accountDesignator='" + accountDesignator + '\'' +
+          ", type=" + type +
+          '}';
+    }
+  }
+
+  private static class LedgerMatcher extends ArgumentMatcher<Ledger> {
+    private final String ledgerIdentifer;
+    private final AccountType type;
+    private Ledger matchedArgument;
+
+    LedgerMatcher(String ledgerIdentifier, AccountType type) {
+      this.ledgerIdentifer = ledgerIdentifier;
+      this.type = type;
+      this.matchedArgument = null; //Set when matches called and returns true.
+    }
+
+    @Override
+    public boolean matches(final Object argument) {
+      if (argument == null)
+        return false;
+      if (! (argument instanceof Ledger))
+        return false;
+
+      final Ledger checkedArgument = (Ledger) argument;
+
+      final Validator validator = Validation.buildDefaultValidatorFactory().getValidator();
+      final Set errors = validator.validate(checkedArgument);
+
+      Assert.assertEquals(0, errors.size());
+
+      final boolean ret = checkedArgument.getParentLedgerIdentifier().equals(ledgerIdentifer) &&
+          checkedArgument.getType().equals(type.name());
+
+      if (ret)
+        matchedArgument = checkedArgument;
+
+      return ret;
+    }
+
+    @Override
+    public void describeTo(final Description description) {
+      description.appendText(this.toString());
+    }
+
+    Ledger getMatchedArgument() {
+      return matchedArgument;
+    }
+
+    @Override
+    public String toString() {
+      return "LedgerMatcher{" +
+          "ledgerIdentifer='" + ledgerIdentifer + '\'' +
+          ", type=" + type +
+          '}';
+    }
   }
 
   private static class JournalEntryMatcher extends ArgumentMatcher<JournalEntry> {
@@ -386,6 +476,15 @@
     }
   }
 
+  private static class CreateLedgerAnswer implements Answer {
+    @Override
+    public Void answer(final InvocationOnMock invocation) throws Throwable {
+      final Ledger ledger = invocation.getArgumentAt(0, Ledger.class);
+      makeLedgerResponsive(ledger, (LedgerManager) invocation.getMock());
+      return null;
+    }
+  }
+
   static class AccountEntriesStreamAnswer implements Answer {
     private final AccountData accountData;
 
@@ -437,20 +536,33 @@
     Mockito.doAnswer(new FindAccountAnswer()).when(ledgerManagerMock).findAccount(Matchers.anyString());
     Mockito.doAnswer(new CreateAccountAnswer()).when(ledgerManagerMock).createAccount(Matchers.any());
     Mockito.doAnswer(new CreateJournalEntryAnswer()).when(ledgerManagerMock).createJournalEntry(Matchers.any(JournalEntry.class));
+    Mockito.doAnswer(new CreateLedgerAnswer()).when(ledgerManagerMock).createLedger(Matchers.any(Ledger.class));
   }
 
   static void mockBalance(final String accountIdentifier, final BigDecimal balance) {
     accountMap.get(accountIdentifier).setBalance(balance.doubleValue());
   }
 
-  static String verifyAccountCreation(final LedgerManager ledgerManager,
-                                      final String ledgerIdentifier,
-                                      final AccountType type) {
-    final AccountMatcher specifiesCorrectAccount = new AccountMatcher(ledgerIdentifier, type);
+  static String verifyAccountCreationMatchingDesignator(
+      final LedgerManager ledgerManager,
+      final String ledgerIdentifier,
+      final String accountDesignator,
+      final AccountType type) {
+    final AccountMatcher specifiesCorrectAccount = new AccountMatcher(ledgerIdentifier, accountDesignator, type);
     Mockito.verify(ledgerManager).createAccount(AdditionalMatchers.and(argThat(isValid()), argThat(specifiesCorrectAccount)));
     return specifiesCorrectAccount.getMatchedArgument().getIdentifier();
   }
 
+  static String verifyLedgerCreation(
+      final LedgerManager ledgerManager,
+      final String ledgerIdentifier,
+      final AccountType type) {
+    final LedgerMatcher specifiesCorrectLedger = new LedgerMatcher(ledgerIdentifier, type);
+    Mockito.verify(ledgerManager).createLedger(AdditionalMatchers.and(argThat(isValid()), argThat(specifiesCorrectLedger)));
+    makeLedgerResponsive(specifiesCorrectLedger.getMatchedArgument(), ledgerManager);
+    return specifiesCorrectLedger.getMatchedArgument().getIdentifier();
+  }
+
   static void verifyTransfer(final LedgerManager ledgerManager,
                              final Set<Debtor> debtors,
                              final Set<Creditor> creditors,
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 900ac99..2623acf 100644
--- a/component-test/src/main/java/io/mifos/portfolio/Fixture.java
+++ b/component-test/src/main/java/io/mifos/portfolio/Fixture.java
@@ -19,6 +19,7 @@
 import io.mifos.individuallending.api.v1.domain.caseinstance.CaseParameters;
 import io.mifos.individuallending.api.v1.domain.caseinstance.CreditWorthinessFactor;
 import io.mifos.individuallending.api.v1.domain.caseinstance.CreditWorthinessSnapshot;
+import io.mifos.individuallending.api.v1.domain.product.AccountDesignators;
 import io.mifos.individuallending.api.v1.domain.product.ProductParameters;
 import io.mifos.portfolio.api.v1.domain.*;
 
@@ -71,10 +72,20 @@
     //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);
+    final AccountAssignment customerLoanPrincipalAccountAssignment = new AccountAssignment();
+    customerLoanPrincipalAccountAssignment.setDesignator(AccountDesignators.CUSTOMER_LOAN_PRINCIPAL);
+    customerLoanPrincipalAccountAssignment.setLedgerIdentifier(CUSTOMER_LOAN_LEDGER_IDENTIFIER);
+    accountAssignments.add(customerLoanPrincipalAccountAssignment);
+
+    final AccountAssignment customerLoanInterestAccountAssignment = new AccountAssignment();
+    customerLoanInterestAccountAssignment.setDesignator(AccountDesignators.CUSTOMER_LOAN_INTEREST);
+    customerLoanInterestAccountAssignment.setLedgerIdentifier(CUSTOMER_LOAN_LEDGER_IDENTIFIER);
+    accountAssignments.add(customerLoanInterestAccountAssignment);
+
+    final AccountAssignment customerLoanFeesAccountAssignment = new AccountAssignment();
+    customerLoanFeesAccountAssignment.setDesignator(AccountDesignators.CUSTOMER_LOAN_FEES);
+    customerLoanFeesAccountAssignment.setLedgerIdentifier(CUSTOMER_LOAN_LEDGER_IDENTIFIER);
+    accountAssignments.add(customerLoanFeesAccountAssignment);
     product.setAccountAssignments(accountAssignments);
 
     final ProductParameters productParameters = new ProductParameters();
diff --git a/component-test/src/main/java/io/mifos/portfolio/TestAccountingInteractionInLoanWorkflow.java b/component-test/src/main/java/io/mifos/portfolio/TestAccountingInteractionInLoanWorkflow.java
index def64ad..46421e9 100644
--- a/component-test/src/main/java/io/mifos/portfolio/TestAccountingInteractionInLoanWorkflow.java
+++ b/component-test/src/main/java/io/mifos/portfolio/TestAccountingInteractionInLoanWorkflow.java
@@ -68,7 +68,9 @@
   private Product product = null;
   private Case customerCase = null;
   private TaskDefinition taskDefinition = null;
-  private String customerLoanAccountIdentifier = null;
+  private String customerLoanPrincipalIdentifier = null;
+  private String customerLoanInterestIdentifier = null;
+  private String customerLoanFeeIdentifier = null;
 
   private BigDecimal expectedCurrentBalance = null;
   private BigDecimal interestAccrued = BigDecimal.ZERO.setScale(MINOR_CURRENCY_UNIT_DIGITS, RoundingMode.HALF_EVEN);
@@ -212,7 +214,7 @@
     while (expectedCurrentBalance.compareTo(BigDecimal.ZERO) > 0) {
       logger.info("Simulating week {}. Expected current balance {}.", week, expectedCurrentBalance);
       if (week == weekOfLateRepayment) {
-        final BigDecimal lateFee = BigDecimal.valueOf(17_31, MINOR_CURRENCY_UNIT_DIGITS);
+        final BigDecimal lateFee = BigDecimal.valueOf(31_18, MINOR_CURRENCY_UNIT_DIGITS);
         step6CalculateInterestAndCheckForLatenessForRangeOfDays(
             today,
             (week * 7) + 1,
@@ -242,7 +244,7 @@
   private BigDecimal findNextRepaymentAmount(
       final LocalDateTime referenceDate,
       final int dayNumber) {
-    AccountingFixture.mockBalance(customerLoanAccountIdentifier, expectedCurrentBalance);
+    AccountingFixture.mockBalance(customerLoanPrincipalIdentifier, expectedCurrentBalance);
 
     final Payment nextPayment = portfolioManager.getCostComponentsForAction(
         product.getIdentifier(),
@@ -251,7 +253,7 @@
         null,
         null,
         DateConverter.toIsoString(referenceDate.plusDays(dayNumber)));
-    return nextPayment.getCostComponents().stream().filter(x -> x.getChargeIdentifier().equals(ChargeIdentifiers.REPAYMENT_ID)).findFirst()
+    return nextPayment.getCostComponents().stream().filter(x -> x.getChargeIdentifier().equals(ChargeIdentifiers.REPAY_PRINCIPAL_ID)).findFirst()
         .orElseThrow(() -> new IllegalArgumentException("return missing repayment charge."))
         .getAmount();
   }
@@ -382,8 +384,17 @@
         Case.State.APPROVED);
     checkNextActionsCorrect(product.getIdentifier(), customerCase.getIdentifier(), Action.DISBURSE, Action.CLOSE);
 
-    customerLoanAccountIdentifier =
-        AccountingFixture.verifyAccountCreation(ledgerManager, AccountingFixture.CUSTOMER_LOAN_LEDGER_IDENTIFIER, AccountType.ASSET);
+    final String customerLoanLedgerIdentifier = AccountingFixture.verifyLedgerCreation(
+        ledgerManager,
+        AccountingFixture.CUSTOMER_LOAN_LEDGER_IDENTIFIER,
+        AccountType.ASSET);
+
+    customerLoanPrincipalIdentifier =
+        AccountingFixture.verifyAccountCreationMatchingDesignator(ledgerManager, customerLoanLedgerIdentifier, AccountDesignators.CUSTOMER_LOAN_PRINCIPAL, AccountType.ASSET);
+    customerLoanInterestIdentifier =
+        AccountingFixture.verifyAccountCreationMatchingDesignator(ledgerManager, customerLoanLedgerIdentifier, AccountDesignators.CUSTOMER_LOAN_INTEREST, AccountType.ASSET);
+    customerLoanFeeIdentifier =
+        AccountingFixture.verifyAccountCreationMatchingDesignator(ledgerManager, customerLoanLedgerIdentifier, AccountDesignators.CUSTOMER_LOAN_FEES, AccountType.ASSET);
 
     expectedCurrentBalance = BigDecimal.ZERO;
   }
@@ -398,7 +409,7 @@
         product.getIdentifier(),
         customerCase.getIdentifier(),
         Action.DISBURSE,
-        Sets.newLinkedHashSet(AccountDesignators.ENTRY, AccountDesignators.CUSTOMER_LOAN),
+        Sets.newLinkedHashSet(AccountDesignators.ENTRY, AccountDesignators.CUSTOMER_LOAN_GROUP),
         amount, new CostComponent(whichDisbursementFee, disbursementFeeAmount),
         new CostComponent(ChargeIdentifiers.LOAN_ORIGINATION_FEE_ID, LOAN_ORIGINATION_FEE_AMOUNT),
         new CostComponent(ChargeIdentifiers.PROCESSING_FEE_ID, PROCESSING_FEE_AMOUNT),
@@ -417,10 +428,10 @@
         Action.APPLY_INTEREST, Action.MARK_LATE, Action.ACCEPT_PAYMENT, Action.DISBURSE, Action.WRITE_OFF, Action.CLOSE);
 
     final Set<Debtor> debtors = new HashSet<>();
-    debtors.add(new Debtor(customerLoanAccountIdentifier, amount.toPlainString()));
-    debtors.add(new Debtor(customerLoanAccountIdentifier, PROCESSING_FEE_AMOUNT.toPlainString()));
-    debtors.add(new Debtor(customerLoanAccountIdentifier, disbursementFeeAmount.toPlainString()));
-    debtors.add(new Debtor(customerLoanAccountIdentifier, LOAN_ORIGINATION_FEE_AMOUNT.toPlainString()));
+    debtors.add(new Debtor(customerLoanPrincipalIdentifier, amount.toPlainString()));
+    debtors.add(new Debtor(customerLoanFeeIdentifier, PROCESSING_FEE_AMOUNT.toPlainString()));
+    debtors.add(new Debtor(customerLoanFeeIdentifier, disbursementFeeAmount.toPlainString()));
+    debtors.add(new Debtor(customerLoanFeeIdentifier, LOAN_ORIGINATION_FEE_AMOUNT.toPlainString()));
 
     final Set<Creditor> creditors = new HashSet<>();
     creditors.add(new Creditor(AccountingFixture.TELLER_ONE_ACCOUNT_IDENTIFIER, amount.toString()));
@@ -482,7 +493,7 @@
     final String beatIdentifier = "alignment0";
     final String midnightTimeStamp = DateConverter.toIsoString(forTime);
 
-    AccountingFixture.mockBalance(customerLoanAccountIdentifier, expectedCurrentBalance);
+    AccountingFixture.mockBalance(customerLoanPrincipalIdentifier, expectedCurrentBalance);
 
     final BigDecimal calculatedInterest = expectedCurrentBalance.multiply(Fixture.INTEREST_RATE.divide(Fixture.ACCRUAL_PERIODS, 8, BigDecimal.ROUND_HALF_EVEN))
         .setScale(MINOR_CURRENCY_UNIT_DIGITS, BigDecimal.ROUND_HALF_EVEN);
@@ -525,7 +536,7 @@
 
     final Set<Debtor> debtors = new HashSet<>();
     debtors.add(new Debtor(
-        customerLoanAccountIdentifier,
+        customerLoanInterestIdentifier,
         calculatedInterest.toPlainString()));
 
     final Set<Creditor> creditors = new HashSet<>();
@@ -544,15 +555,15 @@
       final BigDecimal lateFee) throws InterruptedException {
     logger.info("step7PaybackPartialAmount '{}'", amount);
 
-    AccountingFixture.mockBalance(customerLoanAccountIdentifier, expectedCurrentBalance);
+    AccountingFixture.mockBalance(customerLoanPrincipalIdentifier, expectedCurrentBalance);
 
     checkCostComponentForActionCorrect(
         product.getIdentifier(),
         customerCase.getIdentifier(),
         Action.ACCEPT_PAYMENT,
-        new HashSet<>(Arrays.asList(AccountDesignators.ENTRY, AccountDesignators.CUSTOMER_LOAN, AccountDesignators.LOAN_FUNDS_SOURCE)),
+        new HashSet<>(Arrays.asList(AccountDesignators.ENTRY, AccountDesignators.CUSTOMER_LOAN_GROUP, AccountDesignators.LOAN_FUNDS_SOURCE)),
         amount,
-        new CostComponent(ChargeIdentifiers.REPAYMENT_ID, amount),
+        new CostComponent(ChargeIdentifiers.REPAY_PRINCIPAL_ID, amount),
         new CostComponent(ChargeIdentifiers.INTEREST_ID, interestAccrued),
         new CostComponent(ChargeIdentifiers.LATE_FEE_ID, lateFee));
     checkStateTransfer(
@@ -576,7 +587,7 @@
       debtors.add(new Debtor(AccountingFixture.LATE_FEE_ACCRUAL_ACCOUNT_IDENTIFIER, lateFee.toPlainString()));
 
     final Set<Creditor> creditors = new HashSet<>();
-    creditors.add(new Creditor(customerLoanAccountIdentifier, amount.toPlainString()));
+    creditors.add(new Creditor(customerLoanPrincipalIdentifier, amount.toPlainString()));
     if (interestAccrued.compareTo(BigDecimal.ZERO) != 0)
       creditors.add(new Creditor(AccountingFixture.CONSUMER_LOAN_INTEREST_ACCOUNT_IDENTIFIER, interestAccrued.toPlainString()));
     if (lateFee.compareTo(BigDecimal.ZERO) != 0)
@@ -591,7 +602,7 @@
   private void step8Close() throws InterruptedException {
     logger.info("step8Close");
 
-    AccountingFixture.mockBalance(customerLoanAccountIdentifier, expectedCurrentBalance);
+    AccountingFixture.mockBalance(customerLoanPrincipalIdentifier, expectedCurrentBalance);
 
     checkCostComponentForActionCorrect(
         product.getIdentifier(),
diff --git a/component-test/src/main/java/io/mifos/portfolio/TestCases.java b/component-test/src/main/java/io/mifos/portfolio/TestCases.java
index b6afb42..0206d75 100644
--- a/component-test/src/main/java/io/mifos/portfolio/TestCases.java
+++ b/component-test/src/main/java/io/mifos/portfolio/TestCases.java
@@ -21,6 +21,7 @@
 import io.mifos.individuallending.api.v1.domain.caseinstance.CaseParameters;
 import io.mifos.individuallending.api.v1.domain.caseinstance.CreditWorthinessFactor;
 import io.mifos.individuallending.api.v1.domain.caseinstance.CreditWorthinessSnapshot;
+import io.mifos.individuallending.api.v1.domain.product.AccountDesignators;
 import io.mifos.portfolio.api.v1.domain.AccountAssignment;
 import io.mifos.portfolio.api.v1.domain.Case;
 import io.mifos.portfolio.api.v1.domain.CasePage;
@@ -37,9 +38,6 @@
 import java.util.stream.Collectors;
 import java.util.stream.Stream;
 
-import static io.mifos.individuallending.api.v1.domain.product.AccountDesignators.CUSTOMER_LOAN;
-import static io.mifos.individuallending.api.v1.domain.product.AccountDesignators.ENTRY;
-
 /**
  * @author Myrle Krantz
  */
@@ -96,8 +94,8 @@
     final Case caseInstance = createAdjustedCase(product.getIdentifier(), x -> x.setParameters(originalParameters));
 
     final Set<AccountAssignment> accountAssignments = new HashSet<>();
-    accountAssignments.add(new AccountAssignment(CUSTOMER_LOAN, "002-011"));
-    accountAssignments.add(new AccountAssignment(ENTRY, "002-012"));
+    accountAssignments.add(new AccountAssignment(AccountDesignators.CUSTOMER_LOAN_GROUP, "002-011"));
+    accountAssignments.add(new AccountAssignment(AccountDesignators.ENTRY, "002-012"));
     caseInstance.setAccountAssignments(accountAssignments);
 
     newCaseParameters.setMaximumBalance(Fixture.fixScale(BigDecimal.TEN));
diff --git a/component-test/src/main/java/io/mifos/portfolio/TestChargeDefinitions.java b/component-test/src/main/java/io/mifos/portfolio/TestChargeDefinitions.java
index ec4540d..a358757 100644
--- a/component-test/src/main/java/io/mifos/portfolio/TestChargeDefinitions.java
+++ b/component-test/src/main/java/io/mifos/portfolio/TestChargeDefinitions.java
@@ -59,7 +59,9 @@
         ChargeIdentifiers.ALLOW_FOR_WRITE_OFF_ID,
         ChargeIdentifiers.DISBURSE_PAYMENT_ID,
         ChargeIdentifiers.INTEREST_ID,
-        ChargeIdentifiers.REPAYMENT_ID)
+        ChargeIdentifiers.REPAY_PRINCIPAL_ID,
+        ChargeIdentifiers.REPAY_INTEREST_ID,
+        ChargeIdentifiers.REPAY_FEES_ID)
         .collect(Collectors.toSet());
     final Set<String> expectedChangeableChargeDefinitionIdentifiers = Stream.of(
         ChargeIdentifiers.DISBURSEMENT_FEE_ID,
diff --git a/component-test/src/main/java/io/mifos/portfolio/TestIndividualLoans.java b/component-test/src/main/java/io/mifos/portfolio/TestIndividualLoans.java
index 1f8c03e..4294623 100644
--- a/component-test/src/main/java/io/mifos/portfolio/TestIndividualLoans.java
+++ b/component-test/src/main/java/io/mifos/portfolio/TestIndividualLoans.java
@@ -90,7 +90,7 @@
     Assert.assertNotNull(paymentScheduleFirstPage);
     paymentScheduleFirstPage.getElements().forEach(x -> {
       x.getPayment().getCostComponents().forEach(y -> Assert.assertEquals(product.getMinorCurrencyUnitDigits(), y.getAmount().scale()));
-      Assert.assertEquals(product.getMinorCurrencyUnitDigits(), x.getBalances().get(AccountDesignators.CUSTOMER_LOAN).scale());
+      Assert.assertEquals(product.getMinorCurrencyUnitDigits(), x.getBalances().get(AccountDesignators.CUSTOMER_LOAN_PRINCIPAL).scale());
     });
   }
 
diff --git a/service/src/main/java/io/mifos/individuallending/IndividualLendingPatternFactory.java b/service/src/main/java/io/mifos/individuallending/IndividualLendingPatternFactory.java
index bd76bb4..c18c710 100644
--- a/service/src/main/java/io/mifos/individuallending/IndividualLendingPatternFactory.java
+++ b/service/src/main/java/io/mifos/individuallending/IndividualLendingPatternFactory.java
@@ -16,9 +16,11 @@
 package io.mifos.individuallending;
 
 import com.google.gson.Gson;
+import io.mifos.accounting.api.v1.domain.AccountType;
 import io.mifos.core.lang.ServiceException;
 import io.mifos.customer.api.v1.client.CustomerManager;
 import io.mifos.individuallending.api.v1.domain.caseinstance.CaseParameters;
+import io.mifos.individuallending.api.v1.domain.product.AccountDesignators;
 import io.mifos.individuallending.api.v1.domain.product.ChargeProportionalDesignator;
 import io.mifos.individuallending.api.v1.domain.workflow.Action;
 import io.mifos.individuallending.internal.mapper.CaseParametersMapper;
@@ -45,7 +47,6 @@
 import java.util.*;
 import java.util.stream.Collectors;
 
-import static io.mifos.individuallending.api.v1.domain.product.AccountDesignators.*;
 import static io.mifos.individuallending.api.v1.domain.product.ChargeIdentifiers.*;
 
 /**
@@ -55,6 +56,62 @@
 @Component
 public class IndividualLendingPatternFactory implements PatternFactory {
   final static private String INDIVIDUAL_LENDING_PACKAGE = "io.mifos.individuallending.api.v1";
+  final static private Pattern INDIVIDUAL_LENDING_PATTERN;
+
+  static {
+    INDIVIDUAL_LENDING_PATTERN = new Pattern();
+    INDIVIDUAL_LENDING_PATTERN.setParameterPackage(INDIVIDUAL_LENDING_PACKAGE);
+    INDIVIDUAL_LENDING_PATTERN.setAccountAssignmentGroups(Collections.singleton(AccountDesignators.CUSTOMER_LOAN_GROUP));
+    final Set<RequiredAccountAssignment> individualLendingRequiredAccounts = new HashSet<>();
+    individualLendingRequiredAccounts.add(new RequiredAccountAssignment(
+        AccountDesignators.CUSTOMER_LOAN_PRINCIPAL,
+        AccountType.ASSET.name(),
+        AccountDesignators.CUSTOMER_LOAN_GROUP));
+    individualLendingRequiredAccounts.add(new RequiredAccountAssignment(
+        AccountDesignators.CUSTOMER_LOAN_INTEREST,
+        AccountType.ASSET.name(),
+        AccountDesignators.CUSTOMER_LOAN_GROUP));
+    individualLendingRequiredAccounts.add(new RequiredAccountAssignment(
+        AccountDesignators.CUSTOMER_LOAN_FEES,
+        AccountType.ASSET.name(),
+        AccountDesignators.CUSTOMER_LOAN_GROUP));
+
+    individualLendingRequiredAccounts.add(new RequiredAccountAssignment(
+        AccountDesignators.LOAN_FUNDS_SOURCE,
+        AccountType.ASSET.name()));
+    individualLendingRequiredAccounts.add(new RequiredAccountAssignment(
+        AccountDesignators.PROCESSING_FEE_INCOME,
+        AccountType.REVENUE.name()));
+    individualLendingRequiredAccounts.add(new RequiredAccountAssignment(
+        AccountDesignators.ORIGINATION_FEE_INCOME,
+        AccountType.REVENUE.name()));
+    individualLendingRequiredAccounts.add(new RequiredAccountAssignment(
+        AccountDesignators.DISBURSEMENT_FEE_INCOME,
+        AccountType.REVENUE.name()));
+    individualLendingRequiredAccounts.add(new RequiredAccountAssignment(
+        AccountDesignators.INTEREST_INCOME,
+        AccountType.REVENUE.name()));
+    individualLendingRequiredAccounts.add(new RequiredAccountAssignment(
+        AccountDesignators.INTEREST_ACCRUAL,
+        AccountType.REVENUE.name()));
+    individualLendingRequiredAccounts.add(new RequiredAccountAssignment(
+        AccountDesignators.LATE_FEE_INCOME,
+        AccountType.REVENUE.name()));
+    individualLendingRequiredAccounts.add(new RequiredAccountAssignment(
+        AccountDesignators.LATE_FEE_ACCRUAL,
+        AccountType.REVENUE.name()));
+    individualLendingRequiredAccounts.add(new RequiredAccountAssignment(
+        AccountDesignators.ARREARS_ALLOWANCE,
+        AccountType.LIABILITY.name())); //TODO: type?
+    individualLendingRequiredAccounts.add(new RequiredAccountAssignment(
+        AccountDesignators.ENTRY,
+        AccountType.LIABILITY.name()));
+    INDIVIDUAL_LENDING_PATTERN.setAccountAssignmentsRequired(individualLendingRequiredAccounts);
+  }
+  public static Pattern individualLendingPattern() {
+    return INDIVIDUAL_LENDING_PATTERN;
+  }
+
   private final CaseParametersRepository caseParametersRepository;
   private final DataContextService dataContextService;
   private final CostComponentService costComponentService;
@@ -81,110 +138,38 @@
 
   @Override
   public Pattern pattern() {
-
-    final Set<String> individualLendingRequiredAccounts = new HashSet<>();
-    individualLendingRequiredAccounts.add(CUSTOMER_LOAN);
-    //TODO: fix in migration individualLendingRequiredAccounts.add(PENDING_DISBURSAL);
-    //was String PENDING_DISBURSAL = "pending-disbursal";
-    individualLendingRequiredAccounts.add(LOAN_FUNDS_SOURCE);
-    individualLendingRequiredAccounts.add(PROCESSING_FEE_INCOME);
-    individualLendingRequiredAccounts.add(ORIGINATION_FEE_INCOME);
-    individualLendingRequiredAccounts.add(DISBURSEMENT_FEE_INCOME);
-    individualLendingRequiredAccounts.add(INTEREST_INCOME);
-    individualLendingRequiredAccounts.add(INTEREST_ACCRUAL);
-    individualLendingRequiredAccounts.add(LATE_FEE_INCOME);
-    individualLendingRequiredAccounts.add(LATE_FEE_ACCRUAL);
-    individualLendingRequiredAccounts.add(ARREARS_ALLOWANCE);
-    individualLendingRequiredAccounts.add(ENTRY);
-    return new Pattern(INDIVIDUAL_LENDING_PACKAGE, individualLendingRequiredAccounts);
+    return INDIVIDUAL_LENDING_PATTERN;
   }
 
   @Override
   public List<ChargeDefinition> charges() {
-    return defaultIndividualLoanCharges();
+    final List<ChargeDefinition> ret = defaultIndividualLoanCharges();
+    ret.addAll(requiredIndividualLoanCharges());
+    return ret;
   }
 
-  public static List<ChargeDefinition> defaultIndividualLoanCharges() {
+  public static List<ChargeDefinition> requiredIndividualLoanCharges() {
     final List<ChargeDefinition> ret = new ArrayList<>();
-    final ChargeDefinition processingFee = charge(
-        PROCESSING_FEE_NAME,
-        Action.DISBURSE, //TODO: fix existing charges in migration
-        BigDecimal.ONE,
-        CUSTOMER_LOAN, //TODO: fix existing charges in migration
-        PROCESSING_FEE_INCOME);
-    processingFee.setReadOnly(false);
-
-    final ChargeDefinition loanOriginationFee = charge(
-        LOAN_ORIGINATION_FEE_NAME,
-        Action.DISBURSE, //TODO: fix existing charges in migration
-        BigDecimal.ONE,
-        CUSTOMER_LOAN, //TODO: fix existing charges in migration
-        ORIGINATION_FEE_INCOME);
-    loanOriginationFee.setReadOnly(false);
-
-    /*final ChargeDefinition loanFundsAllocation = charge(
-            LOAN_FUNDS_ALLOCATION_ID,
-            Action.APPROVE,
-            BigDecimal.valueOf(100),
-            LOAN_FUNDS_SOURCE,
-            PENDING_DISBURSAL);
-    loanFundsAllocation.setReadOnly(true);*/
-    //TODO: handle removing this extraneous charge in migration.
-
-    final ChargeDefinition disbursementFee = charge(
-        DISBURSEMENT_FEE_NAME,
-        Action.DISBURSE,
-        BigDecimal.valueOf(0.1),
-        CUSTOMER_LOAN, //TODO: fix existing charges in migration
-        DISBURSEMENT_FEE_INCOME);
-    disbursementFee.setProportionalTo(ChargeProportionalDesignator.REQUESTED_DISBURSEMENT_DESIGNATOR.getValue());  //TODO: fix existing charges in migration
-    disbursementFee.setReadOnly(false);
 
     final ChargeDefinition disbursePayment = new ChargeDefinition();
     disbursePayment.setChargeAction(Action.DISBURSE.name());
     disbursePayment.setIdentifier(DISBURSE_PAYMENT_ID);
     disbursePayment.setName(DISBURSE_PAYMENT_NAME);
     disbursePayment.setDescription(DISBURSE_PAYMENT_NAME);
-    disbursePayment.setFromAccountDesignator(CUSTOMER_LOAN);  //TODO: fix existing charges in migration
-    disbursePayment.setToAccountDesignator(ENTRY);
+    disbursePayment.setFromAccountDesignator(AccountDesignators.CUSTOMER_LOAN_PRINCIPAL);
+    disbursePayment.setToAccountDesignator(AccountDesignators.ENTRY);
     disbursePayment.setProportionalTo(ChargeProportionalDesignator.REQUESTED_DISBURSEMENT_DESIGNATOR.getValue());
     disbursePayment.setChargeMethod(ChargeDefinition.ChargeMethod.PROPORTIONAL);
     disbursePayment.setAmount(BigDecimal.valueOf(100));
     disbursePayment.setReadOnly(true);
 
-    /*
-    final ChargeDefinition trackPrincipalDisbursePayment = new ChargeDefinition();
-    trackPrincipalDisbursePayment.setChargeAction(Action.DISBURSE.name());
-    trackPrincipalDisbursePayment.setIdentifier(TRACK_DISBURSAL_PAYMENT_ID);
-    trackPrincipalDisbursePayment.setName(TRACK_DISBURSAL_PAYMENT_NAME);
-    trackPrincipalDisbursePayment.setDescription(TRACK_DISBURSAL_PAYMENT_NAME);
-    trackPrincipalDisbursePayment.setFromAccountDesignator(PENDING_DISBURSAL);
-    trackPrincipalDisbursePayment.setToAccountDesignator(CUSTOMER_LOAN);
-    trackPrincipalDisbursePayment.setProportionalTo(ChargeProportionalDesignator.REQUESTED_DISBURSEMENT_DESIGNATOR.getValue());
-    trackPrincipalDisbursePayment.setChargeMethod(ChargeDefinition.ChargeMethod.PROPORTIONAL);
-    trackPrincipalDisbursePayment.setAmount(BigDecimal.valueOf(100));
-    trackPrincipalDisbursePayment.setReadOnly(true);*/
-    //TODO: handle removing this extraneous charge in migration.
-
-    final ChargeDefinition lateFee = charge(
-            LATE_FEE_NAME,
-            Action.ACCEPT_PAYMENT,
-            BigDecimal.TEN,
-            CUSTOMER_LOAN,
-            LATE_FEE_INCOME);
-    lateFee.setAccrueAction(Action.MARK_LATE.name());
-    lateFee.setAccrualAccountDesignator(LATE_FEE_ACCRUAL);
-    lateFee.setProportionalTo(ChargeProportionalDesignator.CONTRACTUAL_REPAYMENT_DESIGNATOR.getValue());
-    lateFee.setChargeOnTop(true);
-    lateFee.setReadOnly(false);
-
     //TODO: Make multiple write off allowance charges.
     final ChargeDefinition writeOffAllowanceCharge = charge(
         ALLOW_FOR_WRITE_OFF_NAME,
         Action.MARK_LATE,
         BigDecimal.valueOf(30),
-        LOAN_FUNDS_SOURCE, //TODO: this and previous value ("pending-disbursal") are not correct and will require migration.
-        ARREARS_ALLOWANCE);
+        AccountDesignators.LOAN_FUNDS_SOURCE,
+        AccountDesignators.ARREARS_ALLOWANCE);
     writeOffAllowanceCharge.setProportionalTo(ChargeProportionalDesignator.RUNNING_BALANCE_DESIGNATOR.getValue());
     writeOffAllowanceCharge.setReadOnly(true);
 
@@ -192,62 +177,105 @@
         INTEREST_NAME,
         Action.ACCEPT_PAYMENT,
         BigDecimal.valueOf(100),
-        CUSTOMER_LOAN,
-        INTEREST_INCOME);
+        AccountDesignators.CUSTOMER_LOAN_INTEREST,
+        AccountDesignators.INTEREST_INCOME);
     interestCharge.setForCycleSizeUnit(ChronoUnit.YEARS);
     interestCharge.setAccrueAction(Action.APPLY_INTEREST.name());
-    interestCharge.setAccrualAccountDesignator(INTEREST_ACCRUAL);
+    interestCharge.setAccrualAccountDesignator(AccountDesignators.INTEREST_ACCRUAL);
     interestCharge.setProportionalTo(ChargeProportionalDesignator.RUNNING_BALANCE_DESIGNATOR.getValue());
     interestCharge.setChargeMethod(ChargeDefinition.ChargeMethod.INTEREST);
     interestCharge.setReadOnly(true);
 
-    final ChargeDefinition customerRepaymentCharge = new ChargeDefinition();
-    customerRepaymentCharge.setChargeAction(Action.ACCEPT_PAYMENT.name());
-    customerRepaymentCharge.setIdentifier(REPAYMENT_ID);
-    customerRepaymentCharge.setName(REPAYMENT_NAME);
-    customerRepaymentCharge.setDescription(REPAYMENT_NAME);
-    customerRepaymentCharge.setFromAccountDesignator(ENTRY);  //TODO: fix existing charges in migration
-    customerRepaymentCharge.setToAccountDesignator(CUSTOMER_LOAN);  //TODO: fix existing charges in migration
-    customerRepaymentCharge.setProportionalTo(ChargeProportionalDesignator.REQUESTED_REPAYMENT_DESIGNATOR.getValue());
-    customerRepaymentCharge.setChargeMethod(ChargeDefinition.ChargeMethod.PROPORTIONAL);
-    customerRepaymentCharge.setAmount(BigDecimal.valueOf(100));
-    customerRepaymentCharge.setReadOnly(true);
+    final ChargeDefinition customerPrincipalRepaymentCharge = new ChargeDefinition();
+    customerPrincipalRepaymentCharge.setChargeAction(Action.ACCEPT_PAYMENT.name());
+    customerPrincipalRepaymentCharge.setIdentifier(REPAY_PRINCIPAL_ID);
+    customerPrincipalRepaymentCharge.setName(REPAY_PRINCIPAL_NAME);
+    customerPrincipalRepaymentCharge.setDescription(REPAY_PRINCIPAL_NAME);
+    customerPrincipalRepaymentCharge.setFromAccountDesignator(AccountDesignators.ENTRY);
+    customerPrincipalRepaymentCharge.setToAccountDesignator(AccountDesignators.CUSTOMER_LOAN_PRINCIPAL);
+    customerPrincipalRepaymentCharge.setProportionalTo(ChargeProportionalDesignator.REQUESTED_REPAYMENT_DESIGNATOR.getValue());
+    customerPrincipalRepaymentCharge.setChargeMethod(ChargeDefinition.ChargeMethod.PROPORTIONAL);
+    customerPrincipalRepaymentCharge.setAmount(BigDecimal.valueOf(100));
+    customerPrincipalRepaymentCharge.setReadOnly(true);
 
-    /*final ChargeDefinition trackReturnPrincipalCharge = new ChargeDefinition();
-    trackReturnPrincipalCharge.setChargeAction(Action.ACCEPT_PAYMENT.name());
-    trackReturnPrincipalCharge.setIdentifier(TRACK_RETURN_PRINCIPAL_ID);
-    trackReturnPrincipalCharge.setName(TRACK_RETURN_PRINCIPAL_NAME);
-    trackReturnPrincipalCharge.setDescription(TRACK_RETURN_PRINCIPAL_NAME);
-    trackReturnPrincipalCharge.setFromAccountDesignator(LOAN_FUNDS_SOURCE);
-    trackReturnPrincipalCharge.setToAccountDesignator(LOANS_PAYABLE);
-    trackReturnPrincipalCharge.setProportionalTo(ChargeProportionalDesignator.REQUESTED_DISBURSEMENT_DESIGNATOR.getValue());
-    trackReturnPrincipalCharge.setChargeMethod(ChargeDefinition.ChargeMethod.PROPORTIONAL);
-    trackReturnPrincipalCharge.setAmount(BigDecimal.valueOf(100));
-    trackReturnPrincipalCharge.setReadOnly(true);*/
-    //TODO: handle removing this extraneous charge in migration.
+    final ChargeDefinition customerInterestRepaymentCharge = new ChargeDefinition();
+    customerInterestRepaymentCharge.setChargeAction(Action.ACCEPT_PAYMENT.name());
+    customerInterestRepaymentCharge.setIdentifier(REPAY_INTEREST_ID);
+    customerInterestRepaymentCharge.setName(REPAY_INTEREST_NAME);
+    customerInterestRepaymentCharge.setDescription(REPAY_INTEREST_NAME);
+    customerInterestRepaymentCharge.setFromAccountDesignator(AccountDesignators.ENTRY);
+    customerInterestRepaymentCharge.setToAccountDesignator(AccountDesignators.CUSTOMER_LOAN_INTEREST);
+    customerInterestRepaymentCharge.setProportionalTo(ChargeProportionalDesignator.REQUESTED_REPAYMENT_DESIGNATOR.getValue()); //TODO: ???
+    customerInterestRepaymentCharge.setChargeMethod(ChargeDefinition.ChargeMethod.PROPORTIONAL); //TODO: ???
+    customerInterestRepaymentCharge.setAmount(BigDecimal.valueOf(100)); //TODO: ???
+    customerInterestRepaymentCharge.setReadOnly(true);
 
-    /*final ChargeDefinition disbursementReturnCharge = charge(
-        RETURN_DISBURSEMENT_NAME,
-        Action.CLOSE,
-        BigDecimal.valueOf(100),
-        PENDING_DISBURSAL,
-        LOAN_FUNDS_SOURCE);
-    disbursementReturnCharge.setProportionalTo(ChargeProportionalDesignator.RUNNING_BALANCE_DESIGNATOR.getValue());
-    disbursementReturnCharge.setReadOnly(true);*/
-    //TODO: handle removing this extraneous charge in migration.
+    final ChargeDefinition customerFeeRepaymentCharge = new ChargeDefinition();
+    customerFeeRepaymentCharge.setChargeAction(Action.ACCEPT_PAYMENT.name());
+    customerFeeRepaymentCharge.setIdentifier(REPAY_FEES_ID);
+    customerFeeRepaymentCharge.setName(REPAY_FEES_NAME);
+    customerFeeRepaymentCharge.setDescription(REPAY_FEES_NAME);
+    customerFeeRepaymentCharge.setFromAccountDesignator(AccountDesignators.ENTRY);
+    customerFeeRepaymentCharge.setToAccountDesignator(AccountDesignators.CUSTOMER_LOAN_INTEREST);
+    customerFeeRepaymentCharge.setProportionalTo(ChargeProportionalDesignator.REQUESTED_REPAYMENT_DESIGNATOR.getValue());  //TODO: ???
+    customerFeeRepaymentCharge.setChargeMethod(ChargeDefinition.ChargeMethod.PROPORTIONAL); //TODO: ???
+    customerFeeRepaymentCharge.setAmount(BigDecimal.valueOf(100)); //TODO: ???
+    customerFeeRepaymentCharge.setReadOnly(true);
+
+    ret.add(disbursePayment);
+    ret.add(writeOffAllowanceCharge);
+    ret.add(interestCharge);
+    ret.add(customerPrincipalRepaymentCharge);
+    ret.add(customerInterestRepaymentCharge);
+    ret.add(customerFeeRepaymentCharge);
+
+    return ret;
+
+  }
+
+  public static List<ChargeDefinition> defaultIndividualLoanCharges() {
+    final List<ChargeDefinition> ret = new ArrayList<>();
+    final ChargeDefinition processingFee = charge(
+        PROCESSING_FEE_NAME,
+        Action.DISBURSE,
+        BigDecimal.ONE,
+        AccountDesignators.CUSTOMER_LOAN_FEES,
+        AccountDesignators.PROCESSING_FEE_INCOME);
+    processingFee.setReadOnly(false);
+
+    final ChargeDefinition loanOriginationFee = charge(
+        LOAN_ORIGINATION_FEE_NAME,
+        Action.DISBURSE,
+        BigDecimal.ONE,
+        AccountDesignators.CUSTOMER_LOAN_FEES,
+        AccountDesignators.ORIGINATION_FEE_INCOME);
+    loanOriginationFee.setReadOnly(false);
+
+    final ChargeDefinition disbursementFee = charge(
+        DISBURSEMENT_FEE_NAME,
+        Action.DISBURSE,
+        BigDecimal.valueOf(0.1),
+        AccountDesignators.CUSTOMER_LOAN_FEES,
+        AccountDesignators.DISBURSEMENT_FEE_INCOME);
+    disbursementFee.setProportionalTo(ChargeProportionalDesignator.REQUESTED_DISBURSEMENT_DESIGNATOR.getValue());
+    disbursementFee.setReadOnly(false);
+
+    final ChargeDefinition lateFee = charge(
+        LATE_FEE_NAME,
+        Action.ACCEPT_PAYMENT,
+        BigDecimal.TEN,
+        AccountDesignators.CUSTOMER_LOAN_FEES,
+        AccountDesignators.LATE_FEE_INCOME);
+    lateFee.setAccrueAction(Action.MARK_LATE.name());
+    lateFee.setAccrualAccountDesignator(AccountDesignators.LATE_FEE_ACCRUAL);
+    lateFee.setProportionalTo(ChargeProportionalDesignator.CONTRACTUAL_REPAYMENT_DESIGNATOR.getValue());
+    lateFee.setChargeOnTop(true);
+    lateFee.setReadOnly(false);
 
     ret.add(processingFee);
     ret.add(loanOriginationFee);
-    //TODO: ret.add(loanFundsAllocation);
     ret.add(disbursementFee);
-    ret.add(disbursePayment);
-    //TODO: ret.add(trackPrincipalDisbursePayment);
     ret.add(lateFee);
-    ret.add(writeOffAllowanceCharge);
-    ret.add(interestCharge);
-    ret.add(customerRepaymentCharge);
-    //TODO: ret.add(trackReturnPrincipalCharge);
-    //TODO: ret.add(disbursementReturnCharge);
 
     return ret;
   }
diff --git a/service/src/main/java/io/mifos/individuallending/internal/command/handler/BeatPublishCommandHandler.java b/service/src/main/java/io/mifos/individuallending/internal/command/handler/BeatPublishCommandHandler.java
index cb63a4f..91c427f 100644
--- a/service/src/main/java/io/mifos/individuallending/internal/command/handler/BeatPublishCommandHandler.java
+++ b/service/src/main/java/io/mifos/individuallending/internal/command/handler/BeatPublishCommandHandler.java
@@ -127,16 +127,16 @@
 
     final DesignatorToAccountIdentifierMapper designatorToAccountIdentifierMapper
         = new DesignatorToAccountIdentifierMapper(dataContextOfAction);
-    final String customerLoanAccountIdentifier = designatorToAccountIdentifierMapper.mapOrThrow(AccountDesignators.CUSTOMER_LOAN);
+    final String customerLoanPrincipalAccountIdentifier = designatorToAccountIdentifierMapper.mapOrThrow(AccountDesignators.CUSTOMER_LOAN_PRINCIPAL);
     final String lateFeeAccrualAccountIdentifier = designatorToAccountIdentifierMapper.mapOrThrow(AccountDesignators.LATE_FEE_ACCRUAL);
 
-    final BigDecimal currentBalance = accountingAdapter.getCurrentBalance(customerLoanAccountIdentifier);
+    final BigDecimal currentBalance = accountingAdapter.getCurrentAccountBalance(customerLoanPrincipalAccountIdentifier);
     if (currentBalance.compareTo(BigDecimal.ZERO) == 0) //No late fees if the current balance is zilch.
       return new IndividualLoanCommandEvent(productIdentifier, caseIdentifier, command.getForTime());
 
 
     final LocalDateTime dateOfMostRecentDisbursement =
-        accountingAdapter.getDateOfMostRecentEntryContainingMessage(customerLoanAccountIdentifier, dataContextOfAction.getMessageForCharge(Action.DISBURSE))
+        accountingAdapter.getDateOfMostRecentEntryContainingMessage(customerLoanPrincipalAccountIdentifier, dataContextOfAction.getMessageForCharge(Action.DISBURSE))
             .orElseThrow(() ->
                 ServiceException.badRequest("No last disbursal date for ''{0}.{1}'' could be determined.  " +
                     "Therefore it cannot be checked for lateness.", productIdentifier, caseIdentifier));
@@ -155,7 +155,7 @@
         .multiply(BigDecimal.valueOf(repaymentPeriodsBetweenBeginningAndToday));
 
     final BigDecimal paymentsSum = accountingAdapter.sumMatchingEntriesSinceDate(
-        customerLoanAccountIdentifier,
+        customerLoanPrincipalAccountIdentifier,
         dateOfMostRecentDisbursement.toLocalDate(),
         dataContextOfAction.getMessageForCharge(Action.ACCEPT_PAYMENT));
 
@@ -165,7 +165,7 @@
         dataContextOfAction.getMessageForCharge(Action.MARK_LATE));
 
     if (paymentsSum.compareTo(expectedPaymentSum.add(lateFeesAccrued)) < 0) {
-      final Optional<LocalDateTime> dateOfMostRecentLateFee = accountingAdapter.getDateOfMostRecentEntryContainingMessage(customerLoanAccountIdentifier, dataContextOfAction.getMessageForCharge(Action.MARK_LATE));
+      final Optional<LocalDateTime> dateOfMostRecentLateFee = accountingAdapter.getDateOfMostRecentEntryContainingMessage(customerLoanPrincipalAccountIdentifier, dataContextOfAction.getMessageForCharge(Action.MARK_LATE));
       if (!dateOfMostRecentLateFee.isPresent() ||
           mostRecentLateFeeIsBeforeMostRecentRepaymentPeriod(repaymentPeriods, dateOfMostRecentLateFee.get())) {
         commandBus.dispatch(new MarkLateCommand(productIdentifier, caseIdentifier, command.getForTime()));
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 fb51ef3..5caf2ea 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
@@ -169,15 +169,27 @@
     final DesignatorToAccountIdentifierMapper designatorToAccountIdentifierMapper
             = new DesignatorToAccountIdentifierMapper(dataContextOfAction);
 
+    //Create the needed account assignments for groups and persist them for the case.
+    designatorToAccountIdentifierMapper.getGroupsNeedingLedgers()
+        .map(groupNeedingLedger -> new AccountAssignment(groupNeedingLedger.getGroupName(),
+            accountingAdapter.createLedger(
+                dataContextOfAction.getCaseParametersEntity().getCustomerIdentifier(),
+                groupNeedingLedger.getGroupName(),
+                groupNeedingLedger.getParentLedger())))
+        .map(accountAssignment -> CaseMapper.map(accountAssignment, dataContextOfAction.getCustomerCaseEntity()))
+        .forEach(caseAccountAssignmentEntity -> dataContextOfAction.getCustomerCaseEntity().getAccountAssignments().add(caseAccountAssignmentEntity));
+
     //Create the needed account assignments and persist them for the case.
     designatorToAccountIdentifierMapper.getLedgersNeedingAccounts()
-            .map(ledger ->
-                    new AccountAssignment(ledger.getDesignator(),
-                            accountingAdapter.createAccountForLedgerAssignment(dataContextOfAction.getCaseParametersEntity().getCustomerIdentifier(), ledger)))
-            .map(accountAssignment -> CaseMapper.map(accountAssignment, dataContextOfAction.getCustomerCaseEntity()))
-            .forEach(caseAccountAssignmentEntity ->
-              dataContextOfAction.getCustomerCaseEntity().getAccountAssignments().add(caseAccountAssignmentEntity)
-            );
+        .map(ledger ->
+            new AccountAssignment(ledger.getDesignator(),
+                accountingAdapter.createAccountForLedgerAssignment(
+                    dataContextOfAction.getCaseParametersEntity().getCustomerIdentifier(),
+                    ledger)))
+        .map(accountAssignment -> CaseMapper.map(accountAssignment, dataContextOfAction.getCustomerCaseEntity()))
+        .forEach(caseAccountAssignmentEntity ->
+            dataContextOfAction.getCustomerCaseEntity().getAccountAssignments().add(caseAccountAssignmentEntity)
+        );
     caseRepository.save(dataContextOfAction.getCustomerCaseEntity());
 
     final PaymentBuilder paymentBuilder =
@@ -239,8 +251,10 @@
       customerCase.setCurrentState(Case.State.ACTIVE.name());
       caseRepository.save(customerCase);
     }
-    final String customerLoanAccountIdentifier = designatorToAccountIdentifierMapper.mapOrThrow(AccountDesignators.CUSTOMER_LOAN);
-    final BigDecimal currentBalance = accountingAdapter.getCurrentBalance(customerLoanAccountIdentifier).negate();
+    final String customerLoanPrinicipalAccountIdentifier = designatorToAccountIdentifierMapper.mapOrThrow(AccountDesignators.CUSTOMER_LOAN_PRINCIPAL);
+    final String customerLoanInterestAccountIdentifier = designatorToAccountIdentifierMapper.mapOrThrow(AccountDesignators.CUSTOMER_LOAN_INTEREST);
+    final String customerLoanFeesAccountIdentifier = designatorToAccountIdentifierMapper.mapOrThrow(AccountDesignators.CUSTOMER_LOAN_FEES);
+    final BigDecimal currentBalance = accountingAdapter.getTotalOfCurrentAccountBalances(customerLoanPrinicipalAccountIdentifier, customerLoanInterestAccountIdentifier, customerLoanFeesAccountIdentifier);
 
     final BigDecimal newLoanPaymentSize = costComponentService.getLoanPaymentSizeForSingleDisbursement(
         currentBalance.add(disbursalAmount),
diff --git a/service/src/main/java/io/mifos/individuallending/internal/service/CostComponentService.java b/service/src/main/java/io/mifos/individuallending/internal/service/CostComponentService.java
index cbca5ee..69b7c33 100644
--- a/service/src/main/java/io/mifos/individuallending/internal/service/CostComponentService.java
+++ b/service/src/main/java/io/mifos/individuallending/internal/service/CostComponentService.java
@@ -158,9 +158,9 @@
       final @Nullable BigDecimal requestedDisbursalSize) {
     final DesignatorToAccountIdentifierMapper designatorToAccountIdentifierMapper
         = new DesignatorToAccountIdentifierMapper(dataContextOfAction);
-    final String customerLoanAccountIdentifier = designatorToAccountIdentifierMapper.mapOrThrow(AccountDesignators.CUSTOMER_LOAN);
+    final String customerLoanPrincipalAccountIdentifier = designatorToAccountIdentifierMapper.mapOrThrow(AccountDesignators.CUSTOMER_LOAN_PRINCIPAL);
     final RealRunningBalances runningBalances = new RealRunningBalances(accountingAdapter, designatorToAccountIdentifierMapper);
-    final BigDecimal currentBalance = runningBalances.getBalance(AccountDesignators.CUSTOMER_LOAN);
+    final BigDecimal currentBalance = runningBalances.getBalance(AccountDesignators.CUSTOMER_LOAN_PRINCIPAL);
 
     if (requestedDisbursalSize != null &&
         dataContextOfAction.getCaseParametersEntity().getBalanceRangeMaximum().compareTo(
@@ -168,7 +168,7 @@
       throw ServiceException.conflict("Cannot disburse over the maximum balance.");
 
     final Optional<LocalDateTime> optionalStartOfTerm = accountingAdapter.getDateOfOldestEntryContainingMessage(
-        customerLoanAccountIdentifier,
+        customerLoanPrincipalAccountIdentifier,
         dataContextOfAction.getMessageForCharge(Action.DISBURSE));
     final CaseParametersEntity caseParameters = dataContextOfAction.getCaseParametersEntity();
     final String productIdentifier = dataContextOfAction.getProductEntity().getIdentifier();
@@ -218,10 +218,9 @@
   {
     final DesignatorToAccountIdentifierMapper designatorToAccountIdentifierMapper
         = new DesignatorToAccountIdentifierMapper(dataContextOfAction);
-    final String customerLoanAccountIdentifier = designatorToAccountIdentifierMapper.mapOrThrow(AccountDesignators.CUSTOMER_LOAN);
     final RunningBalances runningBalances = new RealRunningBalances(accountingAdapter, designatorToAccountIdentifierMapper);
 
-    final LocalDate startOfTerm = getStartOfTermOrThrow(dataContextOfAction, customerLoanAccountIdentifier);
+    final LocalDate startOfTerm = getStartOfTermOrThrow(dataContextOfAction, designatorToAccountIdentifierMapper);
 
     final CaseParametersEntity caseParameters = dataContextOfAction.getCaseParametersEntity();
     final String productIdentifier = dataContextOfAction.getProductEntity().getIdentifier();
@@ -262,10 +261,9 @@
   {
     final DesignatorToAccountIdentifierMapper designatorToAccountIdentifierMapper
         = new DesignatorToAccountIdentifierMapper(dataContextOfAction);
-    final String customerLoanAccountIdentifier = designatorToAccountIdentifierMapper.mapOrThrow(AccountDesignators.CUSTOMER_LOAN);
     final RealRunningBalances runningBalances = new RealRunningBalances(accountingAdapter, designatorToAccountIdentifierMapper);
 
-    final LocalDate startOfTerm = getStartOfTermOrThrow(dataContextOfAction, customerLoanAccountIdentifier);
+    final LocalDate startOfTerm = getStartOfTermOrThrow(dataContextOfAction, designatorToAccountIdentifierMapper);
 
     final CaseParametersEntity caseParameters = dataContextOfAction.getCaseParametersEntity();
     final String productIdentifier = dataContextOfAction.getProductEntity().getIdentifier();
@@ -299,10 +297,10 @@
     }
     else {
       if (scheduledAction.actionPeriod != null && scheduledAction.actionPeriod.isLastPeriod()) {
-        loanPaymentSize = runningBalances.getBalance(AccountDesignators.CUSTOMER_LOAN);
+        loanPaymentSize = runningBalances.getBalance(AccountDesignators.CUSTOMER_LOAN_GROUP);
       }
       else {
-        final BigDecimal paymentSizeBeforeOnTopCharges = runningBalances.getBalance(AccountDesignators.CUSTOMER_LOAN)
+        final BigDecimal paymentSizeBeforeOnTopCharges = runningBalances.getBalance(AccountDesignators.CUSTOMER_LOAN_GROUP)
             .min(dataContextOfAction.getCaseParametersEntity().getPaymentSize());
 
         @SuppressWarnings("UnnecessaryLocalVariable")
@@ -332,14 +330,12 @@
   public PaymentBuilder getCostComponentsForClose(final DataContextOfAction dataContextOfAction) {
     final DesignatorToAccountIdentifierMapper designatorToAccountIdentifierMapper
         = new DesignatorToAccountIdentifierMapper(dataContextOfAction);
-    final String customerLoanAccountIdentifier = designatorToAccountIdentifierMapper.mapOrThrow(AccountDesignators.CUSTOMER_LOAN);
     final RealRunningBalances runningBalances = new RealRunningBalances(accountingAdapter, designatorToAccountIdentifierMapper);
 
-    if (runningBalances.getBalance(AccountDesignators.CUSTOMER_LOAN).compareTo(BigDecimal.ZERO) != 0)
+    if (runningBalances.getBalance(AccountDesignators.CUSTOMER_LOAN_GROUP).compareTo(BigDecimal.ZERO) != 0)
       throw ServiceException.conflict("Cannot close loan until the balance is zero.");
 
-
-    final LocalDate startOfTerm = getStartOfTermOrThrow(dataContextOfAction, customerLoanAccountIdentifier);
+    final LocalDate startOfTerm = getStartOfTermOrThrow(dataContextOfAction, designatorToAccountIdentifierMapper);
 
     final CaseParametersEntity caseParameters = dataContextOfAction.getCaseParametersEntity();
     final String productIdentifier = dataContextOfAction.getProductEntity().getIdentifier();
@@ -379,10 +375,9 @@
   {
     final DesignatorToAccountIdentifierMapper designatorToAccountIdentifierMapper
         = new DesignatorToAccountIdentifierMapper(dataContextOfAction);
-    final String customerLoanAccountIdentifier = designatorToAccountIdentifierMapper.mapOrThrow(AccountDesignators.CUSTOMER_LOAN);
     final RunningBalances runningBalances = new RealRunningBalances(accountingAdapter, designatorToAccountIdentifierMapper);
 
-    final LocalDate startOfTerm = getStartOfTermOrThrow(dataContextOfAction, customerLoanAccountIdentifier);
+    final LocalDate startOfTerm = getStartOfTermOrThrow(dataContextOfAction, designatorToAccountIdentifierMapper);
 
     final CaseParametersEntity caseParameters = dataContextOfAction.getCaseParametersEntity();
     final String productIdentifier = dataContextOfAction.getProductEntity().getIdentifier();
@@ -516,8 +511,8 @@
       case MAXIMUM_BALANCE_DESIGNATOR:
         return maximumBalance;
       case RUNNING_BALANCE_DESIGNATOR: {
-        final BigDecimal customerLoanRunningBalance = runningBalances.getBalance(AccountDesignators.CUSTOMER_LOAN);
-        return customerLoanRunningBalance.subtract(paymentBuilder.getBalanceAdjustment(AccountDesignators.CUSTOMER_LOAN));
+        final BigDecimal customerLoanRunningBalance = runningBalances.getBalance(AccountDesignators.CUSTOMER_LOAN_GROUP);
+        return customerLoanRunningBalance.subtract(paymentBuilder.getBalanceAdjustment(AccountDesignators.CUSTOMER_LOAN_GROUP));
       }
       case CONTRACTUAL_REPAYMENT_DESIGNATOR:
         return contractualRepayment;
@@ -602,7 +597,7 @@
         minorCurrencyUnitDigits,
         false
         );
-    final BigDecimal finalDisbursementSize = paymentBuilder.getBalanceAdjustment(AccountDesignators.CUSTOMER_LOAN).negate();
+    final BigDecimal finalDisbursementSize = paymentBuilder.getBalanceAdjustment(AccountDesignators.CUSTOMER_LOAN_PRINCIPAL).negate();
 
     final MonetaryAmount presentValue = AnnuityPayment.calculate(
         Money.of(finalDisbursementSize, "XXX"),
@@ -644,9 +639,12 @@
   }
 
   private LocalDate getStartOfTermOrThrow(final DataContextOfAction dataContextOfAction,
-                                          final String customerLoanAccountIdentifier) {
+                                          final DesignatorToAccountIdentifierMapper designatorToAccountIdentifierMapper) {
+
+    final String customerLoanPrincipalAccountIdentifier = designatorToAccountIdentifierMapper.mapOrThrow(AccountDesignators.CUSTOMER_LOAN_PRINCIPAL);
+
     final Optional<LocalDateTime> firstDisbursalDateTime = accountingAdapter.getDateOfOldestEntryContainingMessage(
-        customerLoanAccountIdentifier,
+        customerLoanPrincipalAccountIdentifier,
         dataContextOfAction.getMessageForCharge(Action.DISBURSE));
 
     return firstDisbursalDateTime.map(LocalDateTime::toLocalDate)
diff --git a/service/src/main/java/io/mifos/individuallending/internal/service/DesignatorToAccountIdentifierMapper.java b/service/src/main/java/io/mifos/individuallending/internal/service/DesignatorToAccountIdentifierMapper.java
index 851bedd..fbc0273 100644
--- a/service/src/main/java/io/mifos/individuallending/internal/service/DesignatorToAccountIdentifierMapper.java
+++ b/service/src/main/java/io/mifos/individuallending/internal/service/DesignatorToAccountIdentifierMapper.java
@@ -16,17 +16,19 @@
 package io.mifos.individuallending.internal.service;
 
 import io.mifos.core.lang.ServiceException;
+import io.mifos.individuallending.IndividualLendingPatternFactory;
 import io.mifos.individuallending.api.v1.domain.product.AccountDesignators;
 import io.mifos.portfolio.api.v1.domain.AccountAssignment;
+import io.mifos.portfolio.api.v1.domain.RequiredAccountAssignment;
 import io.mifos.portfolio.service.internal.mapper.CaseMapper;
 import io.mifos.portfolio.service.internal.mapper.ProductMapper;
 import io.mifos.portfolio.service.internal.repository.CaseAccountAssignmentEntity;
 import io.mifos.portfolio.service.internal.repository.ProductAccountAssignmentEntity;
 
 import javax.annotation.Nonnull;
-import java.util.List;
-import java.util.Optional;
-import java.util.Set;
+import java.util.*;
+import java.util.function.Function;
+import java.util.stream.Collectors;
 import java.util.stream.Stream;
 
 /**
@@ -38,9 +40,19 @@
   private final @Nonnull List<AccountAssignment> oneTimeAccountAssignments;
 
   public DesignatorToAccountIdentifierMapper(final @Nonnull DataContextOfAction dataContextOfAction) {
-    this.productAccountAssignments = dataContextOfAction.getProductEntity().getAccountAssignments();
-    this.caseAccountAssignments = dataContextOfAction.getCustomerCaseEntity().getAccountAssignments();
-    this.oneTimeAccountAssignments = dataContextOfAction.getOneTimeAccountAssignments();
+    this(dataContextOfAction.getProductEntity().getAccountAssignments(),
+        dataContextOfAction.getCustomerCaseEntity().getAccountAssignments(),
+        dataContextOfAction.getOneTimeAccountAssignments());
+  }
+
+  DesignatorToAccountIdentifierMapper(
+      final @Nonnull Set<ProductAccountAssignmentEntity> productAccountAssignments,
+      final @Nonnull Set<CaseAccountAssignmentEntity> caseAccountAssignments,
+      final @Nonnull List<AccountAssignment> oneTimeAccountAssignments) {
+
+    this.productAccountAssignments = productAccountAssignments;
+    this.caseAccountAssignments = caseAccountAssignments;
+    this.oneTimeAccountAssignments = oneTimeAccountAssignments;
   }
 
   private Stream<AccountAssignment> allAccountAssignmentsAsStream() {
@@ -49,13 +61,36 @@
 
   private Stream<AccountAssignment> fixedAccountAssignmentsAsStream() {
     return Stream.concat(caseAccountAssignments.stream().map(CaseMapper::mapAccountAssignmentEntity),
-            productAccountAssignments.stream().map(ProductMapper::mapAccountAssignmentEntity));
+        productAccountAssignmentsAsStream());
+  }
+
+  private Stream<AccountAssignment> productAccountAssignmentsAsStream() {
+    return productAccountAssignments.stream().map(ProductMapper::mapAccountAssignmentEntity);
+  }
+
+  private Optional<AccountAssignment> mapToAccountAssignment(final @Nonnull String accountDesignator) {
+    return allAccountAssignmentsAsStream()
+        .filter(x -> x.getDesignator().equals(accountDesignator))
+        .findFirst();
+  }
+
+  private Optional<AccountAssignment> mapToProductAccountAssignment(final @Nonnull String accountDesignator) {
+    return productAccountAssignments.stream().map(ProductMapper::mapAccountAssignmentEntity)
+        .filter(x -> x.getDesignator().equals(accountDesignator))
+        .findFirst();
+  }
+
+  Optional<AccountAssignment> mapToCaseAccountAssignment(final @Nonnull String accountDesignator) {
+    return caseAccountAssignments.stream().map(CaseMapper::mapAccountAssignmentEntity)
+        .filter(x -> x.getDesignator().equals(accountDesignator))
+        .findFirst();
   }
 
   public Optional<String> map(final @Nonnull String accountDesignator) {
-    return allAccountAssignmentsAsStream()
-        .filter(x -> x.getDesignator().equals(accountDesignator))
-        .findFirst()
+    final Set<String> accountAssignmentGroups = IndividualLendingPatternFactory.individualLendingPattern().getAccountAssignmentGroups();
+    if (accountAssignmentGroups.contains(accountDesignator))
+      return Optional.empty();
+    return mapToAccountAssignment(accountDesignator)
         .map(AccountAssignment::getAccountIdentifier);
   }
 
@@ -64,9 +99,109 @@
         ServiceException.badRequest("A required account designator was not set ''{0}''.", accountDesignator));
   }
 
+  @SuppressWarnings("OptionalUsedAsFieldOrParameterType")
+  public static class GroupNeedingLedger {
+    final String groupName;
+    final String parentLedger;
+
+    GroupNeedingLedger(final String groupName, final String parentLedger) {
+      this.groupName = groupName;
+      this.parentLedger = parentLedger;
+    }
+
+    public String getGroupName() {
+      return groupName;
+    }
+
+    public String getParentLedger() {
+      return parentLedger;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+      if (this == o) return true;
+      if (o == null || getClass() != o.getClass()) return false;
+      GroupNeedingLedger that = (GroupNeedingLedger) o;
+      return Objects.equals(groupName, that.groupName) &&
+          Objects.equals(parentLedger, that.parentLedger);
+    }
+
+    @Override
+    public int hashCode() {
+      return Objects.hash(groupName, parentLedger);
+    }
+
+    @Override
+    public String toString() {
+      return "GroupNeedingLedger{" +
+          "groupName='" + groupName + '\'' +
+          ", parentLedger='" + parentLedger + '\'' +
+          '}';
+    }
+  }
+
+  public Stream<GroupNeedingLedger> getGroupsNeedingLedgers() {
+    //If all of the accounts in one group are assigned the same ledger, create a grouping ledger at the case level for
+    // those accounts under that ledger.
+    //Save that grouping ledger to an account assignment using the group name as its designator.
+    //To this end, return a stream of group names requiring a ledger, and the parent ledger under which the ledger
+    // should be created.
+
+    final Set<String> accountAssignmentGroups = IndividualLendingPatternFactory.individualLendingPattern().getAccountAssignmentGroups();
+    final Set<RequiredAccountAssignment> accountAssignmentsRequired = IndividualLendingPatternFactory.individualLendingPattern().getAccountAssignmentsRequired();
+
+    return accountAssignmentGroups.stream()
+        .filter(groupName -> !mapToProductAccountAssignment(groupName).isPresent()) //Only assign groups to ledgers which aren't already assigned.
+        .map(groupName -> {
+          final Stream<RequiredAccountAssignment> requiredAccountAssignmentsInThisGroup
+              = accountAssignmentsRequired.stream().filter(x -> groupName.equals(x.getGroup()));
+          final List<String> ledgersAssignedToThem = requiredAccountAssignmentsInThisGroup
+              .map(requiredAccountAssignment -> mapToProductAccountAssignment(requiredAccountAssignment.getAccountDesignator()))
+              .map(optionalAccountAssignment -> optionalAccountAssignment.map(AccountAssignment::getLedgerIdentifier))
+              .distinct()
+              .filter(Optional::isPresent)
+              .map(Optional::get)
+              .limit(2) //If there's more than one then we won't be creating this ledger.  We don't care about more than two.
+              .collect(Collectors.toList());
+          if (ledgersAssignedToThem.size() == 1) {
+            //noinspection ConstantConditions
+            return new GroupNeedingLedger(groupName, ledgersAssignedToThem.get(0));
+          }
+          else
+            return null;
+        })
+        .filter(Objects::nonNull);
+  }
+
   public Stream<AccountAssignment> getLedgersNeedingAccounts() {
-    return fixedAccountAssignmentsAsStream()
-            .filter(x -> !x.getDesignator().equals(AccountDesignators.ENTRY))
-            .filter(x -> (x.getAccountIdentifier() == null) && (x.getLedgerIdentifier() != null));
+    final Set<String> accountAssignmentGroups = IndividualLendingPatternFactory.individualLendingPattern().getAccountAssignmentGroups();
+    final Set<RequiredAccountAssignment> accountAssignmentsRequired = IndividualLendingPatternFactory.individualLendingPattern().getAccountAssignmentsRequired();
+    final Map<String, RequiredAccountAssignment> accountAssignmentsRequiredMap = accountAssignmentsRequired.stream().collect(Collectors.toMap(RequiredAccountAssignment::getAccountDesignator, x -> x));
+    final Map<String, Optional<String>> groupToLedgerMapping = accountAssignmentGroups.stream()
+        .collect(Collectors.toMap(
+            Function.identity(),
+            group -> mapToCaseAccountAssignment(group).map(AccountAssignment::getAccountIdentifier)));
+
+    final Stream<AccountAssignment> ledgerAccountAssignments = productAccountAssignmentsAsStream()
+        .filter(x -> !x.getDesignator().equals(AccountDesignators.ENTRY))
+        .filter(x -> (x.getAccountIdentifier() == null) && (x.getLedgerIdentifier() != null));
+
+    return ledgerAccountAssignments
+        .map(ledgerAccountAssignment -> {
+          final String accountAssignmentGroup = accountAssignmentsRequiredMap.get(ledgerAccountAssignment.getDesignator()).getGroup();
+          if (accountAssignmentGroup == null)
+            return ledgerAccountAssignment;
+          else {
+            final Optional<String> changedLedger = groupToLedgerMapping.get(accountAssignmentGroup);
+            if (!changedLedger.isPresent())
+              return ledgerAccountAssignment;
+            else {
+              final AccountAssignment ret = new AccountAssignment();
+              ret.setDesignator(ledgerAccountAssignment.getDesignator());
+              ret.setLedgerIdentifier(changedLedger.get());
+              return ret;
+            }
+          }
+        });
   }
 }
\ No newline at end of file
diff --git a/service/src/main/java/io/mifos/individuallending/internal/service/PaymentBuilder.java b/service/src/main/java/io/mifos/individuallending/internal/service/PaymentBuilder.java
index f158dee..feaabb1 100644
--- a/service/src/main/java/io/mifos/individuallending/internal/service/PaymentBuilder.java
+++ b/service/src/main/java/io/mifos/individuallending/internal/service/PaymentBuilder.java
@@ -16,11 +16,13 @@
 package io.mifos.individuallending.internal.service;
 
 import com.google.common.collect.Sets;
+import io.mifos.individuallending.IndividualLendingPatternFactory;
 import io.mifos.individuallending.api.v1.domain.caseinstance.PlannedPayment;
 import io.mifos.individuallending.api.v1.domain.workflow.Action;
 import io.mifos.portfolio.api.v1.domain.ChargeDefinition;
 import io.mifos.portfolio.api.v1.domain.CostComponent;
 import io.mifos.portfolio.api.v1.domain.Payment;
+import io.mifos.portfolio.api.v1.domain.RequiredAccountAssignment;
 import io.mifos.portfolio.service.internal.util.ChargeInstance;
 
 import java.math.BigDecimal;
@@ -107,26 +109,18 @@
       final ChargeDefinition chargeDefinition,
       final BigDecimal chargeAmount) {
     BigDecimal adjustedChargeAmount = BigDecimal.ZERO;
-    if (this.accrualAccounting) {
-      if (chargeIsAccrued(chargeDefinition)) {
-        if (Action.valueOf(chargeDefinition.getAccrueAction()) == action) {
-          final BigDecimal maxDebit = prePaymentBalances.getMaxDebit(chargeDefinition.getFromAccountDesignator(), chargeAmount);
-          adjustedChargeAmount = prePaymentBalances.getMaxCredit(chargeDefinition.getAccrualAccountDesignator(), maxDebit);
-
-          this.addToBalance(chargeDefinition.getFromAccountDesignator(), adjustedChargeAmount.negate());
-          this.addToBalance(chargeDefinition.getAccrualAccountDesignator(), adjustedChargeAmount);
-        } else if (Action.valueOf(chargeDefinition.getChargeAction()) == action) {
-          final BigDecimal maxDebit = prePaymentBalances.getMaxDebit(chargeDefinition.getAccrualAccountDesignator(), chargeAmount);
-          adjustedChargeAmount = prePaymentBalances.getMaxCredit(chargeDefinition.getToAccountDesignator(), maxDebit);
-
-          this.addToBalance(chargeDefinition.getAccrualAccountDesignator(), adjustedChargeAmount.negate());
-          this.addToBalance(chargeDefinition.getToAccountDesignator(), adjustedChargeAmount);
-        }
-      } else if (Action.valueOf(chargeDefinition.getChargeAction()) == action) {
+    if (this.accrualAccounting && chargeIsAccrued(chargeDefinition)) {
+      if (Action.valueOf(chargeDefinition.getAccrueAction()) == action) {
         final BigDecimal maxDebit = prePaymentBalances.getMaxDebit(chargeDefinition.getFromAccountDesignator(), chargeAmount);
-        adjustedChargeAmount = prePaymentBalances.getMaxCredit(chargeDefinition.getToAccountDesignator(), maxDebit);
+        adjustedChargeAmount = prePaymentBalances.getMaxCredit(chargeDefinition.getAccrualAccountDesignator(), maxDebit);
 
         this.addToBalance(chargeDefinition.getFromAccountDesignator(), adjustedChargeAmount.negate());
+        this.addToBalance(chargeDefinition.getAccrualAccountDesignator(), adjustedChargeAmount);
+      } else if (Action.valueOf(chargeDefinition.getChargeAction()) == action) {
+        final BigDecimal maxDebit = prePaymentBalances.getMaxDebit(chargeDefinition.getAccrualAccountDesignator(), chargeAmount);
+        adjustedChargeAmount = prePaymentBalances.getMaxCredit(chargeDefinition.getToAccountDesignator(), maxDebit);
+
+        this.addToBalance(chargeDefinition.getAccrualAccountDesignator(), adjustedChargeAmount.negate());
         this.addToBalance(chargeDefinition.getToAccountDesignator(), adjustedChargeAmount);
       }
     }
@@ -179,9 +173,30 @@
     if (chargeDefinition.getAccrualAccountDesignator() != null)
       accountsToCompare.add(chargeDefinition.getAccrualAccountDesignator());
 
-    return !Sets.intersection(accountsToCompare, forAccountDesignators).isEmpty();
+    final Set<String> expandedForAccountDesignators = expandAccountDesignators(forAccountDesignators);
+
+    return !Sets.intersection(accountsToCompare, expandedForAccountDesignators).isEmpty();
   }
 
+  static Set<String> expandAccountDesignators(final Set<String> accountDesignators) {
+    final Set<RequiredAccountAssignment> accountAssignmentsRequired = IndividualLendingPatternFactory.individualLendingPattern().getAccountAssignmentsRequired();
+    final Map<String, List<RequiredAccountAssignment>> accountAssignmentsByGroup = accountAssignmentsRequired.stream()
+        .filter(x -> x.getGroup() != null)
+        .collect(Collectors.groupingBy(RequiredAccountAssignment::getGroup, Collectors.toList()));
+    final Set<String> groupExpansions = accountDesignators.stream()
+        .flatMap(accountDesignator -> {
+          final List<RequiredAccountAssignment> group = accountAssignmentsByGroup.get(accountDesignator);
+          if (group != null)
+            return group.stream();
+          else
+            return Stream.empty();
+        })
+        .map(RequiredAccountAssignment::getAccountDesignator)
+        .collect(Collectors.toSet());
+    final Set<String> ret = new HashSet<>(accountDesignators);
+    ret.addAll(groupExpansions);
+    return ret;
+  }
 
   private static CostComponent constructEmptyCostComponent(final ChargeDefinition chargeDefinition) {
     final CostComponent ret = new CostComponent();
diff --git a/service/src/main/java/io/mifos/individuallending/internal/service/RealRunningBalances.java b/service/src/main/java/io/mifos/individuallending/internal/service/RealRunningBalances.java
index 761c963..34a6227 100644
--- a/service/src/main/java/io/mifos/individuallending/internal/service/RealRunningBalances.java
+++ b/service/src/main/java/io/mifos/individuallending/internal/service/RealRunningBalances.java
@@ -28,13 +28,12 @@
  * @author Myrle Krantz
  */
 public class RealRunningBalances implements RunningBalances {
-  private final ExpiringMap<String, BigDecimal> realBalanceCache;
-
+  private final ExpiringMap<String, BigDecimal> realAccountBalanceCache;
 
   RealRunningBalances(
       final AccountingAdapter accountingAdapter,
       final DesignatorToAccountIdentifierMapper designatorToAccountIdentifierMapper) {
-    this.realBalanceCache = ExpiringMap.builder()
+    this.realAccountBalanceCache = ExpiringMap.builder()
         .maxSize(20)
         .expirationPolicy(ExpirationPolicy.CREATED)
         .expiration(30,TimeUnit.SECONDS)
@@ -46,13 +45,13 @@
           else {
             accountIdentifier = Optional.of(designatorToAccountIdentifierMapper.mapOrThrow(accountDesignator));
           }
-          return accountIdentifier.map(accountingAdapter::getCurrentBalance).orElse(BigDecimal.ZERO);
+          return accountIdentifier.map(accountingAdapter::getCurrentAccountBalance).orElse(BigDecimal.ZERO);
         })
         .build();
   }
 
   @Override
-  public BigDecimal getBalance(final String accountDesignator) {
-    return realBalanceCache.get(accountDesignator);
+  public BigDecimal getAccountBalance(final String accountDesignator) {
+    return realAccountBalanceCache.get(accountDesignator);
   }
 }
diff --git a/service/src/main/java/io/mifos/individuallending/internal/service/RunningBalances.java b/service/src/main/java/io/mifos/individuallending/internal/service/RunningBalances.java
index 08197f6..1a08a7a 100644
--- a/service/src/main/java/io/mifos/individuallending/internal/service/RunningBalances.java
+++ b/service/src/main/java/io/mifos/individuallending/internal/service/RunningBalances.java
@@ -15,7 +15,10 @@
  */
 package io.mifos.individuallending.internal.service;
 
+import io.mifos.individuallending.IndividualLendingPatternFactory;
 import io.mifos.individuallending.api.v1.domain.product.AccountDesignators;
+import io.mifos.portfolio.api.v1.domain.Pattern;
+import io.mifos.portfolio.api.v1.domain.RequiredAccountAssignment;
 
 import java.math.BigDecimal;
 import java.util.HashMap;
@@ -29,7 +32,9 @@
     final BigDecimal negative = BigDecimal.valueOf(-1);
     final BigDecimal positive = BigDecimal.valueOf(1);
 
-    this.put(AccountDesignators.CUSTOMER_LOAN, negative);
+    this.put(AccountDesignators.CUSTOMER_LOAN_PRINCIPAL, negative);
+    this.put(AccountDesignators.CUSTOMER_LOAN_FEES, negative);
+    this.put(AccountDesignators.CUSTOMER_LOAN_INTEREST, negative);
     this.put(AccountDesignators.LOAN_FUNDS_SOURCE, negative);
     this.put(AccountDesignators.PROCESSING_FEE_INCOME, positive);
     this.put(AccountDesignators.ORIGINATION_FEE_INCOME, positive);
@@ -42,7 +47,24 @@
     this.put(AccountDesignators.ENTRY, positive);
   }};
 
-  BigDecimal getBalance(final String accountDesignator);
+  BigDecimal getAccountBalance(final String accountDesignator);
+
+  default BigDecimal getLedgerBalance(final String ledgerDesignator) {
+    final Pattern individualLendingPattern = IndividualLendingPatternFactory.individualLendingPattern();
+    return individualLendingPattern.getAccountAssignmentsRequired().stream()
+        .filter(requiredAccountAssignment -> ledgerDesignator.equals(requiredAccountAssignment.getGroup()))
+        .map(RequiredAccountAssignment::getAccountDesignator)
+        .map(this::getAccountBalance)
+        .reduce(BigDecimal.ZERO, BigDecimal::add);
+  }
+
+  default BigDecimal getBalance(final String designator) {
+    final Pattern individualLendingPattern = IndividualLendingPatternFactory.individualLendingPattern();
+    if (individualLendingPattern.getAccountAssignmentGroups().contains(designator))
+      return getLedgerBalance(designator);
+    else
+      return getAccountBalance(designator);
+  }
 
   default BigDecimal getMaxDebit(final String accountDesignator, final BigDecimal amount) {
     if (accountDesignator.equals(AccountDesignators.ENTRY))
diff --git a/service/src/main/java/io/mifos/individuallending/internal/service/SimulatedRunningBalances.java b/service/src/main/java/io/mifos/individuallending/internal/service/SimulatedRunningBalances.java
index 3c4ce83..9c96574 100644
--- a/service/src/main/java/io/mifos/individuallending/internal/service/SimulatedRunningBalances.java
+++ b/service/src/main/java/io/mifos/individuallending/internal/service/SimulatedRunningBalances.java
@@ -30,7 +30,8 @@
     this.balances = new HashMap<>();
   }
 
-  public BigDecimal getBalance(final String accountDesignator) {
+  @Override
+  public BigDecimal getAccountBalance(final String accountDesignator) {
     return balances.getOrDefault(accountDesignator, BigDecimal.ZERO);
   }
 
diff --git a/service/src/main/java/io/mifos/portfolio/service/internal/mapper/ChargeDefinitionMapper.java b/service/src/main/java/io/mifos/portfolio/service/internal/mapper/ChargeDefinitionMapper.java
index cff6143..fba4800 100644
--- a/service/src/main/java/io/mifos/portfolio/service/internal/mapper/ChargeDefinitionMapper.java
+++ b/service/src/main/java/io/mifos/portfolio/service/internal/mapper/ChargeDefinitionMapper.java
@@ -104,7 +104,7 @@
         return true;
       case PROCESSING_FEE_ID:
         return true;
-      case REPAYMENT_ID:
+      case REPAY_PRINCIPAL_ID:
         return false;
       default:
         return false;
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 df851db..d305e43 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
@@ -28,6 +28,7 @@
 import io.mifos.portfolio.api.v1.domain.ChargeDefinition;
 import io.mifos.portfolio.service.ServiceConstants;
 import org.apache.commons.lang.RandomStringUtils;
+import org.apache.commons.lang.StringUtils;
 import org.slf4j.Logger;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.beans.factory.annotation.Qualifier;
@@ -49,6 +50,7 @@
 @Component
 public class AccountingAdapter {
 
+
   public enum IdentifierType {LEDGER, ACCOUNT}
 
   private final LedgerManager ledgerManager;
@@ -153,7 +155,11 @@
     return Optional.of(ret);
   }
 
-  public BigDecimal getCurrentBalance(final String accountIdentifier) {
+  public BigDecimal getTotalOfCurrentAccountBalances(final String... accountIdentifiers) {
+    return Arrays.stream(accountIdentifiers).map(this::getCurrentAccountBalance).reduce(BigDecimal.ZERO, BigDecimal::add);
+  }
+
+  public BigDecimal getCurrentAccountBalance(final String accountIdentifier) {
     try {
       final Account account = ledgerManager.findAccount(accountIdentifier);
       if (account == null)
@@ -164,6 +170,24 @@
      throw ServiceException.internalError("Could not find the account with identifier ''{0}''", accountIdentifier);
     }
   }
+  public String createLedger(final String customerIdentifier, final String groupName, final String parentLedger) {
+    final Ledger ledger = ledgerManager.findLedger(parentLedger);
+    final List<Ledger> subLedgers = ledger.getSubLedgers() == null ? Collections.emptyList() : ledger.getSubLedgers();
+
+    final Ledger generatedLedger = new Ledger();
+    generatedLedger.setShowAccountsInChart(true);
+    generatedLedger.setParentLedgerIdentifier(parentLedger);
+    generatedLedger.setType(ledger.getType());
+    final String ledgerIdentifer = createLedgerIdentifier(customerIdentifier, groupName, subLedgers);
+    generatedLedger.setIdentifier(ledgerIdentifer);
+    generatedLedger.setDescription("Individual loan case specific ledger");
+    generatedLedger.setName(ledgerIdentifer);
+
+    logger.info("Creating ledger with identifier '{}'", ledgerIdentifer);
+
+    ledgerManager.createLedger(generatedLedger); //TODO: wait?
+    return ledgerIdentifer;
+  }
 
   public String createAccountForLedgerAssignment(final String customerIdentifier, final AccountAssignment ledgerAssignment) {
     final Ledger ledger = ledgerManager.findLedger(ledgerAssignment.getLedgerIdentifier());
@@ -197,8 +221,24 @@
             customerIdentifier, ledgerAssignment.getDesignator(), ledgerAssignment.getLedgerIdentifier()));
   }
 
+  private String createLedgerIdentifier(
+      final String customerIdentifier,
+      final String groupName,
+      final List<Ledger> subLedgers) {
+    final String partialCustomerIdentifer = StringUtils.left(customerIdentifier, 22);
+    final String partialGroupName = StringUtils.left(groupName, 3);
+    final Set<String> subLedgerIdentifiers = subLedgers.stream().map(Ledger::getIdentifier).collect(Collectors.toSet());
+    long index = 0;
+    while (true) {
+      index++;
+      final String generatedIdentifier = partialCustomerIdentifer + "." + partialGroupName + "." + String.format("%05d", index);
+      if (!subLedgerIdentifiers.contains(generatedIdentifier))
+        return generatedIdentifier;
+    }
+  }
+
   private String createAccountNumber(final String customerIdentifier, final String designator, final long accountIndex) {
-    return customerIdentifier + "." + designator
+    return StringUtils.left(customerIdentifier, 22) + "." + StringUtils.left(designator, 3)
             + "." + String.format("%05d", accountIndex);
   }
 
diff --git a/service/src/test/java/io/mifos/individuallending/internal/service/CostComponentServiceTest.java b/service/src/test/java/io/mifos/individuallending/internal/service/CostComponentServiceTest.java
index 4a639c9..06931c7 100644
--- a/service/src/test/java/io/mifos/individuallending/internal/service/CostComponentServiceTest.java
+++ b/service/src/test/java/io/mifos/individuallending/internal/service/CostComponentServiceTest.java
@@ -108,7 +108,7 @@
   @Test
   public void getAmountProportionalTo() {
     final SimulatedRunningBalances runningBalances = new SimulatedRunningBalances();
-    runningBalances.adjustBalance(AccountDesignators.CUSTOMER_LOAN, testCase.runningBalance.negate());
+    runningBalances.adjustBalance(AccountDesignators.CUSTOMER_LOAN_PRINCIPAL, testCase.runningBalance.negate());
     final BigDecimal amount = CostComponentService.getAmountProportionalTo(
         testCase.chargeProportionalDesignator,
         testCase.maximumBalance,
diff --git a/service/src/test/java/io/mifos/individuallending/internal/service/DesignatorToAccountIdentifierMapperTest.java b/service/src/test/java/io/mifos/individuallending/internal/service/DesignatorToAccountIdentifierMapperTest.java
new file mode 100644
index 0000000..be8a427
--- /dev/null
+++ b/service/src/test/java/io/mifos/individuallending/internal/service/DesignatorToAccountIdentifierMapperTest.java
@@ -0,0 +1,205 @@
+/*
+ * Copyright 2017 Kuelap, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.mifos.individuallending.internal.service;
+
+import io.mifos.individuallending.api.v1.domain.product.AccountDesignators;
+import io.mifos.portfolio.api.v1.domain.AccountAssignment;
+import io.mifos.portfolio.service.internal.repository.CaseAccountAssignmentEntity;
+import io.mifos.portfolio.service.internal.repository.ProductAccountAssignmentEntity;
+import io.mifos.portfolio.service.internal.util.AccountingAdapter;
+import org.junit.Assert;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+import java.util.*;
+import java.util.stream.Collectors;
+
+/**
+ * @author Myrle Krantz
+ */
+@RunWith(Parameterized.class)
+public class DesignatorToAccountIdentifierMapperTest {
+
+
+  @SuppressWarnings("OptionalUsedAsFieldOrParameterType")
+  static private class TestCase {
+    final String description;
+    Set<ProductAccountAssignmentEntity> productAccountAssignments;
+    Set<CaseAccountAssignmentEntity> caseAccountAssignments;
+    List<AccountAssignment> oneTimeAccountAssignments;
+    Set<AccountAssignment> expectedLedgersNeedingAccounts;
+    Optional<AccountAssignment> expectedCaseAccountAssignmentMappingForCustomerLoanGroup;
+    Set<DesignatorToAccountIdentifierMapper.GroupNeedingLedger> expectedGroupsNeedingLedgers;
+    Optional<String> expectedMapCustomerLoanPrincipalResult = Optional.empty();
+
+    private TestCase(String description) {
+      this.description = description;
+    }
+
+    TestCase productAccountAssignments(Set<ProductAccountAssignmentEntity> newVal) {
+      this.productAccountAssignments = newVal;
+      return this;
+    }
+
+    TestCase caseAccountAssignments(Set<CaseAccountAssignmentEntity> newVal) {
+      this.caseAccountAssignments = newVal;
+      return this;
+    }
+
+    TestCase oneTimeAccountAssignments(List<AccountAssignment> newVal) {
+      this.oneTimeAccountAssignments = newVal;
+      return this;
+    }
+
+    TestCase expectedLedgersNeedingAccounts(Set<AccountAssignment> newVal) {
+      this.expectedLedgersNeedingAccounts = newVal;
+      return this;
+    }
+
+    TestCase expectedCaseAccountAssignmentMappingForCustomerLoanGroup(Optional<AccountAssignment> newVal) {
+      this.expectedCaseAccountAssignmentMappingForCustomerLoanGroup = newVal;
+      return this;
+    }
+
+    TestCase expectedGroupsNeedingLedgers(Set<DesignatorToAccountIdentifierMapper.GroupNeedingLedger> newVal) {
+      this.expectedGroupsNeedingLedgers = newVal;
+      return this;
+    }
+
+    @Override
+    public String toString() {
+      return "TestCase{" +
+          "description='" + description + '\'' +
+          '}';
+    }
+  }
+
+  @Parameterized.Parameters
+  public static Collection testCases() {
+    final Collection<TestCase> ret = new ArrayList<>();
+    final TestCase groupedTestCase = new TestCase("basic grouped customer loan assignments")
+        .productAccountAssignments(new HashSet<>(Arrays.asList(
+            pAssignLedger(AccountDesignators.CUSTOMER_LOAN_PRINCIPAL, "x"),
+            pAssignLedger(AccountDesignators.CUSTOMER_LOAN_INTEREST, "x"),
+            pAssignLedger(AccountDesignators.CUSTOMER_LOAN_FEES, "x")
+        )))
+        .caseAccountAssignments(new HashSet<>(Collections.singletonList(
+            cAssignLedger(AccountDesignators.CUSTOMER_LOAN_GROUP)
+        )))
+        .oneTimeAccountAssignments(Collections.emptyList())
+        .expectedLedgersNeedingAccounts(new HashSet<>(Arrays.asList(
+            assignLedger(AccountDesignators.CUSTOMER_LOAN_PRINCIPAL, "y"),
+            assignLedger(AccountDesignators.CUSTOMER_LOAN_INTEREST, "y"),
+            assignLedger(AccountDesignators.CUSTOMER_LOAN_FEES, "y"))))
+        .expectedCaseAccountAssignmentMappingForCustomerLoanGroup(
+            Optional.of(assignAccount(AccountDesignators.CUSTOMER_LOAN_GROUP)))
+        .expectedGroupsNeedingLedgers(new HashSet<>(Collections.singletonList(
+            new DesignatorToAccountIdentifierMapper.GroupNeedingLedger(AccountDesignators.CUSTOMER_LOAN_GROUP, "x"))));
+    ret.add(groupedTestCase);
+
+    final TestCase groupingIgnoredTestCase = new TestCase("customer loan assignments with ignored grouping")
+        .productAccountAssignments(new HashSet<>(Arrays.asList(
+            pAssignLedger(AccountDesignators.CUSTOMER_LOAN_PRINCIPAL, "x"),
+            pAssignLedger(AccountDesignators.CUSTOMER_LOAN_INTEREST, "y"),
+            pAssignLedger(AccountDesignators.CUSTOMER_LOAN_FEES, "z")
+        )))
+        .caseAccountAssignments(Collections.emptySet())
+        .oneTimeAccountAssignments(Collections.emptyList())
+        .expectedLedgersNeedingAccounts(new HashSet<>(Arrays.asList(
+            assignLedger(AccountDesignators.CUSTOMER_LOAN_PRINCIPAL, "x"),
+            assignLedger(AccountDesignators.CUSTOMER_LOAN_INTEREST, "y"),
+            assignLedger(AccountDesignators.CUSTOMER_LOAN_FEES, "z"))))
+        .expectedCaseAccountAssignmentMappingForCustomerLoanGroup(
+            Optional.empty())
+        .expectedGroupsNeedingLedgers(Collections.emptySet());
+    ret.add(groupingIgnoredTestCase);
+    return ret;
+  }
+
+  private static ProductAccountAssignmentEntity pAssignLedger(
+      final String accountDesignator,
+      final String ledgerIdentifier) {
+    final ProductAccountAssignmentEntity ret = new ProductAccountAssignmentEntity();
+    ret.setDesignator(accountDesignator);
+    ret.setIdentifier(ledgerIdentifier);
+    ret.setType(AccountingAdapter.IdentifierType.LEDGER);
+    return ret;
+  }
+
+  private static CaseAccountAssignmentEntity cAssignLedger(
+      final String accountDesignator) {
+    final CaseAccountAssignmentEntity ret = new CaseAccountAssignmentEntity();
+    ret.setDesignator(accountDesignator);
+    ret.setIdentifier("y");
+    return ret;
+  }
+
+  private static AccountAssignment assignLedger(
+      final String accountDesignator,
+      final String ledgerIdentifier) {
+    final AccountAssignment ret = new AccountAssignment();
+    ret.setDesignator(accountDesignator);
+    ret.setLedgerIdentifier(ledgerIdentifier);
+    return ret;
+  }
+
+  private static AccountAssignment assignAccount(
+      final String accountDesignator) {
+    final AccountAssignment ret = new AccountAssignment();
+    ret.setDesignator(accountDesignator);
+    ret.setAccountIdentifier("y");
+    return ret;
+  }
+
+  private final TestCase testCase;
+  private final DesignatorToAccountIdentifierMapper testSubject;
+
+  public DesignatorToAccountIdentifierMapperTest(TestCase testCase) {
+    this.testCase = testCase;
+    this.testSubject = new DesignatorToAccountIdentifierMapper(
+        testCase.productAccountAssignments,
+        testCase.caseAccountAssignments,
+        testCase.oneTimeAccountAssignments);
+  }
+
+  @Test
+  public void map() {
+    Assert.assertEquals(Optional.empty(), testSubject.map(AccountDesignators.CUSTOMER_LOAN_GROUP));
+    Assert.assertEquals(testCase.expectedMapCustomerLoanPrincipalResult, testSubject.map(AccountDesignators.CUSTOMER_LOAN_PRINCIPAL));
+    Assert.assertEquals(Optional.empty(), testSubject.map("this-account-designator-doesnt-exist"));
+  }
+
+  @Test
+  public void mapToCaseAccountAssignment() {
+    final Optional<AccountAssignment> ret = testSubject.mapToCaseAccountAssignment(AccountDesignators.CUSTOMER_LOAN_GROUP);
+    Assert.assertEquals(testCase.expectedCaseAccountAssignmentMappingForCustomerLoanGroup, ret);
+  }
+
+  @Test
+  public void getLedgersNeedingAccounts() {
+    final Set<AccountAssignment> ret = testSubject.getLedgersNeedingAccounts().collect(Collectors.toSet());
+    Assert.assertEquals(testCase.expectedLedgersNeedingAccounts, ret);
+  }
+
+  @Test
+  public void getGroupsNeedingLedgers() {
+    Set<DesignatorToAccountIdentifierMapper.GroupNeedingLedger> ret = testSubject.getGroupsNeedingLedgers().collect(Collectors.toSet());
+    //noinspection ResultOfMethodCallIgnored //Checking GroupNeedingLedger.toString that it doesn't cause exceptions.
+    ret.toString();
+    Assert.assertEquals(testCase.expectedGroupsNeedingLedgers, ret);
+  }
+}
\ No newline at end of file
diff --git a/service/src/test/java/io/mifos/individuallending/internal/service/Fixture.java b/service/src/test/java/io/mifos/individuallending/internal/service/Fixture.java
index 13c7ff3..cf7532b 100644
--- a/service/src/test/java/io/mifos/individuallending/internal/service/Fixture.java
+++ b/service/src/test/java/io/mifos/individuallending/internal/service/Fixture.java
@@ -105,7 +105,7 @@
     chargeDefinition.setAccrueAction(Action.APPLY_INTEREST.name());
     chargeDefinition.setChargeAction(Action.ACCEPT_PAYMENT.name());
     chargeDefinition.setAmount(BigDecimal.ONE);
-    chargeDefinition.setFromAccountDesignator(AccountDesignators.CUSTOMER_LOAN);
+    chargeDefinition.setFromAccountDesignator(AccountDesignators.CUSTOMER_LOAN_INTEREST);
     chargeDefinition.setAccrualAccountDesignator(AccountDesignators.INTEREST_ACCRUAL);
     chargeDefinition.setToAccountDesignator(AccountDesignators.INTEREST_INCOME);
     return new ScheduledCharge(scheduledAction, chargeDefinition, Optional.empty());
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 a31f3e0..8c49177 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
@@ -100,7 +100,7 @@
         LOAN_ORIGINATION_FEE_ID,
         INTEREST_ID,
         DISBURSEMENT_FEE_ID,
-        REPAYMENT_ID,
+        REPAY_PRINCIPAL_ID,
         DISBURSE_PAYMENT_ID,
         LATE_FEE_ID
         ));
@@ -249,7 +249,9 @@
   }
 
   private static List<ChargeDefinition> charges() {
-    return IndividualLendingPatternFactory.defaultIndividualLoanCharges();
+    final List<ChargeDefinition> ret = IndividualLendingPatternFactory.requiredIndividualLoanCharges();
+    ret.addAll(IndividualLendingPatternFactory.defaultIndividualLoanCharges());
+    return ret;
   }
 
   private static ChargeDefinition getFixedSingleChargeDefinition(
@@ -264,7 +266,7 @@
     ret.setChargeAction(action.name());
     ret.setChargeMethod(ChargeDefinition.ChargeMethod.FIXED);
     ret.setProportionalTo(null);
-    ret.setFromAccountDesignator(AccountDesignators.CUSTOMER_LOAN);
+    ret.setFromAccountDesignator(AccountDesignators.CUSTOMER_LOAN_FEES);
     ret.setToAccountDesignator(feeAccountDesignator);
     ret.setForCycleSizeUnit(null);
     return ret;
@@ -316,7 +318,7 @@
         .map(x ->
         {
           final BigDecimal valueOfRepaymentCostComponent = allPlannedPayments.get(x).getPayment().getCostComponents().stream()
-              .filter(costComponent -> costComponent.getChargeIdentifier().equals(ChargeIdentifiers.REPAYMENT_ID))
+              .filter(costComponent -> costComponent.getChargeIdentifier().equals(ChargeIdentifiers.REPAY_PRINCIPAL_ID))
               .map(CostComponent::getAmount)
               .reduce(BigDecimal::add)
               .orElse(BigDecimal.ZERO);
@@ -325,10 +327,13 @@
               .map(CostComponent::getAmount)
               .reduce(BigDecimal::add)
               .orElse(BigDecimal.ZERO);
-          final BigDecimal principalDifference = allPlannedPayments.get(x-1).getBalances().get(AccountDesignators.CUSTOMER_LOAN).subtract(allPlannedPayments.get(x).getBalances().get(AccountDesignators.CUSTOMER_LOAN));
+          final BigDecimal principalDifference =
+              getBalanceForPayment(allPlannedPayments, AccountDesignators.CUSTOMER_LOAN_PRINCIPAL, x - 1)
+                  .subtract(
+                      getBalanceForPayment(allPlannedPayments, AccountDesignators.CUSTOMER_LOAN_PRINCIPAL, x));
           Assert.assertEquals("Checking payment " + x, valueOfRepaymentCostComponent.subtract(valueOfInterestCostComponent), principalDifference);
           Assert.assertNotEquals("Remaining principle should always be positive or zero.",
-              allPlannedPayments.get(x).getBalances().get(AccountDesignators.CUSTOMER_LOAN).signum(), -1);
+              getBalanceForPayment(allPlannedPayments, AccountDesignators.CUSTOMER_LOAN_PRINCIPAL, x).signum(), -1);
           final boolean containsLateFee = allPlannedPayments.get(x).getPayment().getCostComponents().stream().anyMatch(y -> y.getChargeIdentifier().equals(LATE_FEE_ID));
           Assert.assertFalse("Late fee should not be included in planned payments", containsLateFee);
           return valueOfRepaymentCostComponent;
@@ -338,7 +343,7 @@
     //All entries should have the correct scale.
     allPlannedPayments.forEach(x -> {
       x.getPayment().getCostComponents().forEach(y -> Assert.assertEquals(testCase.minorCurrencyUnitDigits, y.getAmount().scale()));
-      Assert.assertEquals(testCase.minorCurrencyUnitDigits, x.getBalances().get(AccountDesignators.CUSTOMER_LOAN).scale());
+      Assert.assertEquals(testCase.minorCurrencyUnitDigits, x.getBalances().get(AccountDesignators.CUSTOMER_LOAN_PRINCIPAL).scale());
       final int uniqueChargeIdentifierCount = x.getPayment().getCostComponents().stream()
           .map(CostComponent::getChargeIdentifier)
           .collect(Collectors.toSet())
@@ -347,9 +352,17 @@
           x.getPayment().getCostComponents().size(), uniqueChargeIdentifierCount);
     });
 
-    Assert.assertEquals("Final balance should be zero.",
+    Assert.assertEquals("Final principal balance should be zero.",
         BigDecimal.ZERO.setScale(testCase.minorCurrencyUnitDigits, BigDecimal.ROUND_HALF_EVEN),
-        allPlannedPayments.get(allPlannedPayments.size()-1).getBalances().get(AccountDesignators.CUSTOMER_LOAN));
+        getBalanceForPayment(allPlannedPayments, AccountDesignators.CUSTOMER_LOAN_PRINCIPAL, allPlannedPayments.size() - 1));
+
+    Assert.assertEquals("Final interest balance should be zero.",
+        BigDecimal.ZERO.setScale(testCase.minorCurrencyUnitDigits, BigDecimal.ROUND_HALF_EVEN),
+        getBalanceForPayment(allPlannedPayments, AccountDesignators.CUSTOMER_LOAN_INTEREST, allPlannedPayments.size() - 1));
+
+    Assert.assertEquals("Final fees balance should be zero.",
+        BigDecimal.ZERO.setScale(testCase.minorCurrencyUnitDigits, BigDecimal.ROUND_HALF_EVEN),
+        getBalanceForPayment(allPlannedPayments, AccountDesignators.CUSTOMER_LOAN_FEES, allPlannedPayments.size() - 1));
 
     //All customer payments should be within one percent of each other.
     final Optional<BigDecimal> maxPayment = customerRepayments.stream().max(BigDecimal::compareTo);
@@ -368,6 +381,13 @@
     Assert.assertEquals(testCase.expectedChargeIdentifiers, resultChargeIdentifiers);
   }
 
+  private BigDecimal getBalanceForPayment(
+      final List<PlannedPayment> allPlannedPayments,
+      final String accountDesignator,
+      int index) {
+    return allPlannedPayments.get(index).getBalances().get(accountDesignator);
+  }
+
   @Test
   public void getScheduledCharges() {
     final List<ScheduledAction> scheduledActions = ScheduledActionHelpers.getHypotheticalScheduledActions(testCase.initialDisbursementDate, testCase.caseParameters);
diff --git a/service/src/test/java/io/mifos/individuallending/internal/service/PaymentBuilderTest.java b/service/src/test/java/io/mifos/individuallending/internal/service/PaymentBuilderTest.java
new file mode 100644
index 0000000..b113d50
--- /dev/null
+++ b/service/src/test/java/io/mifos/individuallending/internal/service/PaymentBuilderTest.java
@@ -0,0 +1,25 @@
+package io.mifos.individuallending.internal.service;
+
+import io.mifos.individuallending.api.v1.domain.product.AccountDesignators;
+import org.junit.Assert;
+import org.junit.Test;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+
+public class PaymentBuilderTest {
+  @Test
+  public void expandAccountDesignators() {
+    final Set<String> ret = PaymentBuilder.expandAccountDesignators(new HashSet<>(Arrays.asList(AccountDesignators.CUSTOMER_LOAN_GROUP, AccountDesignators.ENTRY)));
+    final Set<String> expected = new HashSet<>(Arrays.asList(
+        AccountDesignators.ENTRY,
+        AccountDesignators.CUSTOMER_LOAN_GROUP,
+        AccountDesignators.CUSTOMER_LOAN_PRINCIPAL,
+        AccountDesignators.CUSTOMER_LOAN_FEES,
+        AccountDesignators.CUSTOMER_LOAN_INTEREST));
+
+    Assert.assertEquals(expected, ret);
+  }
+
+}
\ No newline at end of file