Begin implementation of late fee checking and marking.
* implemented command response.
* fixed late fee behavior in planned payments.
* created test for normal payment schedule.
* fixed last payment behavior in test.
diff --git a/api/src/main/java/io/mifos/individuallending/api/v1/domain/caseinstance/CaseParameters.java b/api/src/main/java/io/mifos/individuallending/api/v1/domain/caseinstance/CaseParameters.java
index 7be3e81..f69b387 100644
--- a/api/src/main/java/io/mifos/individuallending/api/v1/domain/caseinstance/CaseParameters.java
+++ b/api/src/main/java/io/mifos/individuallending/api/v1/domain/caseinstance/CaseParameters.java
@@ -50,9 +50,6 @@
   @Valid
   private PaymentCycle paymentCycle;
 
-  @Range(min = 0)
-  private BigDecimal paymentSize;
-
   public CaseParameters() {
   }
 
@@ -100,14 +97,6 @@
     this.paymentCycle = paymentCycle;
   }
 
-  public BigDecimal getPaymentSize() {
-    return paymentSize;
-  }
-
-  public void setPaymentSize(BigDecimal paymentSize) {
-    this.paymentSize = paymentSize;
-  }
-
   @Override
   public boolean equals(Object o) {
     if (this == o) return true;
@@ -117,13 +106,12 @@
         Objects.equals(creditWorthinessSnapshots, that.creditWorthinessSnapshots) &&
         Objects.equals(maximumBalance, that.maximumBalance) &&
         Objects.equals(termRange, that.termRange) &&
-        Objects.equals(paymentCycle, that.paymentCycle) &&
-        Objects.equals(paymentSize, that.paymentSize);
+        Objects.equals(paymentCycle, that.paymentCycle);
   }
 
   @Override
   public int hashCode() {
-    return Objects.hash(customerIdentifier, creditWorthinessSnapshots, maximumBalance, termRange, paymentCycle, paymentSize);
+    return Objects.hash(customerIdentifier, creditWorthinessSnapshots, maximumBalance, termRange, paymentCycle);
   }
 
   @Override
@@ -134,7 +122,6 @@
         ", maximumBalance=" + maximumBalance +
         ", termRange=" + termRange +
         ", paymentCycle=" + paymentCycle +
-        ", paymentSize=" + paymentSize +
         '}';
   }
 }
\ No newline at end of file
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 1adcc04..ffe36ac 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
@@ -344,6 +344,22 @@
       @RequestParam(value="forpaymentsize", required = false, defaultValue = "") final BigDecimal forPaymentSize);
 
 
