Merge pull request #37 from myrle-krantz/develop

Intermediate state so that Mark can start programming against the new…
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 dc65e9b..65bd4e6 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
@@ -23,6 +23,7 @@
   String CUSTOMER_LOAN = "customer-loan";
   String PENDING_DISBURSAL = "pending-disbursal";
   String LOAN_FUNDS_SOURCE = "loan-funds-source";
+  String LOANS_PAYABLE = "loans-payable";
   String PROCESSING_FEE_INCOME = "processing-fee-income";
   String ORIGINATION_FEE_INCOME = "origination-fee-income";
   String DISBURSEMENT_FEE_INCOME = "disbursement-fee-income";
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 37b337a..65e8117 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
@@ -34,15 +34,21 @@
   String LATE_FEE_ID = nameToIdentifier(LATE_FEE_NAME);
   String DISBURSEMENT_FEE_NAME = "Disbursement fee";
   String DISBURSEMENT_FEE_ID = nameToIdentifier(DISBURSEMENT_FEE_NAME);
+  String DISBURSE_PAYMENT_NAME = "Disburse payment";
+  String DISBURSE_PAYMENT_ID = nameToIdentifier(DISBURSE_PAYMENT_NAME);
+  String TRACK_DISBURSAL_PAYMENT_NAME = "Track disburse payment";
+  String TRACK_DISBURSAL_PAYMENT_ID = nameToIdentifier(TRACK_DISBURSAL_PAYMENT_NAME);
   String LOAN_ORIGINATION_FEE_NAME = "Loan origination fee";
   String LOAN_ORIGINATION_FEE_ID = nameToIdentifier(LOAN_ORIGINATION_FEE_NAME);
   String PROCESSING_FEE_NAME = "Processing fee";
   String PROCESSING_FEE_ID = nameToIdentifier(PROCESSING_FEE_NAME);
-  String PAYMENT_NAME = "Payment";
-  String PAYMENT_ID = nameToIdentifier(PAYMENT_NAME);
-
+  String REPAYMENT_NAME = "Repayment";
+  String REPAYMENT_ID = nameToIdentifier(REPAYMENT_NAME);
+  String TRACK_RETURN_PRINCIPAL_NAME = "Return principal";
+  String TRACK_RETURN_PRINCIPAL_ID = nameToIdentifier(TRACK_RETURN_PRINCIPAL_NAME);
   String MAXIMUM_BALANCE_DESIGNATOR = "{maximumbalance}";
   String RUNNING_BALANCE_DESIGNATOR = "{runningbalance}";
+  String PRINCIPAL_ADJUSTMENT_DESIGNATOR = "{principaladjustment}";
 
   static String nameToIdentifier(String name) {
     return name.toLowerCase(Locale.US).replace(" ", "-");
diff --git a/api/src/main/java/io/mifos/portfolio/api/v1/client/PortfolioManager.java b/api/src/main/java/io/mifos/portfolio/api/v1/client/PortfolioManager.java
index 3fbacdd..1b2f61e 100644
--- a/api/src/main/java/io/mifos/portfolio/api/v1/client/PortfolioManager.java
+++ b/api/src/main/java/io/mifos/portfolio/api/v1/client/PortfolioManager.java
@@ -272,10 +272,21 @@
                                 @PathVariable("caseidentifier") final String caseIdentifier);
 
   @RequestMapping(
-          value = "/products/{productidentifier}/cases/{caseidentifier}/actions/{actionidentifier}/costcomponents",
-          method = RequestMethod.GET,
-          produces = MediaType.ALL_VALUE,
-          consumes = MediaType.APPLICATION_JSON_VALUE
+      value = "/products/{productidentifier}/cases/{caseidentifier}/actions/{actionidentifier}/costcomponents",
+      method = RequestMethod.GET,
+      produces = MediaType.ALL_VALUE,
+      consumes = MediaType.APPLICATION_JSON_VALUE
+  )
+  List<CostComponent> getCostComponentsForAction(@PathVariable("productidentifier") final String productIdentifier,
+                                                 @PathVariable("caseidentifier") final String caseIdentifier,
+                                                 @PathVariable("actionidentifier") final String actionIdentifier,
+                                                 @RequestParam(value="touchingaccounts", required = false, defaultValue = "") final String forAccountDesignators);
+
+  @RequestMapping(
+      value = "/products/{productidentifier}/cases/{caseidentifier}/actions/{actionidentifier}/costcomponents",
+      method = RequestMethod.GET,
+      produces = MediaType.ALL_VALUE,
+      consumes = MediaType.APPLICATION_JSON_VALUE
   )
   List<CostComponent> getCostComponentsForAction(@PathVariable("productidentifier") final String productIdentifier,
                                                  @PathVariable("caseidentifier") final String caseIdentifier,
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 961d20d..593a999 100644
--- a/component-test/src/main/java/io/mifos/portfolio/AccountingFixture.java
+++ b/component-test/src/main/java/io/mifos/portfolio/AccountingFixture.java
@@ -56,8 +56,9 @@
   static final String PROCESSING_FEE_INCOME_ACCOUNT_IDENTIFIER = "1312";
   static final String DISBURSEMENT_FEE_INCOME_ACCOUNT_IDENTIFIER = "1313";
   static final String TELLER_ONE_ACCOUNT_IDENTIFIER = "7352";
-  static final String LOAN_INTEREST_ACCRUAL_ACCOUNT = "7810";
-  static final String CONSUMER_LOAN_INTEREST_ACCOUNT = "1103";
+  static final String LOAN_INTEREST_ACCRUAL_ACCOUNT_IDENTIFIER = "7810";
+  static final String CONSUMER_LOAN_INTEREST_ACCOUNT_IDENTIFIER = "1103";
+  static final String LOANS_PAYABLE_ACCOUNT_IDENTIFIER ="missingInChartOfAccounts";
 
   static final Map<String, AccountData> accountMap = new HashMap<>();
 
@@ -73,9 +74,10 @@
       this.account.setBalance(balance);
     }
 
-    void addAccountEntry(final double amount) {
+    void addAccountEntry(final String message, final double amount) {
       final AccountEntry accountEntry = new AccountEntry();
       accountEntry.setAmount(amount);
+      accountEntry.setMessage(message);
       accountEntry.setTransactionDate(DateConverter.toIsoString(LocalDateTime.now(Clock.systemUTC())));
       accountEntries.add(accountEntry);
     }
@@ -87,8 +89,7 @@
     accountMap.put(account.getIdentifier(), accountData);
     Mockito.doAnswer(new AccountEntriesStreamAnswer(accountData))
             .when(ledgerManagerMock)
-            .fetchAccountEntriesStream(Mockito.eq(account.getIdentifier()), Matchers.anyString(), Matchers.anyString());
-
+            .fetchAccountEntriesStream(Mockito.eq(account.getIdentifier()), Matchers.anyString(), Matchers.anyString(), Matchers.eq("ASC"));
   }
 
 
@@ -198,7 +199,7 @@
 
   private static Account loanInterestAccrualAccount() {
     final Account ret = new Account();
-    ret.setIdentifier(LOAN_INTEREST_ACCRUAL_ACCOUNT);
+    ret.setIdentifier(LOAN_INTEREST_ACCRUAL_ACCOUNT_IDENTIFIER);
     ret.setLedger(ACCRUED_INCOME_LEDGER_IDENTIFIER);
     ret.setType(AccountType.ASSET.name());
     return ret;
@@ -206,12 +207,20 @@
 
   private static Account consumerLoanInterestAccount() {
     final Account ret = new Account();
-    ret.setIdentifier(CONSUMER_LOAN_INTEREST_ACCOUNT);
+    ret.setIdentifier(CONSUMER_LOAN_INTEREST_ACCOUNT_IDENTIFIER);
     ret.setLedger(LOAN_INCOME_LEDGER_IDENTIFIER);
     ret.setType(AccountType.REVENUE.name());
     return ret;
   }
 
+  private static Account loansPayableAccount() {
+    final Account ret = new Account();
+    ret.setIdentifier(LOANS_PAYABLE_ACCOUNT_IDENTIFIER);
+    //ret.setLedger(LOAN_INCOME_LEDGER_IDENTIFIER);
+    ret.setType(AccountType.LIABILITY.name());
+    return ret;
+  }
+
   private static AccountPage customerLoanAccountsPage() {
     final Account customerLoanAccount1 = new Account();
     customerLoanAccount1.setIdentifier("customerLoanAccount1");
@@ -316,9 +325,6 @@
 
       checkedArgument = (JournalEntry) argument;
 
-      checkedArgument.getDebtors();
-      checkedArgument.getCreditors();
-
       return this.debtors.equals(checkedArgument.getDebtors()) &&
               this.creditors.equals(checkedArgument.getCreditors());
     }
@@ -337,6 +343,22 @@
     }
   }
 
+  private static class CreateJournalEntryAnswer implements Answer {
+    @Override
+    public Void answer(final InvocationOnMock invocation) throws Throwable {
+      final JournalEntry journalEntry = invocation.getArgumentAt(0, JournalEntry.class);
+      journalEntry.getCreditors().forEach(creditor ->
+          accountMap.get(creditor.getAccountNumber()).addAccountEntry(
+              journalEntry.getMessage(),
+              Double.valueOf(creditor.getAmount())));
+      journalEntry.getDebtors().forEach(debtor ->
+          accountMap.get(debtor.getAccountNumber()).addAccountEntry(
+              journalEntry.getMessage(),
+              Double.valueOf(debtor.getAmount())));
+      return null;
+    }
+  }
+
   private static class FindAccountAnswer implements Answer {
     @Override
     public Account answer(final InvocationOnMock invocation) throws Throwable {
@@ -363,7 +385,11 @@
 
     @Override
     public Stream<AccountEntry> answer(final InvocationOnMock invocation) throws Throwable {
-      return accountData.accountEntries.stream();
+      final String message = invocation.getArgumentAt(2, String.class);
+      if (message != null)
+        return accountData.accountEntries.stream().filter(x -> x.getMessage().equals(message));
+      else
+        return accountData.accountEntries.stream();
     }
   }
 
@@ -375,6 +401,7 @@
     makeAccountResponsive(tellerOneAccount(), universalCreationDate, ledgerManagerMock);
     makeAccountResponsive(loanInterestAccrualAccount(), universalCreationDate, ledgerManagerMock);
     makeAccountResponsive(consumerLoanInterestAccount(), universalCreationDate, ledgerManagerMock);
+    makeAccountResponsive(loansPayableAccount(), universalCreationDate, ledgerManagerMock);
 
     Mockito.doReturn(incomeLedger()).when(ledgerManagerMock).findLedger(INCOME_LEDGER_IDENTIFIER);
     Mockito.doReturn(feesAndChargesLedger()).when(ledgerManagerMock).findLedger(FEES_AND_CHARGES_LEDGER_IDENTIFIER);
@@ -390,6 +417,7 @@
 
     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));
   }
 
   static void mockBalance(final String accountIdentifier, final BigDecimal balance) {
@@ -412,8 +440,6 @@
             Collections.singleton(new Debtor(fromAccountIdentifier, amount.toPlainString())),
             Collections.singleton(new Creditor(toAccountIdentifier, amount.toPlainString())));
     Mockito.verify(ledgerManager).createJournalEntry(AdditionalMatchers.and(argThat(isValid()), argThat(specifiesCorrectJournalEntry)));
