Finishing implementation of late fee
* time of transaction set from command for testability
* late fee add "on top" in suggested payment size.
* late fee only charged once per payment period.
* simple test for late payment
diff --git a/api/src/main/java/io/mifos/portfolio/api/v1/domain/ChargeDefinition.java b/api/src/main/java/io/mifos/portfolio/api/v1/domain/ChargeDefinition.java
index 3890867..50ffa13 100644
--- a/api/src/main/java/io/mifos/portfolio/api/v1/domain/ChargeDefinition.java
+++ b/api/src/main/java/io/mifos/portfolio/api/v1/domain/ChargeDefinition.java
@@ -88,6 +88,8 @@
   @ValidIdentifier(optional = true)
   private String toSegment;
 
+  private Boolean chargeOnTop;
+
   public ChargeDefinition() {
   }
 
@@ -220,6 +222,14 @@
     this.toSegment = toSegment;
   }
 
+  public Boolean getChargeOnTop() {
+    return chargeOnTop;
+  }
+
+  public void setChargeOnTop(Boolean chargeOnTop) {
+    this.chargeOnTop = chargeOnTop;
+  }
+
   @Override
   public boolean equals(Object o) {
     if (this == o) return true;
@@ -240,12 +250,13 @@
         forCycleSizeUnit == that.forCycleSizeUnit &&
         Objects.equals(forSegmentSet, that.forSegmentSet) &&
         Objects.equals(fromSegment, that.fromSegment) &&
-        Objects.equals(toSegment, that.toSegment);
+        Objects.equals(toSegment, that.toSegment) &&
+        Objects.equals(chargeOnTop, that.chargeOnTop);
   }
 
   @Override
   public int hashCode() {
-    return Objects.hash(identifier, name, description, accrueAction, chargeAction, amount, chargeMethod, proportionalTo, fromAccountDesignator, accrualAccountDesignator, toAccountDesignator, forCycleSizeUnit, readOnly, forSegmentSet, fromSegment, toSegment);
+    return Objects.hash(identifier, name, description, accrueAction, chargeAction, amount, chargeMethod, proportionalTo, fromAccountDesignator, accrualAccountDesignator, toAccountDesignator, forCycleSizeUnit, readOnly, forSegmentSet, fromSegment, toSegment, chargeOnTop);
   }
 
   @Override
@@ -267,6 +278,7 @@
         ", forSegmentSet='" + forSegmentSet + '\'' +
         ", fromSegment='" + fromSegment + '\'' +
         ", toSegment='" + toSegment + '\'' +
+        ", chargeOnTop='" + chargeOnTop + '\'' +
         '}';
   }
 }
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 fa38d9b..00e74f7 100644
--- a/component-test/src/main/java/io/mifos/portfolio/AccountingFixture.java
+++ b/component-test/src/main/java/io/mifos/portfolio/AccountingFixture.java
@@ -31,7 +31,6 @@
 import javax.validation.Validation;
 import javax.validation.Validator;
 import java.math.BigDecimal;
-import java.time.Clock;
 import java.time.LocalDateTime;
 import java.util.*;
 import java.util.stream.Collectors;
@@ -81,11 +80,11 @@
       this.account.setBalance(balance);
     }
 
-    synchronized void addAccountEntry(final String message, final double amount) {
+    synchronized void addAccountEntry(final String message, final String date, final double amount) {
       final AccountEntry accountEntry = new AccountEntry();
       accountEntry.setAmount(amount);
       accountEntry.setMessage(message);
-      accountEntry.setTransactionDate(DateConverter.toIsoString(LocalDateTime.now(Clock.systemUTC())));
+      accountEntry.setTransactionDate(date);
       accountEntries.add(accountEntry);
     }
 
@@ -391,10 +390,12 @@
       journalEntry.getCreditors().forEach(creditor ->
           accountMap.get(creditor.getAccountNumber()).addAccountEntry(
               journalEntry.getMessage(),
+              journalEntry.getTransactionDate(),
               Double.valueOf(creditor.getAmount())));
       journalEntry.getDebtors().forEach(debtor ->
           accountMap.get(debtor.getAccountNumber()).addAccountEntry(
               journalEntry.getMessage(),
+              journalEntry.getTransactionDate(),
               Double.valueOf(debtor.getAmount())));
       return null;
     }
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 d2c78d2..50724ed 100644
--- a/component-test/src/main/java/io/mifos/portfolio/TestAccountingInteractionInLoanWorkflow.java
+++ b/component-test/src/main/java/io/mifos/portfolio/TestAccountingInteractionInLoanWorkflow.java
@@ -100,7 +100,7 @@
         BigDecimal.valueOf(2_000_00, MINOR_CURRENCY_UNIT_DIGITS),
         UPPER_RANGE_DISBURSEMENT_FEE_ID, BigDecimal.valueOf(20_00, MINOR_CURRENCY_UNIT_DIGITS));
     step6CalculateInterestAccrualAndCheckForLateness(midnightToday(), null);