+
+  @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,
+      @PathVariable("actionidentifier") final String actionIdentifier,
+      @RequestParam(value="touchingaccounts", required = false, defaultValue = "") final Set<String> forAccountDesignators,
+      @RequestParam(value="forpaymentsize", required = false, defaultValue = "") final BigDecimal forPaymentSize,
+      @RequestParam(value="fordatetime", required = false, defaultValue = "") final String forDateTime);
+
+
   @RequestMapping(
       value = "/products/{productidentifier}/cases/{caseidentifier}/actions/{actionidentifier}/costcomponents",
       method = RequestMethod.GET,
diff --git a/api/src/main/java/io/mifos/portfolio/api/v1/domain/Command.java b/api/src/main/java/io/mifos/portfolio/api/v1/domain/Command.java
index cc28b11..8f102a2 100644
--- a/api/src/main/java/io/mifos/portfolio/api/v1/domain/Command.java
+++ b/api/src/main/java/io/mifos/portfolio/api/v1/domain/Command.java
@@ -15,8 +15,11 @@
  */
 package io.mifos.portfolio.api.v1.domain;
 
+import io.mifos.core.lang.validation.constraints.ValidLocalDateTimeString;
+
 import javax.annotation.Nullable;
 import javax.validation.Valid;
+import javax.validation.constraints.NotNull;
 import java.math.BigDecimal;
 import java.util.List;
 import java.util.Objects;
@@ -34,7 +37,11 @@
   private BigDecimal paymentSize;
 
   private String note;
+
+  @ValidLocalDateTimeString
+  @NotNull
   private String createdOn;
+
   private String createdBy;
 
   public Command() {
diff --git a/api/src/test/java/io/mifos/portfolio/api/v1/domain/CommandTest.java b/api/src/test/java/io/mifos/portfolio/api/v1/domain/CommandTest.java
index 1372188..128f7a3 100644
--- a/api/src/test/java/io/mifos/portfolio/api/v1/domain/CommandTest.java
+++ b/api/src/test/java/io/mifos/portfolio/api/v1/domain/CommandTest.java
@@ -15,11 +15,14 @@
  */
 package io.mifos.portfolio.api.v1.domain;
 
+import io.mifos.core.lang.DateConverter;
 import io.mifos.core.test.domain.ValidationTest;
 import io.mifos.core.test.domain.ValidationTestCase;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
 
+import java.time.Clock;
+import java.time.LocalDateTime;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
@@ -39,6 +42,7 @@
   protected Command createValidTestSubject() {
     final Command ret = new Command();
     ret.setOneTimeAccountAssignments(Collections.emptyList());
+    ret.setCreatedOn(DateConverter.toIsoString(LocalDateTime.now(Clock.systemUTC())));
     return ret;
   }
 
diff --git a/component-test/src/main/java/io/mifos/portfolio/AbstractPortfolioTest.java b/component-test/src/main/java/io/mifos/portfolio/AbstractPortfolioTest.java
index 62de9e5..8ef3fb6 100644
--- a/component-test/src/main/java/io/mifos/portfolio/AbstractPortfolioTest.java
+++ b/component-test/src/main/java/io/mifos/portfolio/AbstractPortfolioTest.java
@@ -54,11 +54,13 @@
 import javax.validation.Validator;
 import javax.validation.ValidatorFactory;
 import java.math.BigDecimal;
+import java.time.Clock;
 import java.time.LocalDateTime;
 import java.time.temporal.ChronoUnit;
 import java.util.*;
 import java.util.function.Consumer;
 import java.util.stream.Collectors;
+import java.util.stream.Stream;
 
 /**
  * @author Myrle Krantz
@@ -195,6 +197,7 @@
         productIdentifier,
         caseIdentifier,
         action,
+        LocalDateTime.now(Clock.systemUTC()),
         oneTimeAccountAssignments,
         BigDecimal.ZERO,
         event,
@@ -205,6 +208,7 @@
   void checkStateTransfer(final String productIdentifier,
                           final String caseIdentifier,
                           final Action action,
+                          final LocalDateTime actionDateTime,
                           final List<AccountAssignment> oneTimeAccountAssignments,
                           final BigDecimal paymentSize,
                           final String event,
@@ -213,6 +217,7 @@
     final Command command = new Command();
     command.setOneTimeAccountAssignments(oneTimeAccountAssignments);
     command.setPaymentSize(paymentSize);
+    command.setCreatedOn(DateConverter.toIsoString(actionDateTime));
     portfolioManager.executeCaseCommand(productIdentifier, caseIdentifier, action.name(), command);
 
     Assert.assertTrue(eventRecorder.wait(event, new IndividualLoanCommandEvent(productIdentifier, caseIdentifier, DateConverter.toIsoString(eventDateTime))));
@@ -271,7 +276,9 @@
         amount
     );
     final Set<CostComponent> setOfCostComponents = new HashSet<>(costComponents);
-    final Set<CostComponent> setOfExpectedCostComponents = new HashSet<>(Arrays.asList(expectedCostComponents));
+    final Set<CostComponent> setOfExpectedCostComponents = Stream.of(expectedCostComponents)
+        .filter(x -> x.getAmount().compareTo(BigDecimal.ZERO) != 0)
+        .collect(Collectors.toSet());
     Assert.assertEquals(setOfExpectedCostComponents, setOfCostComponents);
   }
 
diff --git a/component-test/src/main/java/io/mifos/portfolio/AccountingFixture.java b/component-test/src/main/java/io/mifos/portfolio/AccountingFixture.java
index 595d2b4..fa38d9b 100644
--- a/component-test/src/main/java/io/mifos/portfolio/AccountingFixture.java
+++ b/component-test/src/main/java/io/mifos/portfolio/AccountingFixture.java
@@ -34,6 +34,8 @@
 import java.time.Clock;
 import java.time.LocalDateTime;
 import java.util.*;
+import java.util.stream.Collectors;
+import java.util.stream.IntStream;
 import java.util.stream.Stream;
 
 import static org.mockito.Matchers.argThat;
@@ -60,7 +62,7 @@
   static final String TELLER_ONE_ACCOUNT_IDENTIFIER = "7352";
   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 ="8530";
+  static final String LOANS_PAYABLE_ACCOUNT_IDENTIFIER ="8690";
   static final String LATE_FEE_INCOME_ACCOUNT_IDENTIFIER = "1311";
   static final String LATE_FEE_ACCRUAL_ACCOUNT_IDENTIFIER = "7840";
   static final String ARREARS_ALLOWANCE_ACCOUNT_IDENTIFIER = "3010";
@@ -79,13 +81,17 @@
       this.account.setBalance(balance);
     }
 
-    void addAccountEntry(final String message, final double amount) {
+    synchronized 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);
     }
+
+    synchronized List<AccountEntry> copyAccountEntries() {
+      return new ArrayList<>(accountEntries);
+    }
   }
 
   private static void makeAccountResponsive(final Account account, final LocalDateTime creationDate, final LedgerManager ledgerManagerMock) {
@@ -94,7 +100,7 @@
     accountMap.put(account.getIdentifier(), accountData);
     Mockito.doAnswer(new AccountEntriesStreamAnswer(accountData))
         .when(ledgerManagerMock)
-        .fetchAccountEntriesStream(Mockito.eq(account.getIdentifier()), Matchers.anyString(), Matchers.anyString(), Matchers.eq("ASC"));
+        .fetchAccountEntriesStream(Mockito.eq(account.getIdentifier()), Matchers.anyString(), Matchers.anyString(), AdditionalMatchers.or(Matchers.eq("DESC"), Matchers.eq("ASC")));
   }
 
 
@@ -425,10 +431,20 @@
     @Override
     public Stream<AccountEntry> answer(final InvocationOnMock invocation) throws Throwable {
       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();
+      final String direction = invocation.getArgumentAt(3, String.class);
+      final boolean asc = direction == null || direction.equals("ASC");
+      final List<AccountEntry> accountEntries = accountData.copyAccountEntries();
+      final int entryCount = accountEntries.size();
+      final Stream<AccountEntry> orderedCorrectly = asc ?
+          IntStream.rangeClosed(1, entryCount).mapToObj(i -> accountEntries.get(entryCount - i)) :
+          accountEntries.stream();
+
+      if (message != null) {
+        return orderedCorrectly.filter(x -> x.getMessage().equals(message));
+      }
+      else {
+        return orderedCorrectly;
+      }
     }
   }
 
@@ -494,8 +510,10 @@
                              final String productIdentifier,
                              final String caseIdentifier,
                              final Action action) {
-    final JournalEntryMatcher specifiesCorrectJournalEntry = new JournalEntryMatcher(debtors, creditors, productIdentifier, caseIdentifier, action);
-    Mockito.verify(ledgerManager).createJournalEntry(AdditionalMatchers.and(argThat(isValid()), argThat(specifiesCorrectJournalEntry)));
+    final Set<Debtor> filteredDebtors = debtors.stream().filter(x -> BigDecimal.valueOf(Double.valueOf(x.getAmount())).compareTo(BigDecimal.ZERO) != 0).collect(Collectors.toSet());
+    final Set<Creditor> filteredCreditors = creditors.stream().filter(x -> BigDecimal.valueOf(Double.valueOf(x.getAmount())).compareTo(BigDecimal.ZERO) != 0).collect(Collectors.toSet());
+    final JournalEntryMatcher specifiesCorrectJournalEntry = new JournalEntryMatcher(filteredDebtors, filteredCreditors, productIdentifier, caseIdentifier, action);
+    Mockito.verify(ledgerManager, Mockito.atLeastOnce()).createJournalEntry(AdditionalMatchers.and(argThat(isValid()), argThat(specifiesCorrectJournalEntry)));
 
   }
 }
\ No newline at end of file
diff --git a/component-test/src/main/java/io/mifos/portfolio/Fixture.java b/component-test/src/main/java/io/mifos/portfolio/Fixture.java
index 69e1c0b..de8fc57 100644
--- a/component-test/src/main/java/io/mifos/portfolio/Fixture.java
+++ b/component-test/src/main/java/io/mifos/portfolio/Fixture.java
@@ -139,9 +139,8 @@
 
     ret.setCustomerIdentifier(CUSTOMER_IDENTIFIER);
     ret.setMaximumBalance(fixScale(BigDecimal.valueOf(2000L)));
-    ret.setTermRange(new TermRange(ChronoUnit.MONTHS, 18));
+    ret.setTermRange(new TermRange(ChronoUnit.MONTHS, 3));
     ret.setPaymentCycle(new PaymentCycle(ChronoUnit.MONTHS, 1, 1, null, null));
-    ret.setPaymentSize(BigDecimal.ONE.negate().setScale(4, RoundingMode.HALF_EVEN));
 
     final CreditWorthinessSnapshot customerCreditWorthinessSnapshot = new CreditWorthinessSnapshot();
     customerCreditWorthinessSnapshot.setForCustomer("alice");
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 b34a0ed..d2c78d2 100644
--- a/component-test/src/main/java/io/mifos/portfolio/TestAccountingInteractionInLoanWorkflow.java
+++ b/component-test/src/main/java/io/mifos/portfolio/TestAccountingInteractionInLoanWorkflow.java
@@ -41,10 +41,11 @@
 
 import java.math.BigDecimal;
 import java.math.RoundingMode;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.Set;
+import java.time.Clock;
+import java.time.LocalDateTime;
+import java.time.temporal.ChronoUnit;
+import java.util.*;
+import java.util.stream.IntStream;
 
 import static io.mifos.portfolio.Fixture.MINOR_CURRENCY_UNIT_DIGITS;
 
@@ -89,6 +90,8 @@
 
   @Test
   public void workflowTerminatingInEarlyLoanPayoff() throws InterruptedException {
+    final LocalDateTime today = midnightToday();
+
     step1CreateProduct();
     step2CreateCase();
     step3OpenCase();
@@ -96,13 +99,15 @@
     step5Disburse(
         BigDecimal.valueOf(2_000_00, MINOR_CURRENCY_UNIT_DIGITS),
         UPPER_RANGE_DISBURSEMENT_FEE_ID, BigDecimal.valueOf(20_00, MINOR_CURRENCY_UNIT_DIGITS));
-    step6CalculateInterestAccrual();
-    step7PaybackPartialAmount(expectedCurrentBalance);
+    step6CalculateInterestAccrualAndCheckForLateness(midnightToday(), null);
+    step7PaybackPartialAmount(expectedCurrentBalance, today, 0);
     step8Close();
   }
 
   @Test
   public void workflowWithTwoUnequalDisbursals() throws InterruptedException {
+    final LocalDateTime today = midnightToday();
+
     step1CreateProduct();
     step2CreateCase();
     step3OpenCase();
@@ -113,13 +118,15 @@
     step5Disburse(
         BigDecimal.valueOf(1_500_00, MINOR_CURRENCY_UNIT_DIGITS),
         UPPER_RANGE_DISBURSEMENT_FEE_ID, BigDecimal.valueOf(15_00, MINOR_CURRENCY_UNIT_DIGITS));
-    step6CalculateInterestAccrual();
-    step7PaybackPartialAmount(expectedCurrentBalance);
+    step6CalculateInterestAccrualAndCheckForLateness(midnightToday(), null);
+    step7PaybackPartialAmount(expectedCurrentBalance, today, 0);
     step8Close();
   }
 
   @Test
   public void workflowWithTwoNearlyEqualRepayments() throws InterruptedException {
+    final LocalDateTime today = midnightToday();
+
     step1CreateProduct();
     step2CreateCase();
     step3OpenCase();
@@ -127,10 +134,13 @@
     step5Disburse(
         BigDecimal.valueOf(2_000_00, MINOR_CURRENCY_UNIT_DIGITS),
         UPPER_RANGE_DISBURSEMENT_FEE_ID, BigDecimal.valueOf(20_00, MINOR_CURRENCY_UNIT_DIGITS));
-    step6CalculateInterestAccrual();
+    step6CalculateInterestAccrualAndCheckForLateness(midnightToday(), null);
     final BigDecimal repayment1 = expectedCurrentBalance.divide(BigDecimal.valueOf(2), BigDecimal.ROUND_HALF_EVEN);
-    step7PaybackPartialAmount(repayment1.setScale(MINOR_CURRENCY_UNIT_DIGITS, BigDecimal.ROUND_HALF_EVEN));
-    step7PaybackPartialAmount(expectedCurrentBalance);
+    step7PaybackPartialAmount(
+        repayment1.setScale(MINOR_CURRENCY_UNIT_DIGITS, BigDecimal.ROUND_HALF_EVEN),
+        today,
+        0);
+    step7PaybackPartialAmount(expectedCurrentBalance, today, 0);
     step8Close();
   }
 
@@ -148,6 +158,56 @@
     catch (IllegalArgumentException ignored) { }
   }
 
+  @Test
+  public void workflowWithNormalRepayment() throws InterruptedException {
+    final LocalDateTime today = midnightToday();
+
+    step1CreateProduct();
+    step2CreateCase();
+    step3OpenCase();
+    step4ApproveCase();
+    step5Disburse(
+        BigDecimal.valueOf(2_000_00, MINOR_CURRENCY_UNIT_DIGITS),
+        UPPER_RANGE_DISBURSEMENT_FEE_ID, BigDecimal.valueOf(20_00, MINOR_CURRENCY_UNIT_DIGITS));
+
+    int week = 0;
+    final List<BigDecimal> repayments = new ArrayList<>();
+    while (expectedCurrentBalance.compareTo(BigDecimal.ZERO) > 0) {
+      logger.info("Simulating week {}. Expected current balance {}.", week, expectedCurrentBalance);
+      step6CalculateInterestAndCheckForLatenessForWeek(today, week, null);
+      final BigDecimal nextRepaymentAmount = findNextRepaymentAmount(today, (week+1)*7);
+      repayments.add(nextRepaymentAmount);
+      step7PaybackPartialAmount(nextRepaymentAmount, today, (week+1)*7);
+      week++;
+    }
+
+    final BigDecimal minPayment = repayments.stream().min(BigDecimal::compareTo).orElseThrow(IllegalStateException::new);
+    final BigDecimal maxPayment = repayments.stream().max(BigDecimal::compareTo).orElseThrow(IllegalStateException::new);
+    final BigDecimal delta = maxPayment.subtract(minPayment).abs();
+    Assert.assertTrue("minPayment is " + minPayment + ", maxPayment is " + maxPayment,
+        delta.divide(maxPayment, BigDecimal.ROUND_HALF_EVEN).compareTo(BigDecimal.valueOf(0.01)) <= 0);
+
+
+    step8Close();
+  }
+
+  private BigDecimal findNextRepaymentAmount(
+      final LocalDateTime referenceDate,
+      final int dayNumber) {
+    AccountingFixture.mockBalance(customerLoanAccountIdentifier, expectedCurrentBalance.negate());
+
+    final List<CostComponent> costComponentsForNextPayment = portfolioManager.getCostComponentsForAction(
+        product.getIdentifier(),
+        customerCase.getIdentifier(),
+        Action.ACCEPT_PAYMENT.name(),
+        null,
+        null,
+        DateConverter.toIsoString(referenceDate.plusDays(dayNumber)));
+    return costComponentsForNextPayment.stream().filter(x -> x.getChargeIdentifier().equals(ChargeIdentifiers.REPAYMENT_ID)).findFirst()
+        .orElseThrow(() -> new IllegalArgumentException("return missing repayment charge."))
+        .getAmount();
+  }
+
   //Create product and set charges to fixed fees.
   private void step1CreateProduct() throws InterruptedException {
     logger.info("step1CreateProduct");
@@ -204,8 +264,9 @@
 
   private void step2CreateCase() throws InterruptedException {
     logger.info("step2CreateCase");
-    caseParameters = Fixture.createAdjustedCaseParameters(x -> {
-    });
+    caseParameters = Fixture.createAdjustedCaseParameters(x ->
+      x.setPaymentCycle(new PaymentCycle(ChronoUnit.WEEKS, 1, null, null, null))
+    );
     final String caseParametersAsString = new Gson().toJson(caseParameters);
     customerCase = createAdjustedCase(product.getIdentifier(), x -> x.setParameters(caseParametersAsString));
 
@@ -314,6 +375,7 @@
         product.getIdentifier(),
         customerCase.getIdentifier(),
         Action.DISBURSE,
+        LocalDateTime.now(Clock.systemUTC()),
         Collections.singletonList(assignEntryToTeller()),
         amount,
         IndividualLoanEventConstants.DISBURSE_INDIVIDUALLOAN_CASE,
@@ -337,17 +399,44 @@
     expectedCurrentBalance = expectedCurrentBalance.add(amount);
   }
 
+  private void step6CalculateInterestAndCheckForLatenessForWeek(
+      final LocalDateTime referenceDate,
+      final int weekNumber,
+      final BigDecimal calculatedLateFee) throws InterruptedException {
+    try {
+      IntStream.rangeClosed((weekNumber*7)+1, (weekNumber+1)*7)
+          .mapToObj(referenceDate::plusDays)
+          .forEach(day -> {
+            try {
+              step6CalculateInterestAccrualAndCheckForLateness(day, calculatedLateFee);
+            } catch (InterruptedException e) {
+              throw new RuntimeException(e);
+            }
+          });
+    }
+    catch (RuntimeException e) {
+      final Throwable cause = e.getCause();
+      if (cause != null && cause.getClass().isAssignableFrom(InterruptedException.class))
+        throw (InterruptedException)e.getCause();
+      else
+        throw e;
+    }
+  }
+
   //Perform daily interest calculation.
-  private void step6CalculateInterestAccrual() throws InterruptedException {
-    logger.info("step6CalculateInterestAccrual");
+  private void step6CalculateInterestAccrualAndCheckForLateness(
+      final LocalDateTime forTime,
+      final BigDecimal calculatedLateFee) throws InterruptedException {
+    logger.info("step6CalculateInterestAccrualAndCheckForLateness");
     final String beatIdentifier = "alignment0";
-    final String midnightTimeStamp = DateConverter.toIsoString(midnightToday());
+    final String midnightTimeStamp = DateConverter.toIsoString(forTime);
 
     AccountingFixture.mockBalance(customerLoanAccountIdentifier, expectedCurrentBalance.negate());
 
     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);
 
+
     checkCostComponentForActionCorrect(
         product.getIdentifier(),
         customerCase.getIdentifier(),
@@ -355,11 +444,24 @@
         Collections.singleton(AccountDesignators.CUSTOMER_LOAN),
         null,
         new CostComponent(ChargeIdentifiers.INTEREST_ID, calculatedInterest));
+
+    if (calculatedLateFee != null) {
+      checkCostComponentForActionCorrect(
+          product.getIdentifier(),
+          customerCase.getIdentifier(),
+          Action.MARK_LATE,
+          Collections.singleton(AccountDesignators.CUSTOMER_LOAN),
+          null,
+          new CostComponent(ChargeIdentifiers.LATE_FEE_ID, calculatedLateFee));
+    }
     final BeatPublish interestBeat = new BeatPublish(beatIdentifier, midnightTimeStamp);
     portfolioBeatListener.publishBeat(interestBeat);
     Assert.assertTrue(this.eventRecorder.wait(io.mifos.rhythm.spi.v1.events.EventConstants.POST_PUBLISHEDBEAT,
         new BeatPublishEvent(EventConstants.DESTINATION, beatIdentifier, midnightTimeStamp)));
 
+    Assert.assertTrue(this.eventRecorder.wait(IndividualLoanEventConstants.CHECK_LATE_INDIVIDUALLOAN_CASE,
+        new IndividualLoanCommandEvent(product.getIdentifier(), customerCase.getIdentifier(), midnightTimeStamp)));
+
     Assert.assertTrue(eventRecorder.wait(IndividualLoanEventConstants.APPLY_INTEREST_INDIVIDUALLOAN_CASE,
         new IndividualLoanCommandEvent(product.getIdentifier(), customerCase.getIdentifier(), midnightTimeStamp)));
 
@@ -384,7 +486,10 @@
     expectedCurrentBalance = expectedCurrentBalance.add(calculatedInterest);
   }
 
-  private void step7PaybackPartialAmount(final BigDecimal amount) throws InterruptedException {
+  private void step7PaybackPartialAmount(
+      final BigDecimal amount,
+      final LocalDateTime referenceDate,
+      final int dayNumber) throws InterruptedException {
     logger.info("step7PaybackPartialAmount '{}'", amount);
 
     AccountingFixture.mockBalance(customerLoanAccountIdentifier, expectedCurrentBalance.negate());
@@ -404,6 +509,7 @@
         product.getIdentifier(),
         customerCase.getIdentifier(),
         Action.ACCEPT_PAYMENT,
+        referenceDate.plusDays(dayNumber),
         Collections.singletonList(assignEntryToTeller()),
         amount,
         IndividualLoanEventConstants.ACCEPT_PAYMENT_INDIVIDUALLOAN_CASE,
diff --git a/component-test/src/main/java/io/mifos/portfolio/listener/IndividualLoanCaseCommandEventListener.java b/component-test/src/main/java/io/mifos/portfolio/listener/IndividualLoanCaseCommandEventListener.java
index 1e4d4d3..e198db2 100644
--- a/component-test/src/main/java/io/mifos/portfolio/listener/IndividualLoanCaseCommandEventListener.java
+++ b/component-test/src/main/java/io/mifos/portfolio/listener/IndividualLoanCaseCommandEventListener.java
@@ -99,9 +99,19 @@
   }
 
   @JmsListener(
-          subscription = IndividualLoanEventConstants.DESTINATION,
-          destination = IndividualLoanEventConstants.DESTINATION,
-          selector = IndividualLoanEventConstants.SELECTOR_MARK_LATE_INDIVIDUALLOAN_CASE
+      subscription = IndividualLoanEventConstants.DESTINATION,
+      destination = IndividualLoanEventConstants.DESTINATION,
+      selector = IndividualLoanEventConstants.SELECTOR_CHECK_LATE_INDIVIDUALLOAN_CASE
+  )
+  public void onCheckLate(@Header(TenantHeaderFilter.TENANT_HEADER) final String tenant,
+                          final String payload) {
+    this.eventRecorder.event(tenant, IndividualLoanEventConstants.CHECK_LATE_INDIVIDUALLOAN_CASE, payload, IndividualLoanCommandEvent.class);
+  }
+
+  @JmsListener(
+      subscription = IndividualLoanEventConstants.DESTINATION,
+      destination = IndividualLoanEventConstants.DESTINATION,
+      selector = IndividualLoanEventConstants.SELECTOR_MARK_LATE_INDIVIDUALLOAN_CASE
   )
   public void onMarkLate(@Header(TenantHeaderFilter.TENANT_HEADER) final String tenant,
                          final String payload) {
diff --git a/service/src/main/java/io/mifos/individuallending/IndividualLendingPatternFactory.java b/service/src/main/java/io/mifos/individuallending/IndividualLendingPatternFactory.java
index cb83144..8fd47db 100644
--- a/service/src/main/java/io/mifos/individuallending/IndividualLendingPatternFactory.java
+++ b/service/src/main/java/io/mifos/individuallending/IndividualLendingPatternFactory.java
@@ -20,7 +20,6 @@
 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.ChargeIdentifiers;
 import io.mifos.individuallending.api.v1.domain.product.ChargeProportionalDesignator;
 import io.mifos.individuallending.api.v1.domain.workflow.Action;
 import io.mifos.individuallending.internal.mapper.CaseParametersMapper;
@@ -44,9 +43,11 @@
 import org.springframework.transaction.annotation.Transactional;
 
 import java.math.BigDecimal;
+import java.time.LocalDateTime;
 import java.time.temporal.ChronoUnit;
 import java.util.*;
 import java.util.stream.Collectors;
+import java.util.stream.Stream;
 
 import static io.mifos.individuallending.api.v1.domain.product.AccountDesignators.*;
 import static io.mifos.individuallending.api.v1.domain.product.ChargeIdentifiers.*;
@@ -168,13 +169,13 @@
     //TODO: Make payable at time of ACCEPT_PAYMENT but accrued at MARK_LATE
     final ChargeDefinition lateFee = charge(
             LATE_FEE_NAME,
-            Action.MARK_LATE,
-            BigDecimal.ONE,
+            Action.ACCEPT_PAYMENT,
+            BigDecimal.TEN,
             CUSTOMER_LOAN,
             LATE_FEE_INCOME);
     lateFee.setAccrueAction(Action.MARK_LATE.name());
     lateFee.setAccrualAccountDesignator(LATE_FEE_ACCRUAL);
-    lateFee.setProportionalTo(ChargeIdentifiers.REPAYMENT_ID);
+    lateFee.setProportionalTo(ChargeProportionalDesignator.REPAYMENT_DESIGNATOR.getValue());
     lateFee.setReadOnly(false);
 
     //TODO: Make multiple write off allowance charges.
@@ -347,6 +348,7 @@
       final String productIdentifier,
       final String caseIdentifier,
       final String actionIdentifier,
+      final LocalDateTime forDateTime,
       final Set<String> forAccountDesignators,
       final BigDecimal forPaymentSize) {
     final Action action = Action.valueOf(actionIdentifier);
@@ -354,14 +356,21 @@
     final Case.State caseState = Case.State.valueOf(dataContextOfAction.getCustomerCaseEntity().getCurrentState());
     checkActionCanBeExecuted(caseState, action);
 
-    return costComponentService.getCostComponentsForAction(action, dataContextOfAction, forPaymentSize)
-        .stream()
-        .filter(costComponentEntry -> chargeReferencesAccountDesignators(costComponentEntry.getKey(), action, forAccountDesignators))
+    Stream<Map.Entry<ChargeDefinition, CostComponent>> costComponentStream = costComponentService.getCostComponentsForAction(
+        action,
+        dataContextOfAction,
+        forPaymentSize,
+        forDateTime.toLocalDate())
+        .stream();
+
+    if (!forAccountDesignators.isEmpty()) {
+      costComponentStream = costComponentStream
+          .filter(costComponentEntry -> chargeReferencesAccountDesignators(costComponentEntry.getKey(), action, forAccountDesignators));
+    }
+
+    return costComponentStream
         .map(costComponentEntry -> new CostComponent(costComponentEntry.getKey().getIdentifier(), costComponentEntry.getValue().getAmount()))
-        .collect(Collectors.toList())
-            .stream()
-            .map(x -> new CostComponent(x.getChargeIdentifier(), x.getAmount()))
-            .collect(Collectors.toList());
+        .collect(Collectors.toList());
   }
 
   private boolean chargeReferencesAccountDesignators(
diff --git a/service/src/main/java/io/mifos/individuallending/internal/command/CheckLateCommand.java b/service/src/main/java/io/mifos/individuallending/internal/command/CheckLateCommand.java
index bb31db2..acab032 100644
--- a/service/src/main/java/io/mifos/individuallending/internal/command/CheckLateCommand.java
+++ b/service/src/main/java/io/mifos/individuallending/internal/command/CheckLateCommand.java
@@ -15,6 +15,9 @@
  */
 package io.mifos.individuallending.internal.command;
 
+/**
+ * @author Myrle Krantz
+ */
 public class CheckLateCommand {
   private final String productIdentifier;
   private final String caseIdentifier;
diff --git a/service/src/main/java/io/mifos/individuallending/internal/command/MarkLateCommand.java b/service/src/main/java/io/mifos/individuallending/internal/command/MarkLateCommand.java
new file mode 100644
index 0000000..400a8ba
--- /dev/null
+++ b/service/src/main/java/io/mifos/individuallending/internal/command/MarkLateCommand.java
@@ -0,0 +1,52 @@
+/*
+ * 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.command;
+
+/**
+ * @author Myrle Krantz
+ */
+public class MarkLateCommand {
+  private final String productIdentifier;
+  private final String caseIdentifier;
+  private final String forTime;
+
+  public MarkLateCommand(String productIdentifier, String caseIdentifier, String forTime) {
+    this.productIdentifier = productIdentifier;
+    this.caseIdentifier = caseIdentifier;
+    this.forTime = forTime;
+  }
+
+  public String getProductIdentifier() {
+    return productIdentifier;
+  }
+
+  public String getCaseIdentifier() {
+    return caseIdentifier;
+  }
+
+  public String getForTime() {
+    return forTime;
+  }
+
+  @Override
+  public String toString() {
+    return "MarkLateCommand{" +
+        "productIdentifier='" + productIdentifier + '\'' +
+        ", caseIdentifier='" + caseIdentifier + '\'' +
+        ", forTime='" + forTime + '\'' +
+        '}';
+  }
+}
\ No newline at end of file
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 8f1c6e9..a9cc03a 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
@@ -22,23 +22,31 @@
 import io.mifos.core.command.internal.CommandBus;
 import io.mifos.core.lang.ApplicationName;
 import io.mifos.core.lang.DateConverter;
+import io.mifos.core.lang.ServiceException;
+import io.mifos.individuallending.api.v1.domain.product.AccountDesignators;
+import io.mifos.individuallending.api.v1.domain.workflow.Action;
 import io.mifos.individuallending.api.v1.events.IndividualLoanCommandEvent;
 import io.mifos.individuallending.api.v1.events.IndividualLoanEventConstants;
 import io.mifos.individuallending.internal.command.ApplyInterestCommand;
 import io.mifos.individuallending.internal.command.CheckLateCommand;
+import io.mifos.individuallending.internal.command.MarkLateCommand;
 import io.mifos.individuallending.internal.service.DataContextOfAction;
 import io.mifos.individuallending.internal.service.DataContextService;
+import io.mifos.individuallending.internal.service.DesignatorToAccountIdentifierMapper;
+import io.mifos.individuallending.internal.service.ScheduledActionHelpers;
 import io.mifos.portfolio.api.v1.domain.Case;
 import io.mifos.portfolio.service.config.PortfolioProperties;
 import io.mifos.portfolio.service.internal.command.CreateBeatPublishCommand;
 import io.mifos.portfolio.service.internal.repository.CaseEntity;
 import io.mifos.portfolio.service.internal.repository.CaseRepository;
+import io.mifos.portfolio.service.internal.util.AccountingAdapter;
 import io.mifos.rhythm.spi.v1.domain.BeatPublish;
 import io.mifos.rhythm.spi.v1.events.BeatPublishEvent;
 import io.mifos.rhythm.spi.v1.events.EventConstants;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.transaction.annotation.Transactional;
 
+import java.math.BigDecimal;
 import java.time.LocalDateTime;
 import java.util.Collections;
 import java.util.stream.Stream;
@@ -54,6 +62,7 @@
   private final DataContextService dataContextService;
   private final ApplicationName applicationName;
   private final CommandBus commandBus;
+  private final AccountingAdapter accountingAdapter;
 
   @Autowired
   public BeatPublishCommandHandler(
@@ -61,12 +70,14 @@
       final PortfolioProperties portfolioProperties,
       final DataContextService dataContextService,
       final ApplicationName applicationName,
-      final CommandBus commandBus) {
+      final CommandBus commandBus,
+      final AccountingAdapter accountingAdapter) {
     this.caseRepository = caseRepository;
     this.portfolioProperties = portfolioProperties;
     this.dataContextService = dataContextService;
     this.applicationName = applicationName;
     this.commandBus = commandBus;
+    this.accountingAdapter = accountingAdapter;
   }
 
   @Transactional
@@ -106,14 +117,53 @@
   @CommandHandler(logStart = CommandLogLevel.INFO, logFinish = CommandLogLevel.INFO)
   @EventEmitter(
       selectorName = io.mifos.portfolio.api.v1.events.EventConstants.SELECTOR_NAME,
-      selectorValue = IndividualLoanEventConstants.SELECTOR_CHECK_LATE_INDIVIDUALLOAN_CASE)
+      selectorValue = IndividualLoanEventConstants.CHECK_LATE_INDIVIDUALLOAN_CASE)
   public IndividualLoanCommandEvent process(final CheckLateCommand command) {
     final String productIdentifier = command.getProductIdentifier();
     final String caseIdentifier = command.getCaseIdentifier();
+    final LocalDateTime forTime = DateConverter.fromIsoString(command.getForTime());
     final DataContextOfAction dataContextOfAction = dataContextService.checkedGetDataContext(
         productIdentifier, caseIdentifier, Collections.emptyList());
 
-//TODO:
+    final DesignatorToAccountIdentifierMapper designatorToAccountIdentifierMapper
+        = new DesignatorToAccountIdentifierMapper(dataContextOfAction);
+    final String customerLoanAccountIdentifier = designatorToAccountIdentifierMapper.mapOrThrow(AccountDesignators.CUSTOMER_LOAN);
+    final String lateFeeAccrualAccountIdentifier = designatorToAccountIdentifierMapper.mapOrThrow(AccountDesignators.LATE_FEE_ACCRUAL);
+
+    final BigDecimal currentBalance = accountingAdapter.getCurrentBalance(customerLoanAccountIdentifier);
+    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))
+            .orElseThrow(() ->
+                ServiceException.badRequest("No last disbursal date for ''{0}.{1}'' could be determined.  " +
+                    "Therefore it cannot be checked for lateness.", productIdentifier, caseIdentifier));
+
+    final long repaymentPeriodsBetweenBeginningAndToday = ScheduledActionHelpers.generateRepaymentPeriods(
+        dateOfMostRecentDisbursement.toLocalDate(),
+        forTime.toLocalDate(),
+        dataContextOfAction.getCaseParameters())
+        .count() - 1;
+
+    final BigDecimal expectedPaymentSum = dataContextOfAction
+        .getCaseParametersEntity()
+        .getPaymentSize()
+        .multiply(BigDecimal.valueOf(repaymentPeriodsBetweenBeginningAndToday));
+
+    final BigDecimal paymentsSum = accountingAdapter.sumMatchingEntriesSinceDate(
+        customerLoanAccountIdentifier,
+        dateOfMostRecentDisbursement.toLocalDate(),
+        dataContextOfAction.getMessageForCharge(Action.ACCEPT_PAYMENT));
+
+    final BigDecimal lateFeesAccrued = accountingAdapter.sumMatchingEntriesSinceDate(
+        lateFeeAccrualAccountIdentifier,
+        dateOfMostRecentDisbursement.toLocalDate(),
+        dataContextOfAction.getMessageForCharge(Action.MARK_LATE));
+
+    if (paymentsSum.compareTo(expectedPaymentSum.add(lateFeesAccrued)) < 0)
+      commandBus.dispatch(new MarkLateCommand(productIdentifier, caseIdentifier, command.getForTime()));
 
     return new IndividualLoanCommandEvent(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 5d105b4..dd0822d 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
@@ -24,7 +24,6 @@
 import io.mifos.core.lang.ServiceException;
 import io.mifos.individuallending.IndividualLendingPatternFactory;
 import io.mifos.individuallending.api.v1.domain.product.AccountDesignators;
-import io.mifos.individuallending.api.v1.domain.product.ChargeIdentifiers;
 import io.mifos.individuallending.api.v1.domain.workflow.Action;
 import io.mifos.individuallending.api.v1.events.IndividualLoanCommandEvent;
 import io.mifos.individuallending.api.v1.events.IndividualLoanEventConstants;
@@ -338,12 +337,10 @@
           "End of term not set for active case ''{0}.{1}.''", productIdentifier, caseIdentifier);
 
     final CostComponentsForRepaymentPeriod costComponentsForRepaymentPeriod =
-        costComponentService.getCostComponentsForAcceptPayment(dataContextOfAction, command.getCommand().getPaymentSize());
-
-    final BigDecimal sumOfAdjustments = costComponentsForRepaymentPeriod.stream()
-        .filter(entry -> entry.getKey().getIdentifier().equals(ChargeIdentifiers.REPAYMENT_ID))
-        .map(entry -> entry.getValue().getAmount())
-        .reduce(BigDecimal.ZERO, BigDecimal::add);
+        costComponentService.getCostComponentsForAcceptPayment(
+            dataContextOfAction,
+            command.getCommand().getPaymentSize(),
+            DateConverter.fromIsoString(command.getCommand().getCreatedOn()).toLocalDate());
 
     final DesignatorToAccountIdentifierMapper designatorToAccountIdentifierMapper
         = new DesignatorToAccountIdentifierMapper(dataContextOfAction);
@@ -369,6 +366,49 @@
 
   @Transactional
   @CommandHandler(logStart = CommandLogLevel.INFO, logFinish = CommandLogLevel.INFO)
+  @EventEmitter(
+      selectorName = EventConstants.SELECTOR_NAME,
+      selectorValue = IndividualLoanEventConstants.MARK_LATE_INDIVIDUALLOAN_CASE)
+  public IndividualLoanCommandEvent process(final MarkLateCommand command) {
+    final String productIdentifier = command.getProductIdentifier();
+    final String caseIdentifier = command.getCaseIdentifier();
+    final DataContextOfAction dataContextOfAction = dataContextService.checkedGetDataContext(
+        productIdentifier, caseIdentifier, Collections.emptyList());
+    IndividualLendingPatternFactory.checkActionCanBeExecuted(Case.State.valueOf(dataContextOfAction.getCustomerCaseEntity().getCurrentState()), Action.MARK_LATE);
+
+    checkIfTasksAreOutstanding(dataContextOfAction, Action.MARK_LATE);
+
+    if (dataContextOfAction.getCustomerCaseEntity().getEndOfTerm() == null)
+      throw ServiceException.internalError(
+          "End of term not set for active case ''{0}.{1}.''", productIdentifier, caseIdentifier);
+
+    final CostComponentsForRepaymentPeriod costComponentsForRepaymentPeriod =
+        costComponentService.getCostComponentsForMarkLate(dataContextOfAction, DateConverter.fromIsoString(command.getForTime()));
+
+    final DesignatorToAccountIdentifierMapper designatorToAccountIdentifierMapper
+        = new DesignatorToAccountIdentifierMapper(dataContextOfAction);
+
+    final List<ChargeInstance> charges = costComponentsForRepaymentPeriod.stream()
+        .map(entry -> mapCostComponentEntryToChargeInstance(
+            Action.MARK_LATE,
+            entry,
+            designatorToAccountIdentifierMapper))
+        .filter(Optional::isPresent)
+        .map(Optional::get)
+        .collect(Collectors.toList());
+
+    final LocalDateTime today = today();
+
+    accountingAdapter.bookCharges(charges,
+        "Marked late on " + command.getForTime(),
+        dataContextOfAction.getMessageForCharge(Action.MARK_LATE),
+        Action.MARK_LATE.getTransactionType());
+
+    return new IndividualLoanCommandEvent(productIdentifier, caseIdentifier, DateConverter.toIsoString(today));
+  }
+
+  @Transactional
+  @CommandHandler(logStart = CommandLogLevel.INFO, logFinish = CommandLogLevel.INFO)
   @EventEmitter(selectorName = EventConstants.SELECTOR_NAME, selectorValue = IndividualLoanEventConstants.WRITE_OFF_INDIVIDUALLOAN_CASE)
   public IndividualLoanCommandEvent process(final WriteOffCommand command) {
     final String productIdentifier = command.getProductIdentifier();
@@ -503,7 +543,7 @@
           action.name(), productIdentifier, caseIdentifier);
   }
 