-    accountMap.get(fromAccountIdentifier).addAccountEntry(amount.doubleValue() * -1);
-    accountMap.get(toAccountIdentifier).addAccountEntry(amount.doubleValue());
   }
 
   static void verifyTransfer(final LedgerManager ledgerManager,
@@ -421,8 +447,6 @@
                              final Set<Creditor> creditors) {
     final JournalEntryMatcher specifiesCorrectJournalEntry = new JournalEntryMatcher(debtors, creditors);
     Mockito.verify(ledgerManager).createJournalEntry(AdditionalMatchers.and(argThat(isValid()), argThat(specifiesCorrectJournalEntry)));
-    debtors.forEach(debtor -> accountMap.get(debtor.getAccountNumber()).addAccountEntry(Double.valueOf(debtor.getAmount())));
-    creditors.forEach(creditor -> accountMap.get(creditor.getAccountNumber()).addAccountEntry(Double.valueOf(creditor.getAmount())));
 
   }
 }
\ No newline at end of file
diff --git a/component-test/src/main/java/io/mifos/portfolio/Fixture.java b/component-test/src/main/java/io/mifos/portfolio/Fixture.java
index 088bdbe..5910a20 100644
--- a/component-test/src/main/java/io/mifos/portfolio/Fixture.java
+++ b/component-test/src/main/java/io/mifos/portfolio/Fixture.java
@@ -16,11 +16,11 @@
 package io.mifos.portfolio;
 
 import com.google.gson.Gson;
+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.portfolio.api.v1.domain.*;
-import io.mifos.individuallending.api.v1.domain.caseinstance.CaseParameters;
 import io.mifos.individuallending.api.v1.domain.product.ProductParameters;
+import io.mifos.portfolio.api.v1.domain.*;
 
 import java.math.BigDecimal;
 import java.math.RoundingMode;
@@ -66,8 +66,9 @@
     accountAssignments.add(new AccountAssignment(PROCESSING_FEE_INCOME, PROCESSING_FEE_INCOME_ACCOUNT_IDENTIFIER));
     accountAssignments.add(new AccountAssignment(ORIGINATION_FEE_INCOME, LOAN_ORIGINATION_FEES_ACCOUNT_IDENTIFIER));
     accountAssignments.add(new AccountAssignment(DISBURSEMENT_FEE_INCOME, DISBURSEMENT_FEE_INCOME_ACCOUNT_IDENTIFIER));
-    accountAssignments.add(new AccountAssignment(INTEREST_INCOME, CONSUMER_LOAN_INTEREST_ACCOUNT));
-    accountAssignments.add(new AccountAssignment(INTEREST_ACCRUAL, LOAN_INTEREST_ACCRUAL_ACCOUNT));
+    accountAssignments.add(new AccountAssignment(INTEREST_INCOME, CONSUMER_LOAN_INTEREST_ACCOUNT_IDENTIFIER));
+    accountAssignments.add(new AccountAssignment(INTEREST_ACCRUAL, LOAN_INTEREST_ACCRUAL_ACCOUNT_IDENTIFIER));
+    accountAssignments.add(new AccountAssignment(LOANS_PAYABLE, LOANS_PAYABLE_ACCOUNT_IDENTIFIER));
     accountAssignments.add(new AccountAssignment(LATE_FEE_INCOME, "001-008"));
     accountAssignments.add(new AccountAssignment(LATE_FEE_ACCRUAL, "001-009"));
     accountAssignments.add(new AccountAssignment(ARREARS_ALLOWANCE, "001-010"));
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 08bad1c..24f3441 100644
--- a/component-test/src/main/java/io/mifos/portfolio/TestAccountingInteractionInLoanWorkflow.java
+++ b/component-test/src/main/java/io/mifos/portfolio/TestAccountingInteractionInLoanWorkflow.java
@@ -63,6 +63,7 @@
   private String customerLoanAccountIdentifier = null;
 
   private BigDecimal expectedCurrentBalance = null;
+  private BigDecimal interestAccrued = BigDecimal.ZERO;
 
 
   @Before
@@ -207,11 +208,13 @@
 
     final Set<Debtor> debtors = new HashSet<>();
     debtors.add(new Debtor(pendingDisbursalAccountIdentifier, caseParameters.getMaximumBalance().toPlainString()));
-    debtors.add(new Debtor(AccountingFixture.TELLER_ONE_ACCOUNT_IDENTIFIER, DISBURSEMENT_FEE_AMOUNT.toPlainString()));
+    debtors.add(new Debtor(AccountingFixture.TELLER_ONE_ACCOUNT_IDENTIFIER,
+        caseParameters.getMaximumBalance().subtract(DISBURSEMENT_FEE_AMOUNT).toPlainString()));
 
     final Set<Creditor> creditors = new HashSet<>();
     creditors.add(new Creditor(customerLoanAccountIdentifier, caseParameters.getMaximumBalance().toPlainString()));
     creditors.add(new Creditor(AccountingFixture.DISBURSEMENT_FEE_INCOME_ACCOUNT_IDENTIFIER, DISBURSEMENT_FEE_AMOUNT.toPlainString()));
+    creditors.add(new Creditor(AccountingFixture.LOANS_PAYABLE_ACCOUNT_IDENTIFIER, caseParameters.getMaximumBalance().toPlainString()));
     AccountingFixture.verifyTransfer(ledgerManager, debtors, creditors);
 
     expectedCurrentBalance = expectedCurrentBalance.add(caseParameters.getMaximumBalance());
@@ -239,9 +242,11 @@
     final BigDecimal calculatedInterest = caseParameters.getMaximumBalance().multiply(Fixture.INTEREST_RATE.divide(Fixture.ACCRUAL_PERIODS, 8, BigDecimal.ROUND_HALF_EVEN))
         .setScale(MINOR_CURRENCY_UNIT_DIGITS, BigDecimal.ROUND_HALF_EVEN);
 
+    interestAccrued = interestAccrued.add(calculatedInterest);
+
     final Set<Debtor> debtors = new HashSet<>();
     debtors.add(new Debtor(
-        AccountingFixture.LOAN_INTEREST_ACCRUAL_ACCOUNT,
+        AccountingFixture.LOAN_INTEREST_ACCRUAL_ACCOUNT_IDENTIFIER,
         calculatedInterest.toPlainString()));
 
     final Set<Creditor> creditors = new HashSet<>();
@@ -264,14 +269,20 @@
         Action.ACCEPT_PAYMENT,
         Collections.singletonList(assignEntryToTeller()),
         IndividualLoanEventConstants.ACCEPT_PAYMENT_INDIVIDUALLOAN_CASE,
-        Case.State.CLOSED);
-    checkNextActionsCorrect(product.getIdentifier(), customerCase.getIdentifier());
+        Case.State.ACTIVE); //Close has to be done explicitly.
+    checkNextActionsCorrect(product.getIdentifier(), customerCase.getIdentifier(), Action.APPLY_INTEREST,
+        Action.APPLY_INTEREST, Action.MARK_LATE, Action.ACCEPT_PAYMENT, Action.DISBURSE, Action.WRITE_OFF, Action.CLOSE);
 
     final Set<Debtor> debtors = new HashSet<>();
+    debtors.add(new Debtor(AccountingFixture.LOAN_INTEREST_ACCRUAL_ACCOUNT_IDENTIFIER, interestAccrued.toPlainString()));
+    debtors.add(new Debtor(AccountingFixture.LOANS_PAYABLE_ACCOUNT_IDENTIFIER, expectedCurrentBalance.subtract(interestAccrued).toPlainString()));
     debtors.add(new Debtor(customerLoanAccountIdentifier, expectedCurrentBalance.toPlainString()));
 
     final Set<Creditor> creditors = new HashSet<>();
+    creditors.add(new Creditor(AccountingFixture.CONSUMER_LOAN_INTEREST_ACCOUNT_IDENTIFIER, interestAccrued.toPlainString()));
+    creditors.add(new Creditor(AccountingFixture.LOAN_FUNDS_SOURCE_ACCOUNT_IDENTIFIER, expectedCurrentBalance.subtract(interestAccrued).toPlainString()));
     creditors.add(new Creditor(AccountingFixture.TELLER_ONE_ACCOUNT_IDENTIFIER, expectedCurrentBalance.toPlainString()));
+
     AccountingFixture.verifyTransfer(ledgerManager, debtors, creditors);
 
     expectedCurrentBalance = expectedCurrentBalance.subtract(expectedCurrentBalance);
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 293abf2..59f3c27 100644
--- a/component-test/src/main/java/io/mifos/portfolio/TestChargeDefinitions.java
+++ b/component-test/src/main/java/io/mifos/portfolio/TestChargeDefinitions.java
@@ -42,15 +42,20 @@
     final List<ChargeDefinition> charges = portfolioManager.getAllChargeDefinitionsForProduct(product.getIdentifier());
     final Set<String> chargeDefinitionIdentifiers = charges.stream().map(ChargeDefinition::getIdentifier).collect(Collectors.toSet());
     final Set<String> expectedChargeDefinitionIdentifiers = Stream.of(
-            ChargeIdentifiers.ALLOW_FOR_WRITE_OFF_ID,
-            ChargeIdentifiers.DISBURSEMENT_FEE_ID,
-            ChargeIdentifiers.INTEREST_ID,
-            ChargeIdentifiers.LATE_FEE_ID,
-            ChargeIdentifiers.LOAN_FUNDS_ALLOCATION_ID,
-            ChargeIdentifiers.LOAN_ORIGINATION_FEE_ID,
-            ChargeIdentifiers.PROCESSING_FEE_ID,
-            ChargeIdentifiers.RETURN_DISBURSEMENT_ID)
-            .collect(Collectors.toSet());
+        ChargeIdentifiers.ALLOW_FOR_WRITE_OFF_ID,
+        ChargeIdentifiers.DISBURSEMENT_FEE_ID,
+        ChargeIdentifiers.INTEREST_ID,
+        ChargeIdentifiers.LATE_FEE_ID,
+        ChargeIdentifiers.LOAN_FUNDS_ALLOCATION_ID,
+        ChargeIdentifiers.LOAN_ORIGINATION_FEE_ID,
+        ChargeIdentifiers.PROCESSING_FEE_ID,
+        ChargeIdentifiers.RETURN_DISBURSEMENT_ID,
+        ChargeIdentifiers.DISBURSE_PAYMENT_ID,
+        ChargeIdentifiers.TRACK_DISBURSAL_PAYMENT_ID,
+        ChargeIdentifiers.TRACK_RETURN_PRINCIPAL_ID,
+        ChargeIdentifiers.REPAYMENT_ID
+        )
+        .collect(Collectors.toSet());
     Assert.assertEquals(expectedChargeDefinitionIdentifiers, chargeDefinitionIdentifiers);
   }
 