-    step7PaybackPartialAmount(expectedCurrentBalance, today, 0);
+    step7PaybackPartialAmount(expectedCurrentBalance, today, 0, BigDecimal.ZERO);
     step8Close();
   }
 
@@ -119,7 +119,7 @@
         BigDecimal.valueOf(1_500_00, MINOR_CURRENCY_UNIT_DIGITS),
         UPPER_RANGE_DISBURSEMENT_FEE_ID, BigDecimal.valueOf(15_00, MINOR_CURRENCY_UNIT_DIGITS));
     step6CalculateInterestAccrualAndCheckForLateness(midnightToday(), null);
-    step7PaybackPartialAmount(expectedCurrentBalance, today, 0);
+    step7PaybackPartialAmount(expectedCurrentBalance, today, 0, BigDecimal.ZERO);
     step8Close();
   }
 
@@ -139,8 +139,8 @@
     step7PaybackPartialAmount(
         repayment1.setScale(MINOR_CURRENCY_UNIT_DIGITS, BigDecimal.ROUND_HALF_EVEN),
         today,
-        0);
-    step7PaybackPartialAmount(expectedCurrentBalance, today, 0);
+        0, BigDecimal.ZERO);
+    step7PaybackPartialAmount(expectedCurrentBalance, today, 0, BigDecimal.ZERO);
     step8Close();
   }
 
@@ -174,17 +174,67 @@
     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);
+      step6CalculateInterestAndCheckForLatenessForWeek(today, week);
       final BigDecimal nextRepaymentAmount = findNextRepaymentAmount(today, (week+1)*7);
       repayments.add(nextRepaymentAmount);
-      step7PaybackPartialAmount(nextRepaymentAmount, today, (week+1)*7);
+      step7PaybackPartialAmount(nextRepaymentAmount, today, (week+1)*7, BigDecimal.ZERO);
       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,
+    Assert.assertTrue("Payments are " + repayments,
+        delta.divide(maxPayment, BigDecimal.ROUND_HALF_EVEN).compareTo(BigDecimal.valueOf(0.01)) <= 0);
+
+
+    step8Close();
+  }
+
+  @Test
+  public void workflowWithOneLateRepayment() 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 int weekOfLateRepayment = 3;
+    final List<BigDecimal> repayments = new ArrayList<>();
+    while (expectedCurrentBalance.compareTo(BigDecimal.ZERO) > 0) {
+      logger.info("Simulating week {}. Expected current balance {}.", week, expectedCurrentBalance);
+      if (week == weekOfLateRepayment) {
+        final BigDecimal lateFee = BigDecimal.valueOf(14_49, MINOR_CURRENCY_UNIT_DIGITS);
+        step6CalculateInterestAndCheckForLatenessForRangeOfDays(
+            today,
+            (week * 7) + 1,
+            (week + 1) * 7 + 2,
+            8,
+            lateFee);
+        final BigDecimal nextRepaymentAmount = findNextRepaymentAmount(today, (week + 1) * 7 + 2);
+        repayments.add(nextRepaymentAmount);
+        step7PaybackPartialAmount(nextRepaymentAmount, today, (week + 1) * 7 + 2, lateFee);
+      }
+      else {
+        step6CalculateInterestAndCheckForLatenessForWeek(today, week);
+        final BigDecimal nextRepaymentAmount = findNextRepaymentAmount(today, (week + 1) * 7);
+        repayments.add(nextRepaymentAmount);
+        step7PaybackPartialAmount(nextRepaymentAmount, today, (week + 1) * 7, BigDecimal.ZERO);
+      }
+      week++;
+    }
+
+    repayments.remove(3);
+
+    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("Payments are " + repayments,
         delta.divide(maxPayment, BigDecimal.ROUND_HALF_EVEN).compareTo(BigDecimal.valueOf(0.01)) <= 0);
 
 