-  private LocalDateTime today() {
+  private static LocalDateTime today() {
     return LocalDate.now(Clock.systemUTC()).atStartOfDay();
   }
 }
\ No newline at end of file
diff --git a/service/src/main/java/io/mifos/individuallending/internal/mapper/CaseParametersMapper.java b/service/src/main/java/io/mifos/individuallending/internal/mapper/CaseParametersMapper.java
index c1231f1..bac7bdd 100644
--- a/service/src/main/java/io/mifos/individuallending/internal/mapper/CaseParametersMapper.java
+++ b/service/src/main/java/io/mifos/individuallending/internal/mapper/CaseParametersMapper.java
@@ -149,7 +149,6 @@
     ret.setTermRange(getTermRange(caseParametersEntity));
     ret.setMaximumBalance(caseParametersEntity.getBalanceRangeMaximum().setScale(minorCurrencyUnitDigits, BigDecimal.ROUND_HALF_EVEN));
     ret.setPaymentCycle(getPaymentCycle(caseParametersEntity));
-    ret.setPaymentSize(caseParametersEntity.getPaymentSize());
     return ret;
   }
 
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 5f73add..551c39e 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
@@ -61,7 +61,8 @@
   public CostComponentsForRepaymentPeriod getCostComponentsForAction(
       final Action action,
       final DataContextOfAction dataContextOfAction,
-      final BigDecimal forPaymentSize) {
+      final BigDecimal forPaymentSize,
+      final LocalDate forDate) {
     switch (action) {
       case OPEN:
         return getCostComponentsForOpen(dataContextOfAction);
@@ -74,11 +75,11 @@
       case APPLY_INTEREST:
         return getCostComponentsForApplyInterest(dataContextOfAction);
       case ACCEPT_PAYMENT:
-        return getCostComponentsForAcceptPayment(dataContextOfAction, forPaymentSize);
+        return getCostComponentsForAcceptPayment(dataContextOfAction, forPaymentSize, forDate);
       case CLOSE:
         return getCostComponentsForClose(dataContextOfAction);
       case MARK_LATE:
-        return getCostComponentsForMarkLate(dataContextOfAction);
+        return getCostComponentsForMarkLate(dataContextOfAction, today().atStartOfDay());
       case WRITE_OFF:
         return getCostComponentsForWriteOff(dataContextOfAction);
       case RECOVER:
@@ -245,7 +246,8 @@
 
   public CostComponentsForRepaymentPeriod getCostComponentsForAcceptPayment(
       final DataContextOfAction dataContextOfAction,
-      final @Nullable BigDecimal requestedLoanPaymentSize)
+      final @Nullable BigDecimal requestedLoanPaymentSize,
+      final LocalDate forDate)
   {
     final DesignatorToAccountIdentifierMapper designatorToAccountIdentifierMapper
         = new DesignatorToAccountIdentifierMapper(dataContextOfAction);
@@ -260,14 +262,24 @@
     final ScheduledAction scheduledAction
         = ScheduledActionHelpers.getNextScheduledPayment(
         startOfTerm,
+        forDate,
         dataContextOfAction.getCustomerCaseEntity().getEndOfTerm().toLocalDate(),
         dataContextOfAction.getCaseParameters()
     );
 
-    final BigDecimal loanPaymentSize =
-        (requestedLoanPaymentSize != null) ?
-            requestedLoanPaymentSize :
-            dataContextOfAction.getCaseParametersEntity().getPaymentSize();
+    final BigDecimal loanPaymentSize;
+
+    if (requestedLoanPaymentSize != null) {
+      loanPaymentSize = requestedLoanPaymentSize;
+    }
+    else {
+      if (scheduledAction.actionPeriod != null && scheduledAction.actionPeriod.isLastPeriod()) {
+        loanPaymentSize = currentBalance;
+      }
+      else {
+        loanPaymentSize = currentBalance.min(dataContextOfAction.getCaseParametersEntity().getPaymentSize());
+      }
+    }
 
     final List<ScheduledCharge> scheduledChargesForThisAction = scheduledChargesService.getScheduledCharges(
         productIdentifier,
@@ -295,50 +307,6 @@
         true);
   }
 
-  private static boolean isAccruedChargeForAction(final ChargeDefinition chargeDefinition, final Action action) {
-    return chargeDefinition.getAccrueAction() != null &&
-        chargeDefinition.getChargeAction().equals(action.name());
-  }
-
-  private static boolean isAccrualChargeForAction(final ChargeDefinition chargeDefinition, final Action action) {
-    return chargeDefinition.getAccrueAction() != null &&
-        chargeDefinition.getAccrueAction().equals(action.name());
-  }
-
-  private CostComponent getAccruedCostComponentToApply(final DataContextOfAction dataContextOfAction,
-                                                       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(
-        customerLoanAccountIdentifier,
-        dataContextOfAction.getMessageForCharge(Action.DISBURSE));
-
-    return firstDisbursalDateTime.map(LocalDateTime::toLocalDate)
-        .orElseThrow(() -> ServiceException.internalError(
-            "Start of term for loan ''{0}'' could not be acquired from accounting.",
-            dataContextOfAction.getCompoundIdentifer()));
-  }
-
   public CostComponentsForRepaymentPeriod getCostComponentsForClose(final DataContextOfAction dataContextOfAction) {
     final DesignatorToAccountIdentifierMapper designatorToAccountIdentifierMapper
         = new DesignatorToAccountIdentifierMapper(dataContextOfAction);
@@ -379,8 +347,45 @@
         true);
   }
 