diff --git a/component-test/src/main/java/io/mifos/portfolio/TestCommands.java b/component-test/src/main/java/io/mifos/portfolio/TestCommands.java
index c13daf4..e3520ad 100644
--- a/component-test/src/main/java/io/mifos/portfolio/TestCommands.java
+++ b/component-test/src/main/java/io/mifos/portfolio/TestCommands.java
@@ -18,19 +18,14 @@
 import io.mifos.accounting.api.v1.domain.AccountEntry;
 import io.mifos.core.lang.DateConverter;
 import io.mifos.individuallending.api.v1.domain.workflow.Action;
-import io.mifos.individuallending.api.v1.events.IndividualLoanCommandEvent;
-import io.mifos.portfolio.api.v1.domain.AccountAssignment;
 import io.mifos.portfolio.api.v1.domain.Case;
-import io.mifos.portfolio.api.v1.domain.Command;
 import io.mifos.portfolio.api.v1.domain.Product;
-import org.junit.Assert;
 import org.junit.Test;
 import org.mockito.Matchers;
 import org.mockito.Mockito;
 
 import java.time.LocalDateTime;
 import java.util.Collections;
-import java.util.List;
 import java.util.stream.Stream;
 
 import static io.mifos.individuallending.api.v1.events.IndividualLoanEventConstants.*;
@@ -81,7 +76,7 @@
     firstEntry.setTransactionDate(DateConverter.toIsoString(LocalDateTime.now()));
     Mockito.doAnswer((x) -> Stream.of(firstEntry))
         .when(ledgerManager)
-        .fetchAccountEntriesStream(Matchers.anyString(), Matchers.anyString(), Matchers.anyString());
+        .fetchAccountEntriesStream(Matchers.anyString(), Matchers.anyString(), Matchers.anyString(), Matchers.eq("ASC"));
 
 
     checkStateTransfer(
diff --git a/service/src/main/java/io/mifos/individuallending/IndividualLendingPatternFactory.java b/service/src/main/java/io/mifos/individuallending/IndividualLendingPatternFactory.java
index 6be1dae..eb7c4e0 100644
--- a/service/src/main/java/io/mifos/individuallending/IndividualLendingPatternFactory.java
+++ b/service/src/main/java/io/mifos/individuallending/IndividualLendingPatternFactory.java
@@ -79,6 +79,7 @@
     individualLendingRequiredAccounts.add(CUSTOMER_LOAN);
     individualLendingRequiredAccounts.add(PENDING_DISBURSAL);
     individualLendingRequiredAccounts.add(LOAN_FUNDS_SOURCE);
+    individualLendingRequiredAccounts.add(LOANS_PAYABLE);
     individualLendingRequiredAccounts.add(PROCESSING_FEE_INCOME);
     individualLendingRequiredAccounts.add(ORIGINATION_FEE_INCOME);
     individualLendingRequiredAccounts.add(DISBURSEMENT_FEE_INCOME);
@@ -126,6 +127,28 @@
             ENTRY,
             DISBURSEMENT_FEE_INCOME);
 
+    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(ENTRY);
+    disbursePayment.setToAccountDesignator(CUSTOMER_LOAN);
+    disbursePayment.setProportionalTo(ChargeIdentifiers.PRINCIPAL_ADJUSTMENT_DESIGNATOR);
+    disbursePayment.setChargeMethod(ChargeDefinition.ChargeMethod.PROPORTIONAL);
+    disbursePayment.setAmount(BigDecimal.ONE);
+
+    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(LOANS_PAYABLE);
+    trackPrincipalDisbursePayment.setProportionalTo(ChargeIdentifiers.PRINCIPAL_ADJUSTMENT_DESIGNATOR);
+    trackPrincipalDisbursePayment.setChargeMethod(ChargeDefinition.ChargeMethod.PROPORTIONAL);
+    trackPrincipalDisbursePayment.setAmount(BigDecimal.ONE);
+
     //TODO: Make payable at time of ACCEPT_PAYMENT but accrued at MARK_LATE
     final ChargeDefinition lateFee = charge(
             LATE_FEE_NAME,
@@ -135,7 +158,7 @@
             LATE_FEE_INCOME);
     lateFee.setAccrueAction(Action.MARK_LATE.name());
     lateFee.setAccrualAccountDesignator(LATE_FEE_ACCRUAL);
-    lateFee.setProportionalTo(ChargeIdentifiers.PAYMENT_ID);
+    lateFee.setProportionalTo(ChargeIdentifiers.REPAYMENT_ID);
 
     //TODO: Make multiple write off allowance charges.
     final ChargeDefinition writeOffAllowanceCharge = charge(
@@ -151,12 +174,34 @@
         Action.ACCEPT_PAYMENT,
         BigDecimal.valueOf(0.05),
         INTEREST_ACCRUAL,
-        PENDING_DISBURSAL);
+        INTEREST_INCOME);
     interestCharge.setForCycleSizeUnit(ChronoUnit.YEARS);
     interestCharge.setAccrueAction(Action.APPLY_INTEREST.name());
     interestCharge.setAccrualAccountDesignator(CUSTOMER_LOAN);
     interestCharge.setProportionalTo(ChargeIdentifiers.RUNNING_BALANCE_DESIGNATOR);
 
+    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(CUSTOMER_LOAN);
+    customerRepaymentCharge.setToAccountDesignator(ENTRY);
+    customerRepaymentCharge.setProportionalTo(ChargeIdentifiers.PRINCIPAL_ADJUSTMENT_DESIGNATOR);
+    customerRepaymentCharge.setChargeMethod(ChargeDefinition.ChargeMethod.PROPORTIONAL);
+    customerRepaymentCharge.setAmount(BigDecimal.ONE);
+
+    final ChargeDefinition trackPrincipalRepaymentCharge = new ChargeDefinition();
+    trackPrincipalRepaymentCharge.setChargeAction(Action.ACCEPT_PAYMENT.name());
+    trackPrincipalRepaymentCharge.setIdentifier(TRACK_RETURN_PRINCIPAL_ID);
+    trackPrincipalRepaymentCharge.setName(TRACK_RETURN_PRINCIPAL_NAME);
+    trackPrincipalRepaymentCharge.setDescription(TRACK_RETURN_PRINCIPAL_NAME);
+    trackPrincipalRepaymentCharge.setFromAccountDesignator(LOANS_PAYABLE);
+    trackPrincipalRepaymentCharge.setToAccountDesignator(LOAN_FUNDS_SOURCE);
+    trackPrincipalRepaymentCharge.setProportionalTo(ChargeIdentifiers.PRINCIPAL_ADJUSTMENT_DESIGNATOR);
+    trackPrincipalRepaymentCharge.setChargeMethod(ChargeDefinition.ChargeMethod.PROPORTIONAL);
+    trackPrincipalRepaymentCharge.setAmount(BigDecimal.ONE);
+
     final ChargeDefinition disbursementReturnCharge = charge(
             RETURN_DISBURSEMENT_NAME,
             Action.CLOSE,
@@ -169,9 +214,13 @@
     ret.add(loanOriginationFee);
     ret.add(loanFundsAllocation);
     ret.add(disbursementFee);
+    ret.add(disbursePayment);
+    ret.add(trackPrincipalDisbursePayment);
     ret.add(lateFee);
     ret.add(writeOffAllowanceCharge);
     ret.add(interestCharge);
+    ret.add(customerRepaymentCharge);
+    ret.add(trackPrincipalRepaymentCharge);
     ret.add(disbursementReturnCharge);
 
     return ret;
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 4e8fe11..41c3227 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
@@ -35,7 +35,9 @@
 import io.mifos.portfolio.api.v1.domain.CostComponent;
 import io.mifos.portfolio.api.v1.events.EventConstants;
 import io.mifos.portfolio.service.internal.mapper.CaseMapper;
-import io.mifos.portfolio.service.internal.repository.*;
+import io.mifos.portfolio.service.internal.repository.CaseEntity;
+import io.mifos.portfolio.service.internal.repository.CaseRepository;
+import io.mifos.portfolio.service.internal.repository.TaskInstanceRepository;
 import io.mifos.portfolio.service.internal.util.AccountingAdapter;
 import io.mifos.portfolio.service.internal.util.ChargeInstance;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -159,7 +161,8 @@
   public IndividualLoanCommandEvent process(final ApproveCommand command) {
     final String productIdentifier = command.getProductIdentifier();
     final String caseIdentifier = command.getCaseIdentifier();
-    final DataContextOfAction dataContextOfAction = costComponentService.checkedGetDataContext(productIdentifier, caseIdentifier, command.getCommand().getOneTimeAccountAssignments());
+    final DataContextOfAction dataContextOfAction = costComponentService.checkedGetDataContext(
+        productIdentifier, caseIdentifier, command.getCommand().getOneTimeAccountAssignments());
     IndividualLendingPatternFactory.checkActionCanBeExecuted(Case.State.valueOf(dataContextOfAction.getCustomerCase().getCurrentState()), Action.APPROVE);
 
     checkIfTasksAreOutstanding(dataContextOfAction, Action.APPROVE);
@@ -296,7 +299,7 @@
     final String productIdentifier = command.getProductIdentifier();
     final String caseIdentifier = command.getCaseIdentifier();
     final DataContextOfAction dataContextOfAction = costComponentService.checkedGetDataContext(
-        productIdentifier, caseIdentifier, null);
+        productIdentifier, caseIdentifier, command.getCommand().getOneTimeAccountAssignments());
     IndividualLendingPatternFactory.checkActionCanBeExecuted(Case.State.valueOf(dataContextOfAction.getCustomerCase().getCurrentState()), Action.ACCEPT_PAYMENT);
 
     checkIfTasksAreOutstanding(dataContextOfAction, Action.ACCEPT_PAYMENT);
@@ -312,7 +315,7 @@
         = getRequestedChargeAmounts(command.getCommand().getCostComponents());
 
     final BigDecimal sumOfAdjustments = costComponentsForRepaymentPeriod.stream()
-        .filter(entry -> entry.getKey().getIdentifier().equals(ChargeIdentifiers.PAYMENT_ID))
+        .filter(entry -> entry.getKey().getIdentifier().equals(ChargeIdentifiers.REPAYMENT_ID))
         .map(entry -> getChargeAmount(
             requestedChargeAmounts.get(entry.getKey().getIdentifier()),
             entry.getValue().getAmount()))
@@ -335,14 +338,6 @@
         dataContextOfAction.getMessageForCharge(Action.ACCEPT_PAYMENT),
         Action.ACCEPT_PAYMENT.getTransactionType());
 