@@ -401,14 +451,32 @@
 
   private void step6CalculateInterestAndCheckForLatenessForWeek(
       final LocalDateTime referenceDate,
-      final int weekNumber,
+      final int weekNumber) throws InterruptedException {
+    step6CalculateInterestAndCheckForLatenessForRangeOfDays(
+        referenceDate,
+        (weekNumber * 7) + 1,
+        (weekNumber + 1) * 7,
+        -1,
+        null);
+  }
+
+  private void step6CalculateInterestAndCheckForLatenessForRangeOfDays(
+      final LocalDateTime referenceDate,
+      final int startInclusive,
+      final int endInclusive,
+      final int dayOfLateFee,
       final BigDecimal calculatedLateFee) throws InterruptedException {
     try {
-      IntStream.rangeClosed((weekNumber*7)+1, (weekNumber+1)*7)
+      IntStream.rangeClosed(startInclusive, endInclusive)
           .mapToObj(referenceDate::plusDays)
           .forEach(day -> {
             try {
-              step6CalculateInterestAccrualAndCheckForLateness(day, calculatedLateFee);
+              if (day.equals(referenceDate.plusDays(dayOfLateFee))) {
+                step6CalculateInterestAccrualAndCheckForLateness(day, calculatedLateFee);
+              }
+              else {
+                step6CalculateInterestAccrualAndCheckForLateness(day, null);
+              }
             } catch (InterruptedException e) {
               throw new RuntimeException(e);
             }
@@ -489,12 +557,13 @@
   private void step7PaybackPartialAmount(
       final BigDecimal amount,
       final LocalDateTime referenceDate,
-      final int dayNumber) throws InterruptedException {
+      final int dayNumber,
+      final BigDecimal lateFee) throws InterruptedException {
     logger.info("step7PaybackPartialAmount '{}'", amount);
 
     AccountingFixture.mockBalance(customerLoanAccountIdentifier, expectedCurrentBalance.negate());
 
-    final BigDecimal principal = amount.subtract(interestAccrued);
+    final BigDecimal principal = amount.subtract(interestAccrued).subtract(lateFee);
 
     checkCostComponentForActionCorrect(
         product.getIdentifier(),
@@ -504,7 +573,8 @@
         amount,
         new CostComponent(ChargeIdentifiers.REPAYMENT_ID, amount),
         new CostComponent(ChargeIdentifiers.TRACK_RETURN_PRINCIPAL_ID, principal),
-        new CostComponent(ChargeIdentifiers.INTEREST_ID, interestAccrued));
+        new CostComponent(ChargeIdentifiers.INTEREST_ID, interestAccrued),
+        new CostComponent(ChargeIdentifiers.LATE_FEE_ID, lateFee));
     checkStateTransfer(
         product.getIdentifier(),
         customerCase.getIdentifier(),
@@ -523,16 +593,20 @@
     debtors.add(new Debtor(AccountingFixture.LOAN_FUNDS_SOURCE_ACCOUNT_IDENTIFIER, principal.toPlainString()));
     if (interestAccrued.compareTo(BigDecimal.ZERO) != 0)
       debtors.add(new Debtor(AccountingFixture.LOAN_INTEREST_ACCRUAL_ACCOUNT_IDENTIFIER, interestAccrued.toPlainString()));
+    if (lateFee.compareTo(BigDecimal.ZERO) != 0)
+      debtors.add(new Debtor(AccountingFixture.LATE_FEE_ACCRUAL_ACCOUNT_IDENTIFIER, lateFee.toPlainString()));
 
     final Set<Creditor> creditors = new HashSet<>();
     creditors.add(new Creditor(AccountingFixture.TELLER_ONE_ACCOUNT_IDENTIFIER, amount.toPlainString()));
     creditors.add(new Creditor(AccountingFixture.LOANS_PAYABLE_ACCOUNT_IDENTIFIER, principal.toPlainString()));
     if (interestAccrued.compareTo(BigDecimal.ZERO) != 0)
       creditors.add(new Creditor(AccountingFixture.CONSUMER_LOAN_INTEREST_ACCOUNT_IDENTIFIER, interestAccrued.toPlainString()));
+    if (lateFee.compareTo(BigDecimal.ZERO) != 0)
+      creditors.add(new Creditor(AccountingFixture.LATE_FEE_INCOME_ACCOUNT_IDENTIFIER, lateFee.toPlainString()));
 
     AccountingFixture.verifyTransfer(ledgerManager, debtors, creditors, product.getIdentifier(), customerCase.getIdentifier(), Action.ACCEPT_PAYMENT);
 
-    expectedCurrentBalance = expectedCurrentBalance.subtract(amount);
+    expectedCurrentBalance = expectedCurrentBalance.subtract(amount).add(lateFee);
     interestAccrued = BigDecimal.ZERO.setScale(MINOR_CURRENCY_UNIT_DIGITS, RoundingMode.HALF_EVEN);
   }
 
diff --git a/service/src/main/java/io/mifos/individuallending/IndividualLendingPatternFactory.java b/service/src/main/java/io/mifos/individuallending/IndividualLendingPatternFactory.java
index 8fd47db..31ea45d 100644
--- a/service/src/main/java/io/mifos/individuallending/IndividualLendingPatternFactory.java
+++ b/service/src/main/java/io/mifos/individuallending/IndividualLendingPatternFactory.java
@@ -166,7 +166,6 @@
     trackPrincipalDisbursePayment.setAmount(BigDecimal.valueOf(100));
     trackPrincipalDisbursePayment.setReadOnly(true);
 
-    //TODO: Make payable at time of ACCEPT_PAYMENT but accrued at MARK_LATE
     final ChargeDefinition lateFee = charge(
             LATE_FEE_NAME,
             Action.ACCEPT_PAYMENT,
@@ -176,6 +175,7 @@
     lateFee.setAccrueAction(Action.MARK_LATE.name());
     lateFee.setAccrualAccountDesignator(LATE_FEE_ACCRUAL);
     lateFee.setProportionalTo(ChargeProportionalDesignator.REPAYMENT_DESIGNATOR.getValue());
+    lateFee.setChargeOnTop(true);
     lateFee.setReadOnly(false);
 
     //TODO: Make multiple write off allowance charges.
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 a9cc03a..cb63a4f 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
@@ -30,10 +30,7 @@
 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.individuallending.internal.service.*;
 import io.mifos.portfolio.api.v1.domain.Case;
 import io.mifos.portfolio.service.config.PortfolioProperties;
 import io.mifos.portfolio.service.internal.command.CreateBeatPublishCommand;
@@ -49,6 +46,9 @@
 import java.math.BigDecimal;
 import java.time.LocalDateTime;
 import java.util.Collections;
+import java.util.List;
+import java.util.Optional;
+import java.util.stream.Collectors;
 import java.util.stream.Stream;
 
 /**
@@ -141,11 +141,13 @@
                 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(
+    final List<Period> repaymentPeriods = ScheduledActionHelpers.generateRepaymentPeriods(
         dateOfMostRecentDisbursement.toLocalDate(),
         forTime.toLocalDate(),
         dataContextOfAction.getCaseParameters())
-        .count() - 1;
+        .collect(Collectors.toList());
+
+    final long repaymentPeriodsBetweenBeginningAndToday = repaymentPeriods.size() - 1;
 
     final BigDecimal expectedPaymentSum = dataContextOfAction
         .getCaseParametersEntity()
@@ -162,9 +164,21 @@
         dateOfMostRecentDisbursement.toLocalDate(),
         dataContextOfAction.getMessageForCharge(Action.MARK_LATE));
 
-    if (paymentsSum.compareTo(expectedPaymentSum.add(lateFeesAccrued)) < 0)
-      commandBus.dispatch(new MarkLateCommand(productIdentifier, caseIdentifier, command.getForTime()));
+    if (paymentsSum.compareTo(expectedPaymentSum.add(lateFeesAccrued)) < 0) {
+      final Optional<LocalDateTime> dateOfMostRecentLateFee = accountingAdapter.getDateOfMostRecentEntryContainingMessage(customerLoanAccountIdentifier, dataContextOfAction.getMessageForCharge(Action.MARK_LATE));
+      if (!dateOfMostRecentLateFee.isPresent() ||
+          mostRecentLateFeeIsBeforeMostRecentRepaymentPeriod(repaymentPeriods, dateOfMostRecentLateFee.get())) {
+        commandBus.dispatch(new MarkLateCommand(productIdentifier, caseIdentifier, command.getForTime()));
+      }
+    }
 
     return new IndividualLoanCommandEvent(productIdentifier, caseIdentifier, command.getForTime());
   }
+
+  private boolean mostRecentLateFeeIsBeforeMostRecentRepaymentPeriod(
+      final List<Period> repaymentPeriods,
+      final LocalDateTime dateOfMostRecentLateFee) {
+    return repaymentPeriods.stream()
+        .anyMatch(x -> x.getBeginDate().isAfter(dateOfMostRecentLateFee.toLocalDate()));
+  }
 }
\ No newline at end of file
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 dd0822d..d6e6aed 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
@@ -118,6 +118,7 @@
 
     accountingAdapter.bookCharges(charges,
         command.getCommand().getNote(),
+        command.getCommand().getCreatedOn(),
         dataContextOfAction.getMessageForCharge(Action.OPEN),
         Action.OPEN.getTransactionType());
     //Only move to new state if book charges command was accepted.
@@ -210,6 +211,7 @@
 
     accountingAdapter.bookCharges(charges,
         command.getCommand().getNote(),
+        command.getCommand().getCreatedOn(),
         dataContextOfAction.getMessageForCharge(Action.APPROVE),
         Action.APPROVE.getTransactionType());
 
@@ -254,6 +256,7 @@
 
     accountingAdapter.bookCharges(charges,
         command.getCommand().getNote(),
+        command.getCommand().getCreatedOn(),
         dataContextOfAction.getMessageForCharge(Action.DISBURSE),
         Action.DISBURSE.getTransactionType());
     //Only move to new state if book charges command was accepted.
@@ -311,7 +314,8 @@
         .collect(Collectors.toList());
 
     accountingAdapter.bookCharges(charges,
-        "",
+        "Applied interest on " + command.getForTime(),
+        command.getForTime(),
         dataContextOfAction.getMessageForCharge(Action.APPLY_INTEREST),
         Action.APPLY_INTEREST.getTransactionType());
 
@@ -358,6 +362,7 @@
 
     accountingAdapter.bookCharges(charges,
         command.getCommand().getNote(),
+        command.getCommand().getCreatedOn(),
         dataContextOfAction.getMessageForCharge(Action.ACCEPT_PAYMENT),
         Action.ACCEPT_PAYMENT.getTransactionType());
 
@@ -401,6 +406,7 @@
 
     accountingAdapter.bookCharges(charges,
         "Marked late on " + command.getForTime(),
+        command.getForTime(),
         dataContextOfAction.getMessageForCharge(Action.MARK_LATE),
         Action.MARK_LATE.getTransactionType());
 
@@ -460,6 +466,7 @@
 
     accountingAdapter.bookCharges(charges,
         command.getCommand().getNote(),
+        command.getCommand().getCreatedOn(),
         dataContextOfAction.getMessageForCharge(Action.DISBURSE),
         Action.DISBURSE.getTransactionType());
 
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 551c39e..120378b 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
@@ -267,20 +267,6 @@
         dataContextOfAction.getCaseParameters()
     );
 
-    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,
         Collections.singletonList(scheduledAction));
@@ -295,6 +281,28 @@
             chargeDefinition -> getAccruedCostComponentToApply(dataContextOfAction, designatorToAccountIdentifierMapper, startOfTerm, chargeDefinition)));
 
 
+    final BigDecimal loanPaymentSize;
+
+    if (requestedLoanPaymentSize != null) {
+      loanPaymentSize = requestedLoanPaymentSize;
+    }
+    else {
+      if (scheduledAction.actionPeriod != null && scheduledAction.actionPeriod.isLastPeriod()) {
+        loanPaymentSize = currentBalance;
+      }
+      else {
+        final BigDecimal paymentSizeBeforeOnTopCharges = currentBalance.min(dataContextOfAction.getCaseParametersEntity().getPaymentSize());
+
+        @SuppressWarnings("UnnecessaryLocalVariable")
+        final BigDecimal paymentSizeIncludingOnTopCharges = accruedCostComponents.entrySet().stream()
+            .filter(entry -> entry.getKey().getChargeOnTop() != null && entry.getKey().getChargeOnTop())
+            .map(entry -> entry.getValue().getAmount())
+            .reduce(paymentSizeBeforeOnTopCharges, BigDecimal::add);
+
+        loanPaymentSize = paymentSizeIncludingOnTopCharges;
+      }
+    }
+
 
     return getCostComponentsForScheduledCharges(
         accruedCostComponents,
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 625f6cf..0ca16cd 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
@@ -26,7 +26,7 @@
 /**
  * @author Myrle Krantz
  */
-class Period implements Comparable<Period> {
+public class Period implements Comparable<Period> {
   final private LocalDate beginDate;
   final private LocalDate endDate;
   final private boolean lastPeriod;
@@ -55,7 +55,7 @@
     this.lastPeriod = false;
   }
 
-  LocalDate getBeginDate() {
+  public LocalDate getBeginDate() {
     return beginDate;
   }
 
diff --git a/service/src/main/java/io/mifos/portfolio/service/internal/mapper/ChargeDefinitionMapper.java b/service/src/main/java/io/mifos/portfolio/service/internal/mapper/ChargeDefinitionMapper.java
index f99d272..d09fab1 100644
--- a/service/src/main/java/io/mifos/portfolio/service/internal/mapper/ChargeDefinitionMapper.java
+++ b/service/src/main/java/io/mifos/portfolio/service/internal/mapper/ChargeDefinitionMapper.java
@@ -57,6 +57,7 @@
       ret.setFromSegment(fromSegment.getSegmentIdentifier());
       ret.setToSegment(toSegment.getSegmentIdentifier());
     }
+    ret.setOnTop(chargeDefinition.getChargeOnTop());
 
     return ret;
   }
@@ -82,6 +83,7 @@
       ret.setFromSegment(from.getFromSegment());
       ret.setToSegment(from.getToSegment());
     }
+    ret.setChargeOnTop(from.getOnTop());
 
     return ret;
   }
diff --git a/service/src/main/java/io/mifos/portfolio/service/internal/repository/ChargeDefinitionEntity.java b/service/src/main/java/io/mifos/portfolio/service/internal/repository/ChargeDefinitionEntity.java
index 11e154e..4c9bb15 100644
--- a/service/src/main/java/io/mifos/portfolio/service/internal/repository/ChargeDefinitionEntity.java
+++ b/service/src/main/java/io/mifos/portfolio/service/internal/repository/ChargeDefinitionEntity.java
@@ -88,6 +88,9 @@
   @Column(name = "to_segment")
   private String toSegment;
 
+  @Column(name = "on_top")
+  private Boolean onTop;
+
   public ChargeDefinitionEntity() {
   }
 
@@ -235,6 +238,14 @@
     this.toSegment = toSegment;
   }
 
+  public Boolean getOnTop() {
+    return onTop;
+  }
+
+  public void setOnTop(Boolean onTop) {
+    this.onTop = onTop;
+  }
+
   @Override
   public boolean equals(Object o) {
     if (this == o) return true;
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 910cb8f..4396b00 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
@@ -63,6 +63,7 @@
 
   public void bookCharges(final List<ChargeInstance> costComponents,
                           final String note,
+                          final String transactionDate,
                           final String message,
                           final String transactionType) {
     final Set<Creditor> creditors = costComponents.stream()
@@ -80,7 +81,7 @@
     journalEntry.setCreditors(creditors);
     journalEntry.setDebtors(debtors);
     journalEntry.setClerk(UserContextHolder.checkedGetUser());
-    journalEntry.setTransactionDate(DateConverter.toIsoString(LocalDateTime.now()));
+    journalEntry.setTransactionDate(transactionDate);
     journalEntry.setMessage(message);
     journalEntry.setTransactionType(transactionType);
     journalEntry.setNote(note);
diff --git a/service/src/main/resources/db/migrations/mariadb/V8__late_payment_determination.sql b/service/src/main/resources/db/migrations/mariadb/V8__late_payment_determination.sql
index cb3bc59..7d5c8c7 100644
--- a/service/src/main/resources/db/migrations/mariadb/V8__late_payment_determination.sql
+++ b/service/src/main/resources/db/migrations/mariadb/V8__late_payment_determination.sql
@@ -14,4 +14,6 @@
 -- limitations under the License.
 --
 
-ALTER TABLE bastet_il_cases ADD COLUMN payment_size DECIMAL(19,4) NULL DEFAULT NULL;
\ No newline at end of file
+ALTER TABLE bastet_il_cases ADD COLUMN payment_size DECIMAL(19,4) NULL DEFAULT NULL;
+
+ALTER TABLE bastet_p_chrg_defs ADD COLUMN on_top BOOLEAN NULL DEFAULT NULL;
\ No newline at end of file