-  private CostComponentsForRepaymentPeriod getCostComponentsForMarkLate(final DataContextOfAction dataContextOfAction) {
-    return null;
+  public CostComponentsForRepaymentPeriod getCostComponentsForMarkLate(final DataContextOfAction dataContextOfAction,
+                                                                       final LocalDateTime forTime) {
+    final DesignatorToAccountIdentifierMapper designatorToAccountIdentifierMapper
+        = new DesignatorToAccountIdentifierMapper(dataContextOfAction);
+    final String customerLoanAccountIdentifier = designatorToAccountIdentifierMapper.mapOrThrow(AccountDesignators.CUSTOMER_LOAN);
+    final BigDecimal currentBalance = accountingAdapter.getCurrentBalance(customerLoanAccountIdentifier).negate();
+
+    final LocalDate startOfTerm = getStartOfTermOrThrow(dataContextOfAction, customerLoanAccountIdentifier);
+
+    final CaseParametersEntity caseParameters = dataContextOfAction.getCaseParametersEntity();
+    final String productIdentifier = dataContextOfAction.getProductEntity().getIdentifier();
+    final int minorCurrencyUnitDigits = dataContextOfAction.getProductEntity().getMinorCurrencyUnitDigits();
+    final ScheduledAction scheduledAction = new ScheduledAction(Action.MARK_LATE, forTime.toLocalDate());
+
+    final BigDecimal loanPaymentSize = dataContextOfAction.getCaseParametersEntity().getPaymentSize();
+
+    final List<ScheduledCharge> scheduledChargesForThisAction = scheduledChargesService.getScheduledCharges(
+        productIdentifier,
+        Collections.singletonList(scheduledAction));
+
+    final Map<Boolean, List<ScheduledCharge>> chargesSplitIntoScheduledAndAccrued = scheduledChargesForThisAction.stream()
+        .collect(Collectors.partitioningBy(x -> isAccruedChargeForAction(x.getChargeDefinition(), Action.MARK_LATE)));
+
+    final Map<ChargeDefinition, CostComponent> accruedCostComponents = chargesSplitIntoScheduledAndAccrued.get(true)
+        .stream()
+        .map(ScheduledCharge::getChargeDefinition)
+        .collect(Collectors.toMap(chargeDefinition -> chargeDefinition,
+            chargeDefinition -> getAccruedCostComponentToApply(dataContextOfAction, designatorToAccountIdentifierMapper, startOfTerm, chargeDefinition)));
+
+
+    return getCostComponentsForScheduledCharges(
+        accruedCostComponents,
+        chargesSplitIntoScheduledAndAccrued.get(false),
+        caseParameters.getBalanceRangeMaximum(),
+        currentBalance,
+        loanPaymentSize,
+        dataContextOfAction.getInterest(),
+        minorCurrencyUnitDigits,
+        true);
   }
 
   private CostComponentsForRepaymentPeriod getCostComponentsForWriteOff(final DataContextOfAction dataContextOfAction) {
@@ -482,6 +487,8 @@
       case REPAYMENT_DESIGNATOR:
         return loanPaymentSize;
       case PRINCIPAL_ADJUSTMENT_DESIGNATOR: {
+        if (loanPaymentSize.compareTo(BigDecimal.ZERO) <= 0)
+          return loanPaymentSize.abs();
         final BigDecimal newRunningBalance
             = runningBalance.subtract(balanceAdjustments.getOrDefault(AccountDesignators.CUSTOMER_LOAN, BigDecimal.ZERO));
         final BigDecimal newLoanPaymentSize = loanPaymentSize.min(newRunningBalance);
@@ -599,6 +606,50 @@
     return chargeDefinition.getAccrualAccountDesignator() != null;
   }
 
+  private static boolean isAccruedChargeForAction(final ChargeDefinition chargeDefinition, final Action action) {
+    return chargeDefinition.getAccrueAction() != null &&
+        chargeDefinition.getChargeAction().equals(action.name());
+  }
+
+  private static boolean isAccrualChargeForAction(final ChargeDefinition chargeDefinition, final Action action) {
+    return chargeDefinition.getAccrueAction() != null &&
+        chargeDefinition.getAccrueAction().equals(action.name());
+  }
+
+  private CostComponent getAccruedCostComponentToApply(final DataContextOfAction dataContextOfAction,
+                                                       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(
+        customerLoanAccountIdentifier,
+        dataContextOfAction.getMessageForCharge(Action.DISBURSE));
+
+    return firstDisbursalDateTime.map(LocalDateTime::toLocalDate)
+        .orElseThrow(() -> ServiceException.internalError(
+            "Start of term for loan ''{0}'' could not be acquired from accounting.",
+            dataContextOfAction.getCompoundIdentifer()));
+  }
+
   private static LocalDate today() {
     return LocalDate.now(Clock.systemUTC());
   }
diff --git a/service/src/main/java/io/mifos/individuallending/internal/service/CostComponentsForRepaymentPeriod.java b/service/src/main/java/io/mifos/individuallending/internal/service/CostComponentsForRepaymentPeriod.java
index cb7938a..83372f4 100644
--- a/service/src/main/java/io/mifos/individuallending/internal/service/CostComponentsForRepaymentPeriod.java
+++ b/service/src/main/java/io/mifos/individuallending/internal/service/CostComponentsForRepaymentPeriod.java
@@ -41,7 +41,8 @@
   }
 
   public Stream<Map.Entry<ChargeDefinition, CostComponent>> stream() {
-    return costComponents.entrySet().stream();
+    return costComponents.entrySet().stream()
+        .filter(costComponentEntry -> costComponentEntry.getValue().getAmount().compareTo(BigDecimal.ZERO) != 0);
   }
 
   BigDecimal getBalanceAdjustment() {
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 6108651..62ddc51 100644
--- a/service/src/main/java/io/mifos/individuallending/internal/service/IndividualLoanService.java
+++ b/service/src/main/java/io/mifos/individuallending/internal/service/IndividualLoanService.java
@@ -15,9 +15,11 @@
  */
 package io.mifos.individuallending.internal.service;
 
+import io.mifos.core.lang.ServiceException;
 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 org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 
@@ -49,6 +51,8 @@
 
     final List<ScheduledAction> scheduledActions = ScheduledActionHelpers.getHypotheticalScheduledActions(initialDisbursalDate, dataContextOfAction.getCaseParameters());
 
+    final Set<Action> actionsScheduled = scheduledActions.stream().map(x -> x.action).collect(Collectors.toSet());
+
     final List<ScheduledCharge> scheduledCharges = scheduledChargesService.getScheduledCharges(dataContextOfAction.getProductEntity().getIdentifier(), scheduledActions);
 
     final BigDecimal loanPaymentSize = CostComponentService.getLoanPaymentSize(
@@ -60,6 +64,7 @@
     final List<PlannedPayment> plannedPaymentsElements = getPlannedPaymentsElements(
         dataContextOfAction.getCaseParametersEntity().getBalanceRangeMaximum(),
         minorCurrencyUnitDigits,
+        actionsScheduled,
         scheduledCharges,
         loanPaymentSize,
         dataContextOfAction.getInterest());
@@ -78,6 +83,8 @@
           final Set<ChargeName> chargeNames) {
     final int fromIndex = size*pageIndex;
     final int toIndex = Math.min(size*(pageIndex+1), plannedPaymentsElements.size());
+    if (toIndex < fromIndex)
+      throw ServiceException.badRequest("Page number ''{0}'' out of range.", pageIndex);
     final List<PlannedPayment> elements = plannedPaymentsElements.subList(fromIndex, toIndex);
 
     final PlannedPaymentPage ret = new PlannedPaymentPage();
@@ -97,15 +104,17 @@
   static private List<PlannedPayment> getPlannedPaymentsElements(
       final BigDecimal initialBalance,
       final int minorCurrencyUnitDigits,
+      final Set<Action> actionsScheduled,
       final List<ScheduledCharge> scheduledCharges,
       final BigDecimal loanPaymentSize,
       final BigDecimal interest) {
     final Map<Period, SortedSet<ScheduledCharge>> orderedScheduledChargesGroupedByPeriod
         = scheduledCharges.stream()
+        .filter(scheduledCharge -> chargeIsNotAccruedOrAccruesAtActionScheduled(actionsScheduled, scheduledCharge))
         .collect(Collectors.groupingBy(IndividualLoanService::getPeriodFromScheduledCharge,
             Collectors.mapping(x -> x,
                 Collector.of(
-                    () -> new TreeSet<>(new ScheduledChargesService.ScheduledChargeComparator()),
+                    () -> new TreeSet<>(new ScheduledChargeComparator()),
                     SortedSet::add,
                     (left, right) -> { left.addAll(right); return left; }))));
 
@@ -153,6 +162,14 @@
     return plannedPayments;
   }
 
+  private static boolean chargeIsNotAccruedOrAccruesAtActionScheduled(
+      final Set<Action> actionsScheduled,
+      final ScheduledCharge scheduledCharge) {
+    // For example to prevent late charges from showing up on planned payments.
+    return scheduledCharge.getChargeDefinition().getAccrueAction() == null ||
+        actionsScheduled.contains(Action.valueOf(scheduledCharge.getChargeDefinition().getAccrueAction()));
+  }
+
   private static Period getPeriodFromScheduledCharge(final ScheduledCharge scheduledCharge) {
     final ScheduledAction scheduledAction = scheduledCharge.getScheduledAction();
     if (ScheduledActionHelpers.actionHasNoActionPeriod(scheduledAction.action))
diff --git a/service/src/main/java/io/mifos/individuallending/internal/service/Period.java b/service/src/main/java/io/mifos/individuallending/internal/service/Period.java
index 2b28eb5..625f6cf 100644
--- a/service/src/main/java/io/mifos/individuallending/internal/service/Period.java
+++ b/service/src/main/java/io/mifos/individuallending/internal/service/Period.java
@@ -29,20 +29,30 @@
 class Period implements Comparable<Period> {
   final private LocalDate beginDate;
   final private LocalDate endDate;
+  final private boolean lastPeriod;
 
   Period(final LocalDate beginDate, final LocalDate endDateExclusive) {
     this.beginDate = beginDate;
     this.endDate = endDateExclusive;
+    this.lastPeriod = false;
+  }
+
+  Period(final LocalDate beginDate, final LocalDate endDateExclusive, final boolean lastPeriod) {
+    this.beginDate = beginDate;
+    this.endDate = endDateExclusive;
+    this.lastPeriod = lastPeriod;
   }
 
   Period(final LocalDate beginDate, final int periodLength) {
     this.beginDate = beginDate;
     this.endDate = beginDate.plusDays(periodLength);
+    this.lastPeriod = false;
   }
 
   Period(final int periodLength, final LocalDate endDate) {
     this.beginDate = endDate.minusDays(periodLength);
     this.endDate = endDate;
+    this.lastPeriod = false;
   }
 
   LocalDate getBeginDate() {
@@ -53,6 +63,10 @@
     return endDate;
   }
 
+  boolean isLastPeriod() {
+    return lastPeriod;
+  }
+
   String getEndDateAsString() {
     return endDate == null ? null : DateConverter.toIsoString(endDate);
   }
@@ -74,24 +88,33 @@
   public boolean equals(Object o) {
     if (this == o) return true;
     if (o == null || getClass() != o.getClass()) return false;
-    Period that = (Period) o;
-    return Objects.equals(beginDate, that.beginDate) &&
-            Objects.equals(endDate, that.endDate);
+    Period period = (Period) o;
+    return lastPeriod == period.lastPeriod &&
+        Objects.equals(beginDate, period.beginDate) &&
+        Objects.equals(endDate, period.endDate);
   }
 
   @Override
   public int hashCode() {
-    return Objects.hash(beginDate, endDate);
+    return Objects.hash(beginDate, endDate, lastPeriod);
   }
 
   @Override
   public int compareTo(@Nonnull Period o) {
-    final int comparison = compareNullableDates(endDate, o.endDate);
-
-    if (comparison == 0)
-      return compareNullableDates(beginDate, o.beginDate);
-    else
+    int comparison = compareNullableDates(endDate, o.endDate);
+    if (comparison != 0)
       return comparison;
+
+    comparison = compareNullableDates(beginDate, o.beginDate);
+    if (comparison != 0)
+      return comparison;
+
+    if (lastPeriod == o.lastPeriod)
+      return 0;
+    else if (lastPeriod)
+      return -1;
+    else
+      return 1;
   }
 
   @SuppressWarnings("ConstantConditions")
@@ -109,8 +132,9 @@
   @Override
   public String toString() {
     return "Period{" +
-            "beginDate=" + beginDate +
-            ", endDate=" + endDate +
-            '}';
+        "beginDate=" + beginDate +
+        ", endDate=" + endDate +
+        ", lastPeriod=" + lastPeriod +
+        '}';
   }
 }
diff --git a/service/src/main/java/io/mifos/individuallending/internal/service/PeriodChargeCalculator.java b/service/src/main/java/io/mifos/individuallending/internal/service/PeriodChargeCalculator.java
index 91281cc..3aaebf5 100644
--- a/service/src/main/java/io/mifos/individuallending/internal/service/PeriodChargeCalculator.java
+++ b/service/src/main/java/io/mifos/individuallending/internal/service/PeriodChargeCalculator.java
@@ -17,7 +17,6 @@
 
 import io.mifos.individuallending.api.v1.domain.workflow.Action;
 import io.mifos.portfolio.api.v1.domain.ChargeDefinition;
-import org.springframework.stereotype.Service;
 
 import java.math.BigDecimal;
 import java.time.Duration;
@@ -31,13 +30,7 @@
 /**
  * @author Myrle Krantz
  */
-@SuppressWarnings("WeakerAccess")
-@Service
-public class PeriodChargeCalculator {
-  public PeriodChargeCalculator()
-  {
-  }
-
+class PeriodChargeCalculator {
   static Map<Period, BigDecimal> getPeriodAccrualInterestRate(
       final BigDecimal interest,
       final List<ScheduledCharge> scheduledCharges,
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 323e3f1..edcef2e 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
@@ -59,7 +59,7 @@
   }
 
   boolean actionIsOnOrAfter(final LocalDate date) {
-    return when.compareTo(date) > 0;
+    return when.compareTo(date) >= 0;
   }
 
   @Override
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 b9c5eb6..c6f4fc8 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
@@ -55,16 +55,16 @@
   }
 
   public static ScheduledAction getNextScheduledPayment(final @Nonnull LocalDate startOfTerm,
+                                                        final @Nonnull LocalDate fromDate,
                                                         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 effectiveEndOfTerm = fromDate.isAfter(endOfTerm) ? fromDate : endOfTerm;
 
     return getHypotheticalScheduledActionsForDisbursedLoan(startOfTerm, effectiveEndOfTerm, caseParameters)
         .filter(x -> x.action.equals(Action.ACCEPT_PAYMENT))
-        .filter(x -> x.actionIsOnOrAfter(now))
+        .filter(x -> x.actionIsOnOrAfter(fromDate))
         .findFirst()
-        .orElseGet(() -> new ScheduledAction(Action.ACCEPT_PAYMENT, now));
+        .orElseGet(() -> new ScheduledAction(Action.ACCEPT_PAYMENT, fromDate));
   }
 
   private static Stream<ScheduledAction> getHypotheticalScheduledActionsForDisbursedLoan(
@@ -104,10 +104,10 @@
             .limit(ChronoUnit.DAYS.between(repaymentPeriod.getBeginDate(), repaymentPeriod.getEndDate()));
   }
 
-  private static Stream<Period> generateRepaymentPeriods(
-          final LocalDate startOfTerm,
-          final LocalDate endOfTerm,
-          final CaseParameters caseParameters) {
+  public static Stream<Period> generateRepaymentPeriods(
+      final LocalDate startOfTerm,
+      final LocalDate endOfTerm,
+      final CaseParameters caseParameters) {
 
     final List<Period> ret = new ArrayList<>();
     LocalDate lastPaymentDate = startOfTerm;
@@ -119,7 +119,7 @@
       lastPaymentDate = nextPaymentDate;
       nextPaymentDate = generateNextPaymentDate(caseParameters, lastPaymentDate);
     }
-    ret.add(new Period(lastPaymentDate, nextPaymentDate));
+    ret.add(new Period(lastPaymentDate, nextPaymentDate, true));
 
     return ret.stream();
   }
diff --git a/service/src/main/java/io/mifos/individuallending/internal/service/ScheduledChargeComparator.java b/service/src/main/java/io/mifos/individuallending/internal/service/ScheduledChargeComparator.java
new file mode 100644
index 0000000..e597b48
--- /dev/null
+++ b/service/src/main/java/io/mifos/individuallending/internal/service/ScheduledChargeComparator.java
@@ -0,0 +1,68 @@
+/*
+ * 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.ChargeProportionalDesignator;
+import io.mifos.portfolio.api.v1.domain.ChargeDefinition;
+
+import java.util.Comparator;
+import java.util.Optional;
+
+/**
+ * @author Myrle Krantz
+ */
+class ScheduledChargeComparator implements Comparator<ScheduledCharge>
+{
+  @Override
+  public int compare(ScheduledCharge o1, ScheduledCharge o2) {
+    return compareScheduledCharges(o1, o2);
+  }
+
+  static int compareScheduledCharges(ScheduledCharge o1, ScheduledCharge o2) {
+    int ret = o1.getScheduledAction().when.compareTo(o2.getScheduledAction().when);
+    if (ret != 0)
+      return ret;
+
+    ret = o1.getScheduledAction().action.compareTo(o2.getScheduledAction().action);
+    if (ret != 0)
+      return ret;
+
+    ret = proportionalityApplicationOrder(o1.getChargeDefinition(), o2.getChargeDefinition());
+    if (ret != 0)
+      return ret;
+
+    return o1.getChargeDefinition().getIdentifier().compareTo(o2.getChargeDefinition().getIdentifier());
+  }
+
+  static int proportionalityApplicationOrder(final ChargeDefinition o1, final ChargeDefinition o2) {
+    final Optional<ChargeProportionalDesignator> aProportionalToDesignator
+        = ChargeProportionalDesignator.fromString(o1.getProportionalTo());
+    final Optional<ChargeProportionalDesignator> bProportionalToDesignator
+        = ChargeProportionalDesignator.fromString(o2.getProportionalTo());
+
+    if (aProportionalToDesignator.isPresent() && bProportionalToDesignator.isPresent())
+      return Integer.compare(
+          aProportionalToDesignator.get().getOrderOfApplication(),
+          bProportionalToDesignator.get().getOrderOfApplication());
+    else if (aProportionalToDesignator.isPresent())
+      return 1;
+    else if (bProportionalToDesignator.isPresent())
+      return -1;
+    else
+      return 0;
+  }
+}
\ No newline at end of file
diff --git a/service/src/main/java/io/mifos/individuallending/internal/service/ScheduledChargesService.java b/service/src/main/java/io/mifos/individuallending/internal/service/ScheduledChargesService.java
index fcbfc6c..b578443 100644
--- a/service/src/main/java/io/mifos/individuallending/internal/service/ScheduledChargesService.java
+++ b/service/src/main/java/io/mifos/individuallending/internal/service/ScheduledChargesService.java
@@ -15,7 +15,6 @@
  */
 package io.mifos.individuallending.internal.service;
 
-import io.mifos.individuallending.api.v1.domain.product.ChargeProportionalDesignator;
 import io.mifos.portfolio.api.v1.domain.ChargeDefinition;
 import io.mifos.portfolio.service.internal.repository.BalanceSegmentEntity;
 import io.mifos.portfolio.service.internal.repository.BalanceSegmentRepository;
@@ -103,6 +102,15 @@
     Optional<BigDecimal> getUpperBound() {
       return upperBound;
     }
+
+    @Override
+    public String toString() {
+      return "Segment{" +
+          "identifier='" + identifier + '\'' +
+          ", lowerBound=" + lowerBound +
+          ", upperBound=" + upperBound +
+          '}';
+    }
   }
 
   Optional<ChargeRange> findChargeRange(final String productIdentifier, final ChargeDefinition chargeDefinition) {
@@ -151,41 +159,7 @@
       accrueMapping = Stream.empty();
 
     return Stream.concat(
-        accrueMapping.sorted(ScheduledChargesService::proportionalityApplicationOrder),
-        chargeMapping.sorted(ScheduledChargesService::proportionalityApplicationOrder));
-  }
-
-  static class ScheduledChargeComparator implements Comparator<ScheduledCharge>
-  {
-    @Override
-    public int compare(ScheduledCharge o1, ScheduledCharge o2) {
-      int ret = o1.getScheduledAction().when.compareTo(o2.getScheduledAction().when);
-      if (ret == 0)
-        ret = o1.getScheduledAction().action.compareTo(o2.getScheduledAction().action);
-      if (ret == 0)
-        ret = proportionalityApplicationOrder(o1.getChargeDefinition(), o2.getChargeDefinition());
-      if (ret == 0)
-        return o1.getChargeDefinition().getIdentifier().compareTo(o2.getChargeDefinition().getIdentifier());
-      else
-        return ret;
-    }
-  }
-
-  private static int proportionalityApplicationOrder(final ChargeDefinition o1, final ChargeDefinition o2) {
-    final Optional<ChargeProportionalDesignator> aProportionalToDesignator
-        = ChargeProportionalDesignator.fromString(o1.getProportionalTo());
-    final Optional<ChargeProportionalDesignator> bProportionalToDesignator
-        = ChargeProportionalDesignator.fromString(o2.getProportionalTo());
-
-    if (aProportionalToDesignator.isPresent() && bProportionalToDesignator.isPresent())
-      return Integer.compare(
-          aProportionalToDesignator.get().getOrderOfApplication(),
-          bProportionalToDesignator.get().getOrderOfApplication());
-    else if (aProportionalToDesignator.isPresent())
-      return 1;
-    else if (bProportionalToDesignator.isPresent())
-      return -1;
-    else
-      return 0;
+        accrueMapping.sorted(ScheduledChargeComparator::proportionalityApplicationOrder),
+        chargeMapping.sorted(ScheduledChargeComparator::proportionalityApplicationOrder));
   }
 }
diff --git a/service/src/main/java/io/mifos/portfolio/service/internal/service/CaseService.java b/service/src/main/java/io/mifos/portfolio/service/internal/service/CaseService.java
index c1305b8..c004eb4 100644
--- a/service/src/main/java/io/mifos/portfolio/service/internal/service/CaseService.java
+++ b/service/src/main/java/io/mifos/portfolio/service/internal/service/CaseService.java
@@ -35,6 +35,7 @@
 import org.springframework.stereotype.Service;
 
 import java.math.BigDecimal;
+import java.time.LocalDateTime;
 import java.util.Arrays;
 import java.util.List;
 import java.util.Optional;
@@ -135,12 +136,14 @@
   public List<CostComponent> getActionCostComponentsForCase(final String productIdentifier,
                                                             final String caseIdentifier,
                                                             final String actionIdentifier,
+                                                            final LocalDateTime localDateTime,
                                                             final Set<String> forAccountDesignatorsList,
                                                             final BigDecimal forPaymentSize) {
     return getPatternFactoryOrThrow(productIdentifier).getCostComponentsForAction(
         productIdentifier,
         caseIdentifier,
         actionIdentifier,
+        localDateTime,
         forAccountDesignatorsList,
         forPaymentSize);
   }
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 bb15c70..910cb8f 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
@@ -101,18 +101,18 @@
         .map(DateConverter::fromIsoString);
   }
 
-  public List<LocalDateTime> getDatesOfMostRecentTwoEntriesContainingMessage(final String accountIdentifier,
-                                                                             final String message) {
+  public Optional<LocalDateTime> getDateOfMostRecentEntryContainingMessage(
+      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)
+        .findFirst()
         .map(AccountEntry::getTransactionDate)
-        .map(DateConverter::fromIsoString)
-        .collect(Collectors.toList());
+        .map(DateConverter::fromIsoString);
   }
 
   public BigDecimal sumMatchingEntriesSinceDate(final String accountIdentifier, final LocalDate startDate, final String message)
diff --git a/service/src/main/java/io/mifos/portfolio/service/rest/CaseRestController.java b/service/src/main/java/io/mifos/portfolio/service/rest/CaseRestController.java
index 3117f67..a2471c2 100644
--- a/service/src/main/java/io/mifos/portfolio/service/rest/CaseRestController.java
+++ b/service/src/main/java/io/mifos/portfolio/service/rest/CaseRestController.java
@@ -19,7 +19,9 @@
 import io.mifos.anubis.annotation.Permittable;
 import io.mifos.core.api.util.UserContextHolder;
 import io.mifos.core.command.gateway.CommandGateway;
+import io.mifos.core.lang.DateConverter;
 import io.mifos.core.lang.ServiceException;
+import io.mifos.core.lang.validation.constraints.ValidLocalDateTimeString;
 import io.mifos.portfolio.api.v1.PermittableGroupIds;
 import io.mifos.portfolio.api.v1.domain.Case;
 import io.mifos.portfolio.api.v1.domain.CasePage;
@@ -36,10 +38,13 @@
 import org.springframework.http.HttpStatus;
 import org.springframework.http.MediaType;
 import org.springframework.http.ResponseEntity;
+import org.springframework.util.StringUtils;
 import org.springframework.web.bind.annotation.*;
 
 import javax.validation.Valid;
 import java.math.BigDecimal;
+import java.time.Clock;
+import java.time.LocalDateTime;
 import java.util.List;
 import java.util.Set;
 
@@ -192,6 +197,7 @@
   List<CostComponent> getCostComponentsForAction(@PathVariable("productidentifier") final String productIdentifier,
                                                  @PathVariable("caseidentifier") final String caseIdentifier,
                                                  @PathVariable("actionidentifier") final String actionIdentifier,
+                                                 @RequestParam(value="fordatetime", required = false, defaultValue = "") final @ValidLocalDateTimeString String forDateTimeString,
                                                  @RequestParam(value="touchingaccounts", required = false, defaultValue = "") final Set<String> forAccountDesignators,
                                                  @RequestParam(value="forpaymentsize", required = false, defaultValue = "") final BigDecimal forPaymentSize)
   {
@@ -200,8 +206,15 @@
     if (forPaymentSize != null && forPaymentSize.compareTo(BigDecimal.ZERO) < 0)
       throw ServiceException.badRequest("forpaymentsize can''t be negative.");
 
+    final LocalDateTime forDateTime = StringUtils.isEmpty(forDateTimeString) ? LocalDateTime.now(Clock.systemUTC()) : DateConverter.fromIsoString(forDateTimeString);
 
-    return caseService.getActionCostComponentsForCase(productIdentifier, caseIdentifier, actionIdentifier, forAccountDesignators, forPaymentSize);
+    return caseService.getActionCostComponentsForCase(
+        productIdentifier,
+        caseIdentifier,
+        actionIdentifier,
+        forDateTime,
+        forAccountDesignators,
+        forPaymentSize);
   }
 
   @Permittable(value = AcceptedTokenType.TENANT, groupId = PermittableGroupIds.CASE_MANAGEMENT)
diff --git a/service/src/main/java/io/mifos/products/spi/PatternFactory.java b/service/src/main/java/io/mifos/products/spi/PatternFactory.java
index 4b9f3c2..dd0e555 100644
--- a/service/src/main/java/io/mifos/products/spi/PatternFactory.java
+++ b/service/src/main/java/io/mifos/products/spi/PatternFactory.java
@@ -22,6 +22,7 @@
 import io.mifos.portfolio.api.v1.domain.Pattern;
 
 import java.math.BigDecimal;
+import java.time.LocalDateTime;
 import java.util.List;
 import java.util.Optional;
 import java.util.Set;
@@ -41,6 +42,7 @@
       String productIdentifier,
       String caseIdentifier,
       String actionIdentifier,
+      LocalDateTime forDateTime,
       Set<String> forAccountDesignators,
       BigDecimal forPaymentSize);
   ProductCommandDispatcher getIndividualLendingCommandDispatcher();