-    final BigDecimal newBalance = costComponentsForRepaymentPeriod.getRunningBalance()
-        .add(sumOfAdjustments);
-    if (newBalance.compareTo(BigDecimal.ZERO) <= 0) {
-      final CaseEntity customerCase = dataContextOfAction.getCustomerCase();
-      //TODO: customerCase.setCurrentState(Case.State.CLOSED.name());
-      caseRepository.save(customerCase);
-    }
-
     return new IndividualLoanCommandEvent(command.getProductIdentifier(), command.getCaseIdentifier());
   }
 
@@ -352,7 +347,8 @@
   public IndividualLoanCommandEvent process(final WriteOffCommand command) {
     final String productIdentifier = command.getProductIdentifier();
     final String caseIdentifier = command.getCaseIdentifier();
-    final DataContextOfAction dataContextOfAction = costComponentService.checkedGetDataContext(productIdentifier, caseIdentifier, command.getCommand().getOneTimeAccountAssignments());
+    final DataContextOfAction dataContextOfAction = costComponentService.checkedGetDataContext(
+        productIdentifier, caseIdentifier, command.getCommand().getOneTimeAccountAssignments());
     IndividualLendingPatternFactory.checkActionCanBeExecuted(Case.State.valueOf(dataContextOfAction.getCustomerCase().getCurrentState()), Action.WRITE_OFF);
 
     checkIfTasksAreOutstanding(dataContextOfAction, Action.WRITE_OFF);
@@ -369,7 +365,8 @@
   public IndividualLoanCommandEvent process(final CloseCommand command) {
     final String productIdentifier = command.getProductIdentifier();
     final String caseIdentifier = command.getCaseIdentifier();
-    final DataContextOfAction dataContextOfAction = costComponentService.checkedGetDataContext(productIdentifier, caseIdentifier, command.getCommand().getOneTimeAccountAssignments());
+    final DataContextOfAction dataContextOfAction = costComponentService.checkedGetDataContext(
+        productIdentifier, caseIdentifier, command.getCommand().getOneTimeAccountAssignments());
     IndividualLendingPatternFactory.checkActionCanBeExecuted(Case.State.valueOf(dataContextOfAction.getCustomerCase().getCurrentState()), Action.CLOSE);
 
     checkIfTasksAreOutstanding(dataContextOfAction, Action.CLOSE);
@@ -386,7 +383,8 @@
   public IndividualLoanCommandEvent process(final RecoverCommand command) {
     final String productIdentifier = command.getProductIdentifier();
     final String caseIdentifier = command.getCaseIdentifier();
-    final DataContextOfAction dataContextOfAction = costComponentService.checkedGetDataContext(productIdentifier, caseIdentifier, command.getCommand().getOneTimeAccountAssignments());
+    final DataContextOfAction dataContextOfAction = costComponentService.checkedGetDataContext(
+        productIdentifier, caseIdentifier, command.getCommand().getOneTimeAccountAssignments());
     IndividualLendingPatternFactory.checkActionCanBeExecuted(Case.State.valueOf(dataContextOfAction.getCustomerCase().getCurrentState()), Action.RECOVER);
 
     checkIfTasksAreOutstanding(dataContextOfAction, Action.RECOVER);
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 573a405..439141a 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
@@ -40,6 +40,7 @@
 import java.time.ZoneId;
 import java.util.*;
 import java.util.function.BiFunction;
+import java.util.stream.Collectors;
 
 /**
  * @author Myrle Krantz
@@ -111,9 +112,9 @@
       case MARK_LATE:
         return getCostComponentsForMarkLate(dataContextOfAction);
       case WRITE_OFF:
-        return getCostComponentsForMarkLate(dataContextOfAction);
+        return getCostComponentsForWriteOff(dataContextOfAction);
       case RECOVER:
-        return getCostComponentsForMarkLate(dataContextOfAction);
+        return getCostComponentsForRecover(dataContextOfAction);
       default:
         throw ServiceException.internalError("Invalid action: ''{0}''.", action.name());
     }
@@ -125,13 +126,15 @@
     final int minorCurrencyUnitDigits = dataContextOfAction.getProduct().getMinorCurrencyUnitDigits();
     final List<ScheduledAction> scheduledActions = Collections.singletonList(new ScheduledAction(Action.OPEN, today()));
     final List<ScheduledCharge> scheduledCharges = individualLoanService.getScheduledCharges(
-        productIdentifier, minorCurrencyUnitDigits, BigDecimal.ZERO, scheduledActions);
+        productIdentifier, scheduledActions);
 
     return getCostComponentsForScheduledCharges(
-            scheduledCharges,
-            caseParameters.getMaximumBalance(),
-            BigDecimal.ZERO,
-            minorCurrencyUnitDigits);
+        Collections.emptyMap(),
+        scheduledCharges,
+        caseParameters.getMaximumBalance(),
+        BigDecimal.ZERO,
+        BigDecimal.ZERO,
+        minorCurrencyUnitDigits);
   }
 
   public CostComponentsForRepaymentPeriod getCostComponentsForDeny(final DataContextOfAction dataContextOfAction) {
@@ -140,12 +143,14 @@
     final int minorCurrencyUnitDigits = dataContextOfAction.getProduct().getMinorCurrencyUnitDigits();
     final List<ScheduledAction> scheduledActions = Collections.singletonList(new ScheduledAction(Action.DENY, today()));
     final List<ScheduledCharge> scheduledCharges = individualLoanService.getScheduledCharges(
-        productIdentifier, minorCurrencyUnitDigits, BigDecimal.ZERO, scheduledActions);
+        productIdentifier, scheduledActions);
 
     return getCostComponentsForScheduledCharges(
+        Collections.emptyMap(),
         scheduledCharges,
         caseParameters.getMaximumBalance(),
         BigDecimal.ZERO,
+        BigDecimal.ZERO,
         minorCurrencyUnitDigits);
   }
 
@@ -156,28 +161,57 @@
     final int minorCurrencyUnitDigits = dataContextOfAction.getProduct().getMinorCurrencyUnitDigits();
     final List<ScheduledAction> scheduledActions = Collections.singletonList(new ScheduledAction(Action.APPROVE, today()));
     final List<ScheduledCharge> scheduledCharges = individualLoanService.getScheduledCharges(
-        productIdentifier, minorCurrencyUnitDigits, BigDecimal.ZERO, scheduledActions);
+        productIdentifier, scheduledActions);
 
     return getCostComponentsForScheduledCharges(
-            scheduledCharges,
-            caseParameters.getMaximumBalance(),
-            BigDecimal.ZERO,
-            minorCurrencyUnitDigits);
+        Collections.emptyMap(),
+        scheduledCharges,
+        caseParameters.getMaximumBalance(),
+        BigDecimal.ZERO,
+        BigDecimal.ZERO,
+        minorCurrencyUnitDigits);
   }
 
   public CostComponentsForRepaymentPeriod getCostComponentsForDisburse(final DataContextOfAction dataContextOfAction) {
+    final DesignatorToAccountIdentifierMapper designatorToAccountIdentifierMapper
+        = new DesignatorToAccountIdentifierMapper(dataContextOfAction);
+    final String customerLoanAccountIdentifier = designatorToAccountIdentifierMapper.mapOrThrow(AccountDesignators.CUSTOMER_LOAN);
+    final BigDecimal currentBalance = accountingAdapter.getCurrentBalance(customerLoanAccountIdentifier);
+
+
+    final Optional<LocalDateTime> optionalStartOfTerm = accountingAdapter.getDateOfOldestEntryContainingMessage(
+        customerLoanAccountIdentifier,
+        dataContextOfAction.getMessageForCharge(Action.DISBURSE));
     final CaseParameters caseParameters = dataContextOfAction.getCaseParameters();
     final String productIdentifier = dataContextOfAction.getProduct().getIdentifier();
     final int minorCurrencyUnitDigits = dataContextOfAction.getProduct().getMinorCurrencyUnitDigits();
     final List<ScheduledAction> scheduledActions = Collections.singletonList(new ScheduledAction(Action.DISBURSE, today()));
     final List<ScheduledCharge> scheduledCharges = individualLoanService.getScheduledCharges(
-        productIdentifier, minorCurrencyUnitDigits, BigDecimal.ZERO, scheduledActions);
+        productIdentifier, scheduledActions);
+
+
+    final Map<Boolean, List<ScheduledCharge>> chargesSplitIntoScheduledAndAccrued = scheduledCharges.stream()
+        .collect(Collectors.partitioningBy(x -> isAccruedChargeForAction(x, Action.DISBURSE)));
+
+    final Map<ChargeDefinition, CostComponent> accruedCostComponents =
+        optionalStartOfTerm.map(startOfTerm ->
+        chargesSplitIntoScheduledAndAccrued.get(true)
+        .stream()
+        .map(ScheduledCharge::getChargeDefinition)
+        .collect(Collectors.toMap(chargeDefinition -> chargeDefinition,
+            chargeDefinition -> getAccruedCostComponentToApply(
+                dataContextOfAction,
+                designatorToAccountIdentifierMapper,
+                startOfTerm.toLocalDate(),
+                chargeDefinition)))).orElse(Collections.emptyMap());
 
     return getCostComponentsForScheduledCharges(
-            scheduledCharges,
-            caseParameters.getMaximumBalance(),
-            BigDecimal.ZERO,
-            minorCurrencyUnitDigits);
+        accruedCostComponents,
+        chargesSplitIntoScheduledAndAccrued.get(false),
+        caseParameters.getMaximumBalance(),
+        currentBalance,
+        BigDecimal.ZERO,//TODO: This needs to be provided by the user.
+        minorCurrencyUnitDigits);
   }
 
   public CostComponentsForRepaymentPeriod getCostComponentsForApplyInterest(
@@ -188,6 +222,8 @@
     final String customerLoanAccountIdentifier = designatorToAccountIdentifierMapper.mapOrThrow(AccountDesignators.CUSTOMER_LOAN);
     final BigDecimal currentBalance = accountingAdapter.getCurrentBalance(customerLoanAccountIdentifier);
 
+    final LocalDate startOfTerm = getStartOfTermOrThrow(dataContextOfAction, customerLoanAccountIdentifier);
+
     final CaseParameters caseParameters = dataContextOfAction.getCaseParameters();
     final String productIdentifier = dataContextOfAction.getProduct().getIdentifier();
     final int minorCurrencyUnitDigits = dataContextOfAction.getProduct().getMinorCurrencyUnitDigits();
@@ -196,14 +232,23 @@
 
     final List<ScheduledCharge> scheduledCharges = individualLoanService.getScheduledCharges(
         productIdentifier,
-        minorCurrencyUnitDigits,
-        currentBalance,
         Collections.singletonList(interestAction));
 
+    final Map<Boolean, List<ScheduledCharge>> chargesSplitIntoScheduledAndAccrued = scheduledCharges.stream()
+        .collect(Collectors.partitioningBy(x -> isAccruedChargeForAction(x, Action.APPLY_INTEREST)));
+
+    final Map<ChargeDefinition, CostComponent> accruedCostComponents = chargesSplitIntoScheduledAndAccrued.get(true)
+        .stream()
+        .map(ScheduledCharge::getChargeDefinition)
+        .collect(Collectors.toMap(chargeDefinition -> chargeDefinition,
+            chargeDefinition -> getAccruedCostComponentToApply(dataContextOfAction, designatorToAccountIdentifierMapper, startOfTerm, chargeDefinition)));
+
     return getCostComponentsForScheduledCharges(
-        scheduledCharges,
+        accruedCostComponents,
+        chargesSplitIntoScheduledAndAccrued.get(false),
         caseParameters.getMaximumBalance(),
         currentBalance,
+        BigDecimal.ZERO,
         minorCurrencyUnitDigits);
   }
 
@@ -215,44 +260,67 @@
     final String customerLoanAccountIdentifier = designatorToAccountIdentifierMapper.mapOrThrow(AccountDesignators.CUSTOMER_LOAN);
     final BigDecimal currentBalance = accountingAdapter.getCurrentBalance(customerLoanAccountIdentifier);
 
-    final String interestAccrualAccountIdentifier = designatorToAccountIdentifierMapper.mapOrThrow(AccountDesignators.INTEREST_ACCRUAL);
     final LocalDate startOfTerm = getStartOfTermOrThrow(dataContextOfAction, customerLoanAccountIdentifier);
-    final BigDecimal interestAccrued = accountingAdapter.sumMatchingEntriesSinceDate(
-        interestAccrualAccountIdentifier,
-        startOfTerm,
-        dataContextOfAction.getMessageForCharge(Action.APPLY_INTEREST));
-    final BigDecimal interestApplied = accountingAdapter.sumMatchingEntriesSinceDate(
-        interestAccrualAccountIdentifier,
-        startOfTerm,
-        dataContextOfAction.getMessageForCharge(Action.ACCEPT_PAYMENT));
-    final BigDecimal interestOutstanding = interestAccrued.subtract(interestApplied);
 
     final CaseParameters caseParameters = dataContextOfAction.getCaseParameters();
     final String productIdentifier = dataContextOfAction.getProduct().getIdentifier();
     final int minorCurrencyUnitDigits = dataContextOfAction.getProduct().getMinorCurrencyUnitDigits();
-    final List<ScheduledAction> scheduledActions
-        = ScheduledActionHelpers.getNextScheduledActionForDisbursedLoan(
-            startOfTerm,
-            dataContextOfAction.getCustomerCase().getEndOfTerm().toLocalDate(),
-            caseParameters,
-            Action.ACCEPT_PAYMENT
-        )
-        .map(Collections::singletonList)
-        .orElse(Collections.emptyList());
+    final ScheduledAction scheduledAction
+        = ScheduledActionHelpers.getNextScheduledPayment(
+        startOfTerm,
+        dataContextOfAction.getCustomerCase().getEndOfTerm().toLocalDate(),
+        caseParameters
+    );
 
     final List<ScheduledCharge> scheduledCharges = individualLoanService.getScheduledCharges(
         productIdentifier,
-        minorCurrencyUnitDigits,
-        currentBalance,
-        scheduledActions);
+        Collections.singletonList(scheduledAction));
+
+    final Map<Boolean, List<ScheduledCharge>> chargesSplitIntoScheduledAndAccrued = scheduledCharges.stream()
+        .collect(Collectors.partitioningBy(x -> isAccruedChargeForAction(x, Action.ACCEPT_PAYMENT)));
+
+    final Map<ChargeDefinition, CostComponent> accruedCostComponents = chargesSplitIntoScheduledAndAccrued.get(true)
+        .stream()
+        .map(ScheduledCharge::getChargeDefinition)
+        .collect(Collectors.toMap(chargeDefinition -> chargeDefinition,
+            chargeDefinition -> getAccruedCostComponentToApply(dataContextOfAction, designatorToAccountIdentifierMapper, startOfTerm, chargeDefinition)));
 
     return getCostComponentsForScheduledCharges(
-        scheduledCharges,
+        accruedCostComponents,
+        chargesSplitIntoScheduledAndAccrued.get(false),
         caseParameters.getMaximumBalance(),
         currentBalance,
+        BigDecimal.ZERO,//TODO: This needs to be provided by the user, or calculated.  ZERO is wrong.
         minorCurrencyUnitDigits);
   }
 
+  private static boolean isAccruedChargeForAction(final ScheduledCharge scheduledCharge, final Action action) {
+    return scheduledCharge.getChargeDefinition().getAccrueAction() != null &&
+        scheduledCharge.getChargeDefinition().getChargeAction().equals(action.name());
+  }
+
+  private CostComponent getAccruedCostComponentToApply(final DataContextOfAction dataContextOfAction,
+                                                       final DesignatorToAccountIdentifierMapper designatorToAccountIdentifierMapper,
+                                                       final LocalDate startOfTerm,
+                                                       final ChargeDefinition chargeDefinition) {
+    final CostComponent ret = new CostComponent();
+
+    final String accrualAccountIdentifier = designatorToAccountIdentifierMapper.mapOrThrow(chargeDefinition.getAccrualAccountDesignator());
+
+    final BigDecimal amountAccrued = accountingAdapter.sumMatchingEntriesSinceDate(
+        accrualAccountIdentifier,
+        startOfTerm,
+        dataContextOfAction.getMessageForCharge(Action.valueOf(chargeDefinition.getAccrueAction())));
+    final BigDecimal amountApplied = accountingAdapter.sumMatchingEntriesSinceDate(
+        accrualAccountIdentifier,
+        startOfTerm,
+        dataContextOfAction.getMessageForCharge(Action.valueOf(chargeDefinition.getChargeAction())));
+
+    ret.setChargeIdentifier(chargeDefinition.getIdentifier());
+    ret.setAmount(amountAccrued.subtract(amountApplied));
+    return ret;
+  }
+
   private LocalDate getStartOfTermOrThrow(final DataContextOfAction dataContextOfAction,
                                           final String customerLoanAccountIdentifier) {
     final Optional<LocalDateTime> firstDisbursalDateTime = accountingAdapter.getDateOfOldestEntryContainingMessage(
@@ -268,34 +336,42 @@
   private CostComponentsForRepaymentPeriod getCostComponentsForMarkLate(final DataContextOfAction dataContextOfAction) {
     return null;
   }
-  public CostComponentsForRepaymentPeriod getCostComponentsForWriteOff(final DataContextOfAction dataContextOfAction) {
+  private CostComponentsForRepaymentPeriod getCostComponentsForWriteOff(final DataContextOfAction dataContextOfAction) {
     return null;
   }
   private CostComponentsForRepaymentPeriod getCostComponentsForClose(final DataContextOfAction dataContextOfAction) {
     return null;
   }
-  public CostComponentsForRepaymentPeriod getCostComponentsForRecover(final DataContextOfAction dataContextOfAction) {
+  private CostComponentsForRepaymentPeriod getCostComponentsForRecover(final DataContextOfAction dataContextOfAction) {
     return null;
   }
 
   static CostComponentsForRepaymentPeriod getCostComponentsForScheduledCharges(
+      final Map<ChargeDefinition, CostComponent> accruedCostComponents,
       final Collection<ScheduledCharge> scheduledCharges,
       final BigDecimal maximumBalance,
       final BigDecimal runningBalance,
+      final BigDecimal loanPaymentSize,
       final int minorCurrencyUnitDigits) {
     BigDecimal balanceAdjustment = BigDecimal.ZERO;
 
     final Map<ChargeDefinition, CostComponent> costComponentMap = new HashMap<>();
-    for (final ScheduledCharge scheduledCharge : scheduledCharges)
+
+    for (Map.Entry<ChargeDefinition, CostComponent> entry : accruedCostComponents.entrySet()) {
+      costComponentMap.put(entry.getKey(), entry.getValue());
+
+      if (chargeDefinitionTouchesCustomerLoanAccount(entry.getKey()))
+        balanceAdjustment = balanceAdjustment.add(entry.getValue().getAmount());
+    }
+
+    final Map<Boolean, List<ScheduledCharge>> partitionedCharges = scheduledCharges.stream()
+        .collect(Collectors.partitioningBy(CostComponentService::proportionalToPrincipalAdjustment));
+
+    for (final ScheduledCharge scheduledCharge : partitionedCharges.get(false))
     {
       final CostComponent costComponent = costComponentMap
           .computeIfAbsent(scheduledCharge.getChargeDefinition(),
-              chargeIdentifier -> {
-                final CostComponent ret = new CostComponent();
-                ret.setChargeIdentifier(scheduledCharge.getChargeDefinition().getIdentifier());
-                ret.setAmount(BigDecimal.ZERO);
-                return ret;
-              });
+              chargeIdentifier -> constructEmptyCostComponent(scheduledCharge));
 
       final BigDecimal chargeAmount = howToApplyScheduledChargeToBalance(scheduledCharge)
           .apply(maximumBalance, runningBalance)
@@ -305,12 +381,46 @@
       costComponent.setAmount(costComponent.getAmount().add(chargeAmount));
     }
 
+    final BigDecimal principalAdjustment = loanPaymentSize.subtract(balanceAdjustment);
+    for (final ScheduledCharge scheduledCharge : partitionedCharges.get(true))
+    {
+      final CostComponent costComponent = costComponentMap
+          .computeIfAbsent(scheduledCharge.getChargeDefinition(),
+              chargeIdentifier -> constructEmptyCostComponent(scheduledCharge));
+
+      final BigDecimal chargeAmount = applyPrincipalAdjustmentCharge(scheduledCharge, principalAdjustment)
+          .setScale(minorCurrencyUnitDigits, BigDecimal.ROUND_HALF_EVEN);
+      if (chargeDefinitionTouchesCustomerLoanAccount(scheduledCharge.getChargeDefinition()))
+        balanceAdjustment = balanceAdjustment.add(chargeAmount);
+      costComponent.setAmount(costComponent.getAmount().add(chargeAmount));
+    }
+
     return new CostComponentsForRepaymentPeriod(
         runningBalance,
         costComponentMap,
         balanceAdjustment);
   }
 
+  private static BigDecimal applyPrincipalAdjustmentCharge(
+      final ScheduledCharge scheduledCharge,
+      final BigDecimal principalAdjustment) {
+    return scheduledCharge.getChargeDefinition().getAmount().multiply(principalAdjustment);
+  }
+
+  private static CostComponent constructEmptyCostComponent(ScheduledCharge scheduledCharge) {
+    final CostComponent ret = new CostComponent();
+    ret.setChargeIdentifier(scheduledCharge.getChargeDefinition().getIdentifier());
+    ret.setAmount(BigDecimal.ZERO);
+    return ret;
+  }
+
+  private static boolean proportionalToPrincipalAdjustment(final ScheduledCharge scheduledCharge) {
+    if (!scheduledCharge.getChargeDefinition().getChargeMethod().equals(ChargeDefinition.ChargeMethod.PROPORTIONAL))
+      return false;
+    final String proportionalTo = scheduledCharge.getChargeDefinition().getProportionalTo();
+    return proportionalTo != null && proportionalTo.equals(ChargeIdentifiers.PRINCIPAL_ADJUSTMENT_DESIGNATOR);
+  }
+
   private static BiFunction<BigDecimal, BigDecimal, BigDecimal> howToApplyScheduledChargeToBalance(
       final ScheduledCharge scheduledCharge)
   {
@@ -329,6 +439,8 @@
             return (maximumBalance, runningBalance) ->
                 PeriodChargeCalculator.chargeAmountPerPeriod(scheduledCharge, RUNNING_CALCULATION_PRECISION)
                     .multiply(maximumBalance);
+          case ChargeIdentifiers.PRINCIPAL_ADJUSTMENT_DESIGNATOR: //This is handled elsewhere.
+            throw new IllegalStateException("A principal adjustment charge should not be passed to the same application function as the other charges.");
           default:
 //TODO: correctly implement charges which are proportionate to other charges.
             return (maximumBalance, runningBalance) ->
diff --git a/service/src/main/java/io/mifos/individuallending/internal/service/IndividualLoanService.java b/service/src/main/java/io/mifos/individuallending/internal/service/IndividualLoanService.java
index 5dcf51a..b7a09e2 100644
--- a/service/src/main/java/io/mifos/individuallending/internal/service/IndividualLoanService.java
+++ b/service/src/main/java/io/mifos/individuallending/internal/service/IndividualLoanService.java
@@ -19,7 +19,6 @@
 import io.mifos.individuallending.api.v1.domain.caseinstance.ChargeName;
 import io.mifos.individuallending.api.v1.domain.caseinstance.PlannedPayment;
 import io.mifos.individuallending.api.v1.domain.caseinstance.PlannedPaymentPage;
-import io.mifos.individuallending.api.v1.domain.workflow.Action;
 import io.mifos.portfolio.api.v1.domain.ChargeDefinition;
 import io.mifos.portfolio.api.v1.domain.CostComponent;
 import io.mifos.portfolio.api.v1.domain.Product;
@@ -39,11 +38,7 @@
 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.PENDING_DISBURSAL;
-import static io.mifos.individuallending.api.v1.domain.product.ChargeIdentifiers.PAYMENT_ID;
-import static io.mifos.individuallending.api.v1.domain.product.ChargeIdentifiers.PAYMENT_NAME;
-import static io.mifos.portfolio.api.v1.domain.ChargeDefinition.ChargeMethod.FIXED;
+import static io.mifos.individuallending.api.v1.domain.product.ChargeIdentifiers.REPAYMENT_ID;
 
 /**
  * @author Myrle Krantz
@@ -76,9 +71,17 @@
 
     final List<ScheduledAction> scheduledActions = ScheduledActionHelpers.getHypotheticalScheduledActions(initialDisbursalDate, caseParameters);
 
-    final List<ScheduledCharge> scheduledCharges = getScheduledCharges(productIdentifier, minorCurrencyUnitDigits, caseParameters.getMaximumBalance(), scheduledActions);
+    final List<ScheduledCharge> scheduledCharges = getScheduledCharges(productIdentifier, scheduledActions);
 
-    final List<PlannedPayment> plannedPaymentsElements = getPlannedPaymentsElements(caseParameters.getMaximumBalance(), minorCurrencyUnitDigits, scheduledCharges);
+    final int precision = caseParameters.getMaximumBalance().precision() + minorCurrencyUnitDigits + EXTRA_PRECISION;
+    final Map<Period, BigDecimal> accrualRatesByPeriod
+        = periodChargeCalculator.getPeriodAccrualRates(scheduledCharges,
+        precision);
+
+    final BigDecimal geometricMeanAccrualRate = accrualRatesByPeriod.values().stream().collect(RateCollectors.geometricMean(precision));
+    final BigDecimal loanPaymentSize = loanPaymentInContextOfAccruedInterest(caseParameters.getMaximumBalance(), accrualRatesByPeriod.size(), geometricMeanAccrualRate);
+
+    final List<PlannedPayment> plannedPaymentsElements = getPlannedPaymentsElements(caseParameters.getMaximumBalance(), minorCurrencyUnitDigits, scheduledCharges, loanPaymentSize);
 
     final Set<ChargeName> chargeNames = scheduledCharges.stream()
             .map(IndividualLoanService::chargeNameFromChargeDefinition)
@@ -112,8 +115,6 @@
 
   List<ScheduledCharge> getScheduledCharges(
       final String productIdentifier,
-      final int minorCurrencyUnitDigits,
-      final BigDecimal initialBalance,
       final @Nonnull List<ScheduledAction> scheduledActions) {
     final Map<String, List<ChargeDefinition>> chargeDefinitionsMappedByChargeAction
             = chargeDefinitionService.getChargeDefinitionsMappedByChargeAction(productIdentifier);
@@ -121,36 +122,10 @@
     final Map<String, List<ChargeDefinition>> chargeDefinitionsMappedByAccrueAction
             = chargeDefinitionService.getChargeDefinitionsMappedByAccrueAction(productIdentifier);
 
-    final ChargeDefinition acceptPaymentDefinition = getPaymentChargeDefinition();
-
-    final List<ScheduledCharge> scheduledCharges = getScheduledCharges(
+    return getScheduledCharges(
             scheduledActions,
             chargeDefinitionsMappedByChargeAction,
-            chargeDefinitionsMappedByAccrueAction,
-            acceptPaymentDefinition);
-    int digitsInInitialBalance = initialBalance.precision();
-    final Map<Period, BigDecimal> accrualRatesByPeriod
-            = periodChargeCalculator.getPeriodAccrualRates(scheduledCharges,
-            digitsInInitialBalance + minorCurrencyUnitDigits + EXTRA_PRECISION);
-
-    if (accrualRatesByPeriod.size() != 0) {
-      final BigDecimal geometricMeanAccrualRate = accrualRatesByPeriod.values().stream().collect(RateCollectors.geometricMean(digitsInInitialBalance + minorCurrencyUnitDigits + EXTRA_PRECISION));
-      acceptPaymentDefinition.setAmount(loanPaymentInContextOfAccruedInterest(initialBalance, accrualRatesByPeriod.size(), geometricMeanAccrualRate));
-    }
-    else
-      acceptPaymentDefinition.setAmount(initialBalance);
-    return scheduledCharges;
-  }
-
-  private ChargeDefinition getPaymentChargeDefinition() {
-    final ChargeDefinition ret = new ChargeDefinition();
-    ret.setChargeAction(Action.ACCEPT_PAYMENT.name());
-    ret.setIdentifier(PAYMENT_ID);
-    ret.setName(PAYMENT_NAME);
-    ret.setFromAccountDesignator(CUSTOMER_LOAN);
-    ret.setToAccountDesignator(PENDING_DISBURSAL);
-    ret.setChargeMethod(FIXED);
-    return ret;
+            chargeDefinitionsMappedByAccrueAction);
   }
 
   private static class ScheduledChargeComparator implements Comparator<ScheduledCharge>
@@ -168,9 +143,10 @@
   }
 
   static private List<PlannedPayment> getPlannedPaymentsElements(
-          final BigDecimal initialBalance,
-          final int minorCurrencyUnitDigits,
-          final List<ScheduledCharge> scheduledCharges) {
+      final BigDecimal initialBalance,
+      final int minorCurrencyUnitDigits,
+      final List<ScheduledCharge> scheduledCharges,
+      final BigDecimal loanPaymentSize) {
     final Map<Period, SortedSet<ScheduledCharge>> orderedScheduledChargesGroupedByPeriod
             = scheduledCharges.stream()
             .collect(Collectors.groupingBy(scheduledCharge -> {
@@ -196,7 +172,13 @@
     {
       final SortedSet<ScheduledCharge> scheduledChargesInPeriod = orderedScheduledChargesGroupedByPeriod.get(repaymentPeriod);
       final CostComponentsForRepaymentPeriod costComponentsForRepaymentPeriod =
-              CostComponentService.getCostComponentsForScheduledCharges(scheduledChargesInPeriod, balance, balance, minorCurrencyUnitDigits);
+              CostComponentService.getCostComponentsForScheduledCharges(
+                  Collections.emptyMap(),
+                  scheduledChargesInPeriod,
+                  balance,
+                  balance,
+                  loanPaymentSize,
+                  minorCurrencyUnitDigits);
 
       final PlannedPayment plannedPayment = new PlannedPayment();
       plannedPayment.setCostComponents(new ArrayList<>(costComponentsForRepaymentPeriod.getCostComponents().values()));
@@ -209,7 +191,7 @@
     {
       final PlannedPayment lastPayment = plannedPayments.get(plannedPayments.size() - 1);
       final Optional<CostComponent> lastPaymentPayment = lastPayment.getCostComponents().stream()
-              .filter(x -> x.getChargeIdentifier().equals(PAYMENT_ID)).findAny();
+              .filter(x -> x.getChargeIdentifier().equals(REPAYMENT_ID)).findAny();
       lastPaymentPayment.ifPresent(x -> {
         x.setAmount(x.getAmount().subtract(lastPayment.getRemainingPrincipal()));
         lastPayment.setRemainingPrincipal(BigDecimal.ZERO.setScale(minorCurrencyUnitDigits, BigDecimal.ROUND_HALF_EVEN));
@@ -231,14 +213,12 @@
 
   private List<ScheduledCharge> getScheduledCharges(final List<ScheduledAction> scheduledActions,
                                                     final Map<String, List<ChargeDefinition>> chargeDefinitionsMappedByChargeAction,
-                                                    final Map<String, List<ChargeDefinition>> chargeDefinitionsMappedByAccrueAction,
-                                                    final ChargeDefinition acceptPaymentDefinition) {
+                                                    final Map<String, List<ChargeDefinition>> chargeDefinitionsMappedByAccrueAction) {
     return scheduledActions.stream()
         .flatMap(scheduledAction ->
             getChargeDefinitionStream(
                 chargeDefinitionsMappedByChargeAction,
                 chargeDefinitionsMappedByAccrueAction,
-                acceptPaymentDefinition,
                 scheduledAction)
                 .map(chargeDefinition -> new ScheduledCharge(scheduledAction, chargeDefinition)))
         .collect(Collectors.toList());
@@ -247,7 +227,6 @@
   private Stream<ChargeDefinition> getChargeDefinitionStream(
           final Map<String, List<ChargeDefinition>> chargeDefinitionsMappedByChargeAction,
           final Map<String, List<ChargeDefinition>> chargeDefinitionsMappedByAccrueAction,
-          final ChargeDefinition acceptPaymentDefinition,
           final ScheduledAction scheduledAction) {
     final List<ChargeDefinition> chargeMappingList = chargeDefinitionsMappedByChargeAction
         .get(scheduledAction.action.name());
@@ -255,18 +234,12 @@
     if (chargeMapping == null)
       chargeMapping = Stream.empty();
 
-    if (scheduledAction.action == Action.valueOf(acceptPaymentDefinition.getChargeAction()))
-      chargeMapping = Stream.concat(chargeMapping, Stream.of(acceptPaymentDefinition));
-
     final List<ChargeDefinition> accrueMappingList = chargeDefinitionsMappedByAccrueAction
         .get(scheduledAction.action.name());
     Stream<ChargeDefinition> accrueMapping = accrueMappingList == null ? Stream.empty() : accrueMappingList.stream();
     if (accrueMapping == null)
       accrueMapping = Stream.empty();
 
-    if ((acceptPaymentDefinition.getAccrueAction() != null) && (scheduledAction.action == Action.valueOf(acceptPaymentDefinition.getAccrueAction())))
-      accrueMapping = Stream.concat(chargeMapping, Stream.of(acceptPaymentDefinition));
-
 
     return Stream.concat(accrueMapping, chargeMapping);
   }
diff --git a/service/src/main/java/io/mifos/individuallending/internal/service/ScheduledAction.java b/service/src/main/java/io/mifos/individuallending/internal/service/ScheduledAction.java
index 1d85ecb..323e3f1 100644
--- a/service/src/main/java/io/mifos/individuallending/internal/service/ScheduledAction.java
+++ b/service/src/main/java/io/mifos/individuallending/internal/service/ScheduledAction.java
@@ -58,6 +58,10 @@
     this.repaymentPeriod = null;
   }
 
+  boolean actionIsOnOrAfter(final LocalDate date) {
+    return when.compareTo(date) > 0;
+  }
+
   @Override
   public boolean equals(Object o) {
     if (this == o) return true;
diff --git a/service/src/main/java/io/mifos/individuallending/internal/service/ScheduledActionHelpers.java b/service/src/main/java/io/mifos/individuallending/internal/service/ScheduledActionHelpers.java
index e310af1..434414b 100644
--- a/service/src/main/java/io/mifos/individuallending/internal/service/ScheduledActionHelpers.java
+++ b/service/src/main/java/io/mifos/individuallending/internal/service/ScheduledActionHelpers.java
@@ -20,10 +20,7 @@
 import io.mifos.portfolio.api.v1.domain.PaymentCycle;
 
 import javax.annotation.Nonnull;
-import java.time.DayOfWeek;
-import java.time.LocalDate;
-import java.time.YearMonth;
-import java.time.ZoneId;
+import java.time.*;
 import java.time.temporal.ChronoUnit;
 import java.util.*;
 import java.util.stream.Collectors;
@@ -57,19 +54,17 @@
         .collect(Collectors.toList());
   }
 
-  public static Optional<ScheduledAction> getNextScheduledActionForDisbursedLoan(final @Nonnull LocalDate startOfTerm,
-                                                                                 final @Nonnull LocalDate endOfTerm,
-                                                                                 final @Nonnull CaseParameters caseParameters,
-                                                                                 final @Nonnull Action action) {
-    if (preTermActions().anyMatch(x -> action == x))
-      throw new IllegalStateException("Should not be calling getNextScheduledActionsForDisbursedLoan with an action which occurs before disbursement.");
+  public static ScheduledAction getNextScheduledPayment(final @Nonnull LocalDate startOfTerm,
+                                                        final @Nonnull LocalDate endOfTerm,
+                                                        final @Nonnull CaseParameters caseParameters) {
+    final LocalDate now = LocalDate.now(Clock.systemUTC());
+    final LocalDate effectiveEndOfTerm = now.isAfter(endOfTerm) ? now : endOfTerm;
 
-    final LocalDate now = LocalDate.now(ZoneId.of("UTC"));
-    return getHypotheticalScheduledActionsForDisbursedLoan(startOfTerm, endOfTerm, caseParameters)
-        .filter(x -> x.action.equals(action))
-        .filter(x -> x.actionPeriod != null && x.actionPeriod.containsDate(now))
-        .sorted(Comparator.comparing(x -> x.actionPeriod))
-        .findFirst();
+    return getHypotheticalScheduledActionsForDisbursedLoan(startOfTerm, effectiveEndOfTerm, caseParameters)
+        .filter(x -> x.action.equals(Action.ACCEPT_PAYMENT))
+        .filter(x -> x.actionIsOnOrAfter(now))
+        .findFirst()
+        .orElseGet(() -> new ScheduledAction(Action.ACCEPT_PAYMENT, now));
   }
 
   private static Stream<ScheduledAction> getHypotheticalScheduledActionsForDisbursedLoan(
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 f4bff53..adffc64 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
@@ -78,7 +78,7 @@
     else if (identifier.equals(PROCESSING_FEE_ID))
       return MAXIMUM_BALANCE_DESIGNATOR;
     else if (identifier.equals(LATE_FEE_ID))
-      return PAYMENT_ID;
+      return REPAYMENT_ID;
     else
       return RUNNING_BALANCE_DESIGNATOR;
   }
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 d504b23..ad6a55d 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
@@ -44,6 +44,7 @@
  */
 @Component
 public class AccountingAdapter {
+
   public enum IdentifierType {LEDGER, ACCOUNT}
 
   private final LedgerManager ledgerManager;
@@ -87,16 +88,31 @@
     final LocalDateTime accountCreatedOn = DateConverter.fromIsoString(account.getCreatedOn());
     final DateRange fromAccountCreationUntilNow = oneSidedDateRange(accountCreatedOn.toLocalDate());
 
-    return ledgerManager.fetchAccountEntriesStream(accountIdentifier, fromAccountCreationUntilNow.toString(), message)
+    return ledgerManager.fetchAccountEntriesStream(accountIdentifier, fromAccountCreationUntilNow.toString(), message, "ASC")
         .findFirst()
         .map(AccountEntry::getTransactionDate)
         .map(DateConverter::fromIsoString);
   }
 
+  public List<LocalDateTime> getDatesOfMostRecentTwoEntriesContainingMessage(final String accountIdentifier,
+                                                                             final String message) {
+
+    final Account account = ledgerManager.findAccount(accountIdentifier);
+    final LocalDateTime accountCreatedOn = DateConverter.fromIsoString(account.getCreatedOn());
+    final DateRange fromAccountCreationUntilNow = oneSidedDateRange(accountCreatedOn.toLocalDate());
+
+    return ledgerManager.fetchAccountEntriesStream(accountIdentifier, fromAccountCreationUntilNow.toString(), message, "DESC")
+        .limit(2)
+        .map(AccountEntry::getTransactionDate)
+        .map(DateConverter::fromIsoString)
+        .collect(Collectors.toList());
+  }
+
   public BigDecimal sumMatchingEntriesSinceDate(final String accountIdentifier, final LocalDate startDate, final String message)
   {
     final DateRange fromLastPaymentUntilNow = oneSidedDateRange(startDate);
-    return ledgerManager.fetchAccountEntriesStream(accountIdentifier, fromLastPaymentUntilNow.toString(), message)
+    final Stream<AccountEntry> accountEntriesStream = ledgerManager.fetchAccountEntriesStream(accountIdentifier, fromLastPaymentUntilNow.toString(), message, "ASC");
+    return accountEntriesStream
         .map(AccountEntry::getAmount)
         .map(BigDecimal::valueOf).reduce(BigDecimal.ZERO, BigDecimal::add);
   }
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 f194269..f672c6d 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
@@ -87,7 +87,7 @@
     private CaseParameters caseParameters;
     private LocalDate initialDisbursementDate;
     private Map<String, List<ChargeDefinition>> chargeDefinitionsMappedByAction;
-    private Set<String> expectedChargeIdentifiers = new HashSet<>(Arrays.asList(ChargeIdentifiers.INTEREST_ID, ChargeIdentifiers.PAYMENT_ID));
+    private Set<String> expectedChargeIdentifiers = new HashSet<>(Arrays.asList(ChargeIdentifiers.INTEREST_ID, ChargeIdentifiers.REPAYMENT_ID));
     private Map<ActionDatePair, List<ChargeDefinition>> chargeDefinitionsForActions = new HashMap<>();
     //This is an abuse of the ChargeInstance since everywhere else it's intended to contain account identifiers and not
     //account designators.  Don't copy the code around charge instances in this test without thinking about what you're
@@ -165,17 +165,18 @@
     caseParameters.setPaymentCycle(new PaymentCycle(ChronoUnit.WEEKS, 1, 0, null, null));
 
     //I know: this is cheating in a unit test.  But I really didn't want to put this data together by hand.
-
-    final Map<String, List<ChargeDefinition>> chargeDefinitionsMappedByAction = new HashMap<>();
-    chargeDefinitionsMappedByAction.put(Action.APPLY_INTEREST.name(), getInterestChargeDefinition(0.01, ChronoUnit.YEARS));
+    final Map<String, List<ChargeDefinition>> chargeDefinitionsMappedByAction = constructCharges(0.01);
     final ChargeDefinition processingFeeCharge = getFixedSingleChargeDefinition(10.0, Action.OPEN, PROCESSING_FEE_ID, AccountDesignators.PROCESSING_FEE_INCOME);
     chargeDefinitionsMappedByAction.put(Action.OPEN.name(), Collections.singletonList(processingFeeCharge));
-    ChargeDefinition loanOriginationFeeCharge = getFixedSingleChargeDefinition(100.0, Action.APPROVE, LOAN_ORIGINATION_FEE_ID, AccountDesignators.ORIGINATION_FEE_INCOME);
-    ChargeDefinition loanFundsAllocationCharge = getProportionalSingleChargeDefinition(1.0, Action.APPROVE, LOAN_FUNDS_ALLOCATION_ID, AccountDesignators.LOAN_FUNDS_SOURCE, AccountDesignators.PENDING_DISBURSAL);
-    chargeDefinitionsMappedByAction.put(Action.APPROVE.name(),
-            Arrays.asList(
-                loanOriginationFeeCharge,
-                loanFundsAllocationCharge));
+    final ChargeDefinition loanOriginationFeeCharge = getFixedSingleChargeDefinition(100.0, Action.APPROVE, LOAN_ORIGINATION_FEE_ID, AccountDesignators.ORIGINATION_FEE_INCOME);
+    final List<ChargeDefinition> existingApprovalCharges = chargeDefinitionsMappedByAction.get(Action.APPROVE.name());
+    final List<ChargeDefinition> approvalChargesWithLoanOriginationFeeReplaced = existingApprovalCharges.stream().map(x -> {
+      if (x.getIdentifier().equals(LOAN_ORIGINATION_FEE_ID))
+        return loanOriginationFeeCharge;
+      else
+        return x;
+    }).collect(Collectors.toList());
+    chargeDefinitionsMappedByAction.put(Action.APPROVE.name(), approvalChargesWithLoanOriginationFeeReplaced);
 
     return new TestCase("simpleCase")
             .minorCurrencyUnitDigits(2)
@@ -187,7 +188,7 @@
             .expectAdditionalChargeIdentifier(LOAN_ORIGINATION_FEE_ID)
             .expectChargeInstancesForActionDatePair(Action.OPEN, initialDisbursementDate, Collections.singletonList(processingFeeCharge))
             .expectChargeInstancesForActionDatePair(Action.APPROVE, initialDisbursementDate,
-                    Arrays.asList(loanOriginationFeeCharge, loanFundsAllocationCharge));
+                Collections.singletonList(loanOriginationFeeCharge));
   }
 
   private static TestCase yearLoanTestCase()
@@ -199,8 +200,7 @@
     caseParameters.setPaymentCycle(new PaymentCycle(ChronoUnit.MONTHS, 1, 0, null, null));
     caseParameters.setMaximumBalance(BigDecimal.valueOf(200000));
 
-    final Map<String, List<ChargeDefinition>> chargeDefinitionsMappedByAction = new HashMap<>();
-    chargeDefinitionsMappedByAction.put(Action.APPLY_INTEREST.name(), getInterestChargeDefinition(0.10, ChronoUnit.YEARS));
+    final Map<String, List<ChargeDefinition>> chargeDefinitionsMappedByAction = constructCharges(0.10);
 
     return new TestCase("yearLoanTestCase")
             .minorCurrencyUnitDigits(3)
@@ -217,20 +217,25 @@
     caseParameters.setPaymentCycle(new PaymentCycle(ChronoUnit.WEEKS, 1, 1, 0, 0));
     caseParameters.setMaximumBalance(BigDecimal.valueOf(2000));
 
-    final List<ChargeDefinition> defaultLoanCharges = IndividualLendingPatternFactory.defaultIndividualLoanCharges();
-
-    final Map<String, List<ChargeDefinition>> chargeDefinitionsMappedByAction = defaultLoanCharges.stream()
-            .collect(Collectors.groupingBy(ChargeDefinition::getChargeAction,
-                    Collectors.mapping(x -> x, Collectors.toList())));
-
-    chargeDefinitionsMappedByAction.put(Action.APPLY_INTEREST.name(), getInterestChargeDefinition(0.05, ChronoUnit.YEARS));
+    final Map<String, List<ChargeDefinition>> chargeDefinitionsMappedByAction = constructCharges(0.05);
 
     return new TestCase("chargeDefaultsCase")
             .minorCurrencyUnitDigits(2)
             .caseParameters(caseParameters)
             .initialDisbursementDate(initialDisbursementDate)
             .chargeDefinitionsMappedByAction(chargeDefinitionsMappedByAction)
-            .expectedChargeIdentifiers(new HashSet<>(Arrays.asList(PROCESSING_FEE_ID, LOAN_FUNDS_ALLOCATION_ID, RETURN_DISBURSEMENT_ID, LOAN_ORIGINATION_FEE_ID, INTEREST_ID, DISBURSEMENT_FEE_ID, PAYMENT_ID)));
+            .expectedChargeIdentifiers(new HashSet<>(Arrays.asList(PROCESSING_FEE_ID, LOAN_FUNDS_ALLOCATION_ID, RETURN_DISBURSEMENT_ID, LOAN_ORIGINATION_FEE_ID, INTEREST_ID, DISBURSEMENT_FEE_ID, REPAYMENT_ID)));
+  }
+
+  private static Map<String, List<ChargeDefinition>> constructCharges(final double interestRate) {
+    final List<ChargeDefinition> defaultLoanCharges = IndividualLendingPatternFactory.defaultIndividualLoanCharges();
+
+    final Map<String, List<ChargeDefinition>> chargeDefinitionsMappedByAction = defaultLoanCharges.stream()
+            .collect(Collectors.groupingBy(ChargeDefinition::getChargeAction,
+                    Collectors.mapping(x -> x, Collectors.toList())));
+
+    chargeDefinitionsMappedByAction.put(Action.APPLY_INTEREST.name(), getInterestChargeDefinition(interestRate, ChronoUnit.YEARS));
+    return chargeDefinitionsMappedByAction;
   }
 
   private static List<ChargeDefinition> getInterestChargeDefinition(final double amount, final ChronoUnit forCycleSizeUnit) {
@@ -364,8 +369,6 @@
   public void getScheduledCharges() {
     final List<ScheduledAction> scheduledActions = ScheduledActionHelpers.getHypotheticalScheduledActions(testCase.initialDisbursementDate, testCase.caseParameters);
     final List<ScheduledCharge> scheduledCharges = testSubject.getScheduledCharges(testCase.productIdentifier,
-        testCase.minorCurrencyUnitDigits,
-        testCase.caseParameters.getMaximumBalance(),
         scheduledActions);
 
     final List<LocalDate> interestCalculationDates = scheduledCharges.stream()
@@ -382,12 +385,15 @@
 
     final List<LocalDate> acceptPaymentDates = scheduledCharges.stream()
         .filter(scheduledCharge -> scheduledCharge.getScheduledAction().action == Action.ACCEPT_PAYMENT)
-        .filter(scheduledCharge -> scheduledCharge.getChargeDefinition().getIdentifier().equals(ChargeIdentifiers.PAYMENT_ID))
         .map(scheduledCharge -> scheduledCharge.getScheduledAction().when)
         .collect(Collectors.toList());
     final long expectedAcceptPayments = scheduledActions.stream()
         .filter(x -> x.action == Action.ACCEPT_PAYMENT).count();
-    Assert.assertEquals("There should be no duplicate entries for payments", expectedAcceptPayments, acceptPaymentDates.size());
+    final List<ChargeDefinition> chargeDefinitionsMappedToAcceptPayment = testCase.chargeDefinitionsMappedByAction.get(Action.ACCEPT_PAYMENT.name());
+    final int numberOfChangeDefinitionsMappedToAcceptPayment = chargeDefinitionsMappedToAcceptPayment == null ? 0 : chargeDefinitionsMappedToAcceptPayment.size();
+    Assert.assertEquals("check for correct number of scheduled charges for accept payment",
+        expectedAcceptPayments*numberOfChangeDefinitionsMappedToAcceptPayment,
+        acceptPaymentDates.size());
 
     final Map<ActionDatePair, Set<ChargeDefinition>> searchableScheduledCharges = scheduledCharges.stream()
         .collect(
@@ -395,7 +401,7 @@
                 new ActionDatePair(scheduledCharge.getScheduledAction().action, scheduledCharge.getScheduledAction().when),
                 Collectors.mapping(ScheduledCharge::getChargeDefinition, Collectors.toSet())));
 
-    testCase.chargeDefinitionsForActions.forEach((key, value) -> Assert.assertEquals(new HashSet<>(value), searchableScheduledCharges.get(key)));
+    testCase.chargeDefinitionsForActions.forEach((key, value) -> value.forEach(x -> Assert.assertTrue(searchableScheduledCharges.get(key).contains(x))));
   }
 
   private double percentDifference(final BigDecimal maxPayment, final BigDecimal minPayment) {
@@ -406,7 +412,7 @@
 
   private Optional<BigDecimal> getCustomerRepayment(final PlannedPayment plannedPayment) {
     final Optional<CostComponent> ret = plannedPayment.getCostComponents().stream()
-            .filter(y -> y.getChargeIdentifier().equals(ChargeIdentifiers.PAYMENT_ID))
+            .filter(y -> y.getChargeIdentifier().equals(ChargeIdentifiers.REPAYMENT_ID))
             .findAny();
 
     return ret.map(x -> x.getAmount().abs());
diff --git a/service/src/test/java/io/mifos/individuallending/internal/service/ScheduledActionTest.java b/service/src/test/java/io/mifos/individuallending/internal/service/ScheduledActionTest.java
new file mode 100644
index 0000000..ff22aea
--- /dev/null
+++ b/service/src/test/java/io/mifos/individuallending/internal/service/ScheduledActionTest.java
@@ -0,0 +1,20 @@
+package io.mifos.individuallending.internal.service;
+
+import io.mifos.individuallending.api.v1.domain.workflow.Action;
+import org.junit.Assert;
+import org.junit.Test;
+
+import java.time.LocalDate;
+
+public class ScheduledActionTest {
+  @Test
+  public void actionIsOnOrBefore() {
+    final LocalDate today = LocalDate.now();
+    final LocalDate tomorrow = today.plusDays(1);
+    final LocalDate yesterday = today.minusDays(1);
+    final ScheduledAction testSubject = new ScheduledAction(Action.APPLY_INTEREST, today);
+    Assert.assertFalse(testSubject.actionIsOnOrAfter(today));
+    Assert.assertFalse(testSubject.actionIsOnOrAfter(tomorrow));
+    Assert.assertTrue(testSubject.actionIsOnOrAfter(yesterday));
+  }
+}
\ No newline at end of file