diff --git a/service/src/test/java/io/mifos/individuallending/internal/service/ChargeRangeTest.java b/service/src/test/java/io/mifos/individuallending/internal/service/ChargeRangeTest.java
index a581c78..14757d7 100644
--- a/service/src/test/java/io/mifos/individuallending/internal/service/ChargeRangeTest.java
+++ b/service/src/test/java/io/mifos/individuallending/internal/service/ChargeRangeTest.java
@@ -1,3 +1,18 @@
+/*
+ * 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 org.junit.Assert;
@@ -6,6 +21,9 @@
 import java.math.BigDecimal;
 import java.util.Optional;
 
+/**
+ * @author Myrle Krantz
+ */
 public class ChargeRangeTest {
   @Test
   public void amountIsWithinRange() throws Exception {
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 eab6828..f2aa460 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
@@ -1,3 +1,18 @@
+/*
+ * 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.ChargeProportionalDesignator;
@@ -14,6 +29,9 @@
 import static io.mifos.individuallending.api.v1.domain.product.ChargeProportionalDesignator.PRINCIPAL_ADJUSTMENT_DESIGNATOR;
 import static io.mifos.individuallending.api.v1.domain.product.ChargeProportionalDesignator.RUNNING_BALANCE_DESIGNATOR;
 
+/**
+ * @author Myrle Krantz
+ */
 @RunWith(Parameterized.class)
 public class CostComponentServiceTest {
   private static class TestCase {
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 92002b7..13c7ff3 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
@@ -74,15 +74,15 @@
   {
     final List<ScheduledAction> ret = new ArrayList<>();
     LocalDate begin = initial;
-    for (final LocalDate paymentDate : paymentDates) {
-      ret.add(scheduledRepaymentAction(begin, paymentDate));
-      begin = paymentDate;
+    for (int i = 0; i < paymentDates.length; i++) {
+      ret.add(scheduledRepaymentAction(begin, paymentDates[i], (i == paymentDates.length -1)));
+      begin = paymentDates[i];
     }
     return ret;
   }
 
-  private static ScheduledAction scheduledRepaymentAction(final LocalDate from, final LocalDate to) {
-    final Period repaymentPeriod = new Period(from, to);
+  private static ScheduledAction scheduledRepaymentAction(final LocalDate from, final LocalDate to, boolean isLast) {
+    final Period repaymentPeriod = new Period(from, to, isLast);
     return new ScheduledAction(Action.ACCEPT_PAYMENT, to, repaymentPeriod, repaymentPeriod);
   }
 
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 beeae57..4daaedc 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
@@ -102,7 +102,8 @@
         REPAYMENT_ID,
         TRACK_DISBURSAL_PAYMENT_ID,
         TRACK_RETURN_PRINCIPAL_ID,
-        DISBURSE_PAYMENT_ID
+        DISBURSE_PAYMENT_ID,
+        LATE_FEE_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
@@ -314,7 +315,8 @@
             .collect(Collectors.toList());
 
     //Remaining principal should correspond with the other cost components.
-    final Set<BigDecimal> customerRepayments = Stream.iterate(1, x -> x + 1).limit(allPlannedPayments.size() - 1).map(x ->
+    final Set<BigDecimal> customerRepayments = Stream.iterate(1, x -> x + 1).limit(allPlannedPayments.size() - 1)
+        .map(x ->
         {
           final BigDecimal costComponentSum = allPlannedPayments.get(x).getCostComponents().stream()
               .filter(this::includeCostComponentsInSumCheck)
@@ -330,6 +332,8 @@
           Assert.assertEquals(valueOfPrincipleTrackingCostComponent, principalDifference);
           Assert.assertNotEquals("Remaining principle should always be positive or zero.",
               allPlannedPayments.get(x).getRemainingPrincipal().signum(), -1);
+          final boolean containsLateFee = allPlannedPayments.get(x).getCostComponents().stream().anyMatch(y -> y.getChargeIdentifier().equals(LATE_FEE_ID));
+          Assert.assertFalse("Late fee should not be included in planned payments", containsLateFee);
           return costComponentSum;
         }
     ).collect(Collectors.toSet());
diff --git a/service/src/test/java/io/mifos/individuallending/internal/service/ScheduledActionHelperTest.java b/service/src/test/java/io/mifos/individuallending/internal/service/ScheduledActionHelpersTest.java
similarity index 91%
rename from service/src/test/java/io/mifos/individuallending/internal/service/ScheduledActionHelperTest.java
rename to service/src/test/java/io/mifos/individuallending/internal/service/ScheduledActionHelpersTest.java
index 9c3b265..61771b6 100644
--- a/service/src/test/java/io/mifos/individuallending/internal/service/ScheduledActionHelperTest.java
+++ b/service/src/test/java/io/mifos/individuallending/internal/service/ScheduledActionHelpersTest.java
@@ -35,7 +35,7 @@
  * @author Myrle Krantz
  */
 @RunWith(Parameterized.class)
-public class ScheduledActionHelperTest {
+public class ScheduledActionHelpersTest {
   private static class TestCase
   {
     final String description;
@@ -355,7 +355,7 @@
 
   private final TestCase testCase;
 
-  public ScheduledActionHelperTest(final TestCase testCase)
+  public ScheduledActionHelpersTest(final TestCase testCase)
   {
     this.testCase = testCase;
   }
@@ -364,8 +364,12 @@
   public void getScheduledActions() throws Exception {
     final List<ScheduledAction> result = ScheduledActionHelpers.getHypotheticalScheduledActions(testCase.initialDisbursementDate, testCase.caseParameters);
 
-    Assert.assertTrue("Case " + testCase.description + " should contain " + testCase.expectedResultContents,
-        result.containsAll(testCase.expectedResultContents));
+    final List<ScheduledAction> missingExpectedResults = testCase.expectedResultContents.stream()
+        .filter(expectedResult -> !result.contains(expectedResult))
+        .collect(Collectors.toList());
+
+    Assert.assertTrue("Case " + testCase.description + " missing these expected results " + missingExpectedResults,
+        missingExpectedResults.isEmpty());
     result.forEach(x -> {
       Assert.assertTrue(x.toString(), testCase.earliestActionDate.isBefore(x.when) || testCase.earliestActionDate.isEqual(x.when));
       Assert.assertTrue(x.toString(), testCase.latestActionDate.isAfter(x.when) || testCase.latestActionDate.isEqual(x.when));
@@ -385,6 +389,31 @@
     Assert.assertTrue(maximumOneInterestPerDay(result));
   }
 
+  @Test
+  public void getNextScheduledPayment() throws Exception {
+    final LocalDate roughEndDate = ScheduledActionHelpers.getRoughEndDate(testCase.initialDisbursementDate, testCase.caseParameters);
+
+    testCase.expectedResultContents.stream()
+        .filter(x -> x.action == Action.ACCEPT_PAYMENT)
+        .forEach(expectedResultContents -> {
+      final ScheduledAction nextScheduledPayment = ScheduledActionHelpers.getNextScheduledPayment(
+          testCase.initialDisbursementDate,
+          expectedResultContents.when.minusDays(1),
+          roughEndDate,
+          testCase.caseParameters);
+      Assert.assertEquals(expectedResultContents, nextScheduledPayment);
+    });
+
+    final ScheduledAction afterAction = ScheduledActionHelpers.getNextScheduledPayment(
+        testCase.initialDisbursementDate,
+        roughEndDate.plusDays(1),
+        roughEndDate,
+        testCase.caseParameters);
+
+    Assert.assertNotNull(afterAction.actionPeriod);
+    Assert.assertTrue(afterAction.actionPeriod.isLastPeriod());
+  }
+
   private long countActionsByType(final List<ScheduledAction> scheduledActions, final Action actionToCount) {
     return scheduledActions.stream().filter(x -> x.action == actionToCount).count();
   }
diff --git a/service/src/test/java/io/mifos/individuallending/internal/service/ScheduledActionTest.java b/service/src/test/java/io/mifos/individuallending/internal/service/ScheduledActionTest.java
index a883ee5..a287c2f 100644
--- a/service/src/test/java/io/mifos/individuallending/internal/service/ScheduledActionTest.java
+++ b/service/src/test/java/io/mifos/individuallending/internal/service/ScheduledActionTest.java
@@ -28,7 +28,7 @@
     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.assertTrue(testSubject.actionIsOnOrAfter(today));
     Assert.assertFalse(testSubject.actionIsOnOrAfter(tomorrow));
     Assert.assertTrue(testSubject.actionIsOnOrAfter(yesterday));
   }
diff --git a/service/src/test/java/io/mifos/individuallending/internal/service/ScheduledChargeComparatorTest.java b/service/src/test/java/io/mifos/individuallending/internal/service/ScheduledChargeComparatorTest.java
new file mode 100644
index 0000000..2e38d67
--- /dev/null
+++ b/service/src/test/java/io/mifos/individuallending/internal/service/ScheduledChargeComparatorTest.java
@@ -0,0 +1,158 @@
+/*
+ * 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.workflow.Action;
+import io.mifos.portfolio.api.v1.domain.ChargeDefinition;
+import org.junit.Assert;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+import java.math.BigDecimal;
+import java.time.LocalDate;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Optional;
+
+/**
+ * @author Myrle Krantz
+ */
+@RunWith(Parameterized.class)
+public class ScheduledChargeComparatorTest {
+  static class TestCase {
+    private final String description;
+    ScheduledCharge a;
+    ScheduledCharge b;
+    int expected;
+
+    TestCase(String description) {
+      this.description = description;
+    }
+
+
+    TestCase setA(ScheduledCharge a) {
+      this.a = a;
+      return this;
+    }
+
+    TestCase setB(ScheduledCharge b) {
+      this.b = b;
+      return this;
+    }
+
+    TestCase setExpected(int expected) {
+      this.expected = expected;
+      return this;
+    }
+
+    @Override
+    public String toString() {
+      return "TestCase{" +
+          "description='" + description + '\'' +
+          ", a=" + a +
+          ", b=" + b +
+          ", expected=" + expected +
+          '}';
+    }
+  }
+
+  @Parameterized.Parameters
+  public static Collection testCases() {
+    final Collection<ScheduledChargeComparatorTest.TestCase> ret = new ArrayList<>();
+
+    final ScheduledCharge trackDisbursalScheduledCharge = new ScheduledCharge(
+        SCHEDULED_DISBURSE_ACTION,
+        TRACK_DISBURSE_CHARGE_DEFINITION,
+        Optional.empty());
+
+    final ScheduledCharge disburseFeeScheduledCharge = new ScheduledCharge(
+        SCHEDULED_DISBURSE_ACTION,
+        DISBURSE_FEE_CHARGE_DEFINITION,
+        Optional.of(new ChargeRange(BigDecimal.valueOf(1000_0000, 4), Optional.empty())));
+
+    ret.add(new TestCase("disbursementFeeVstrackDisbursement")
+        .setA(trackDisbursalScheduledCharge)
+        .setB(disburseFeeScheduledCharge)
+        .setExpected(1));
+    ret.add(new TestCase("disbursementFeeVstrackDisbursement")
+        .setA(disburseFeeScheduledCharge)
+        .setB(trackDisbursalScheduledCharge)
+        .setExpected(-1));
+    ret.add(new TestCase("disbursementFeeVstrackDisbursement")
+        .setA(disburseFeeScheduledCharge)
+        .setB(disburseFeeScheduledCharge)
+        .setExpected(0));
+
+    return ret;
+  }
+
+  private final static ScheduledAction SCHEDULED_DISBURSE_ACTION = new ScheduledAction(
+      Action.DISBURSE,
+      LocalDate.of(2017, 8, 25));
+
+  private final static ChargeDefinition TRACK_DISBURSE_CHARGE_DEFINITION = new ChargeDefinition() {{
+    this.setIdentifier("track-disburse-payment");
+    this.setName("Track disburse payment");
+    this.setDescription("Track disburse payment");
+    this.setAccrueAction(null);
+    this.setChargeAction(Action.DISBURSE.name());
+    this.setAmount(BigDecimal.valueOf(100_0000, 4));
+    this.setChargeMethod(ChargeDefinition.ChargeMethod.PROPORTIONAL);
+    this.setProportionalTo("{principaladjustment}");
+    this.setFromAccountDesignator("pending-disbursal");
+    this.setAccrualAccountDesignator(null);
+    this.setToAccountDesignator("customer-loan");
+    this.setForCycleSizeUnit(null);
+    this.setReadOnly(true);
+    this.setForSegmentSet(null);
+    this.setFromSegment(null);
+    this.setToSegment(null);
+  }};
+
+  private final static ChargeDefinition DISBURSE_FEE_CHARGE_DEFINITION = new ChargeDefinition() {{
+    this.setIdentifier("disbursement-fee2");
+    this.setName("disbursement-fee2");
+    this.setDescription("Disbursement fee");
+    this.setAccrueAction(null);
+    this.setChargeAction(Action.DISBURSE.name());
+    this.setAmount(BigDecimal.valueOf(1_0000, 4));
+    this.setChargeMethod(ChargeDefinition.ChargeMethod.PROPORTIONAL);
+    this.setProportionalTo("{principaladjustment}");
+    this.setFromAccountDesignator("'entry'");
+    this.setAccrualAccountDesignator(null);
+    this.setToAccountDesignator("disbursement-fee-income");
+    this.setForCycleSizeUnit(null);
+    this.setReadOnly(false);
+    this.setForSegmentSet("disbursement_ranges");
+    this.setFromSegment("larger");
+    this.setToSegment("larger");
+  }};
+
+  private final ScheduledChargeComparatorTest.TestCase testCase;
+
+  public ScheduledChargeComparatorTest(final ScheduledChargeComparatorTest.TestCase testCase) {
+    this.testCase = testCase;
+  }
+
+  @Test
+  public void compare() {
+    Assert.assertEquals(testCase.expected == 0, ScheduledChargeComparator.compareScheduledCharges(testCase.a, testCase.b) == 0);
+    Assert.assertEquals(testCase.expected > 0, ScheduledChargeComparator.compareScheduledCharges(testCase.a, testCase.b) > 0);
+    Assert.assertEquals(testCase.expected < 0, ScheduledChargeComparator.compareScheduledCharges(testCase.a, testCase.b) < 0);
+  }
+
+}
\ No newline at end of file
diff --git a/service/src/test/java/io/mifos/individuallending/internal/service/ScheduledChargesServiceTest.java b/service/src/test/java/io/mifos/individuallending/internal/service/ScheduledChargesServiceTest.java
index 032e223..57cee42 100644
--- a/service/src/test/java/io/mifos/individuallending/internal/service/ScheduledChargesServiceTest.java
+++ b/service/src/test/java/io/mifos/individuallending/internal/service/ScheduledChargesServiceTest.java
@@ -1,3 +1,18 @@
+/*
+ * 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.portfolio.api.v1.domain.ChargeDefinition;
@@ -13,6 +28,9 @@
 import java.math.BigDecimal;
 import java.util.*;
 
+/**
+ * @author Myrle Krantz
+ */
 @RunWith(Parameterized.class)
 public class ScheduledChargesServiceTest {