Adding command history, and reconstructing mark late to go to
command history rather than transaction history.  This in
preparation for arrears handling.
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 bdcdd3b..b476d47 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
@@ -37,6 +37,8 @@
 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.CaseCommandEntity;
+import io.mifos.portfolio.service.internal.repository.CaseCommandRepository;
 import io.mifos.portfolio.service.internal.repository.CaseEntity;
 import io.mifos.portfolio.service.internal.repository.CaseRepository;
 import io.mifos.portfolio.service.internal.util.AccountingAdapter;
@@ -44,6 +46,10 @@
 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.data.domain.Page;
+import org.springframework.data.domain.PageRequest;
+import org.springframework.data.domain.Pageable;
+import org.springframework.data.domain.Sort;
 import org.springframework.transaction.annotation.Transactional;
 
 import java.math.BigDecimal;
@@ -61,6 +67,7 @@
 @Aggregate
 public class BeatPublishCommandHandler {
   private final CaseRepository caseRepository;
+  private final CaseCommandRepository caseCommandRepository;
   private final PortfolioProperties portfolioProperties;
   private final DataContextService dataContextService;
   private final ApplicationName applicationName;
@@ -70,12 +77,14 @@
   @Autowired
   public BeatPublishCommandHandler(
       final CaseRepository caseRepository,
+      final CaseCommandRepository caseCommandRepository,
       final PortfolioProperties portfolioProperties,
       final DataContextService dataContextService,
       final ApplicationName applicationName,
       final CommandBus commandBus,
       final AccountingAdapter accountingAdapter) {
     this.caseRepository = caseRepository;
+    this.caseCommandRepository = caseCommandRepository;
     this.portfolioProperties = portfolioProperties;
     this.dataContextService = dataContextService;
     this.applicationName = applicationName;
@@ -131,6 +140,7 @@
     final DesignatorToAccountIdentifierMapper designatorToAccountIdentifierMapper
         = new DesignatorToAccountIdentifierMapper(dataContextOfAction);
     final String customerLoanPrincipalAccountIdentifier = designatorToAccountIdentifierMapper.mapOrThrow(AccountDesignators.CUSTOMER_LOAN_PRINCIPAL);
+    final String customerLoanInterestAccountIdentifier = designatorToAccountIdentifierMapper.mapOrThrow(AccountDesignators.CUSTOMER_LOAN_INTEREST);
     final String lateFeeAccrualAccountIdentifier = designatorToAccountIdentifierMapper.mapOrThrow(AccountDesignators.LATE_FEE_ACCRUAL);
 
     final BigDecimal currentBalance = accountingAdapter.getCurrentAccountBalance(customerLoanPrincipalAccountIdentifier);
@@ -157,18 +167,23 @@
         .getPaymentSize()
         .multiply(BigDecimal.valueOf(repaymentPeriodsBetweenBeginningAndToday));
 
-    final BigDecimal paymentsSum = accountingAdapter.sumMatchingEntriesSinceDate(
+    final BigDecimal principalSum = accountingAdapter.sumMatchingEntriesSinceDate(
         customerLoanPrincipalAccountIdentifier,
         dateOfMostRecentDisbursement.toLocalDate(),
         dataContextOfAction.getMessageForCharge(Action.ACCEPT_PAYMENT));
+    final BigDecimal interestSum = accountingAdapter.sumMatchingEntriesSinceDate(
+        customerLoanInterestAccountIdentifier,
+        dateOfMostRecentDisbursement.toLocalDate(),
+        dataContextOfAction.getMessageForCharge(Action.ACCEPT_PAYMENT));
+    final BigDecimal paymentsSum = principalSum.add(interestSum);
 
     final BigDecimal lateFeesAccrued = accountingAdapter.sumMatchingEntriesSinceDate(
         lateFeeAccrualAccountIdentifier,
         dateOfMostRecentDisbursement.toLocalDate(),
         dataContextOfAction.getMessageForCharge(Action.MARK_LATE));
 
-    if (paymentsSum.compareTo(expectedPaymentSum.add(lateFeesAccrued)) < 0) {
-      final Optional<LocalDateTime> dateOfMostRecentLateFee = accountingAdapter.getDateOfMostRecentEntryContainingMessage(customerLoanPrincipalAccountIdentifier, dataContextOfAction.getMessageForCharge(Action.MARK_LATE));
+    if (paymentsSum.compareTo(expectedPaymentSum) < 0) {
+      final Optional<LocalDateTime> dateOfMostRecentLateFee = dateOfMostRecentMarkLate(dataContextOfAction.getCustomerCaseEntity().getId());
       if (!dateOfMostRecentLateFee.isPresent() ||
           mostRecentLateFeeIsBeforeMostRecentRepaymentPeriod(repaymentPeriods, dateOfMostRecentLateFee.get())) {
         commandBus.dispatch(new MarkLateCommand(productIdentifier, caseIdentifier, command.getForTime()));
@@ -178,6 +193,16 @@
     return new IndividualLoanCommandEvent(productIdentifier, caseIdentifier, command.getForTime());
   }
 
+  private Optional<LocalDateTime> dateOfMostRecentMarkLate(final Long caseId) {
+    final Pageable pageRequest = new PageRequest(0, 10, Sort.Direction.DESC, "createdOn");
+    final Page<CaseCommandEntity> page = caseCommandRepository.findByCaseIdAndActionName(
+        caseId,
+        Action.MARK_LATE.name(),
+        pageRequest);
+
+    return page.getContent().stream().findFirst().map(CaseCommandEntity::getCreatedOn);
+  }
+
   private boolean mostRecentLateFeeIsBeforeMostRecentRepaymentPeriod(
       final List<Period> repaymentPeriods,
       final LocalDateTime dateOfMostRecentLateFee) {
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 9bf8a8f..46f7689 100644
--- a/service/src/main/java/io/mifos/individuallending/internal/command/handler/IndividualLoanCommandHandler.java
+++ b/service/src/main/java/io/mifos/individuallending/internal/command/handler/IndividualLoanCommandHandler.java
@@ -16,6 +16,7 @@
 package io.mifos.individuallending.internal.command.handler;
 
 
+import io.mifos.core.api.util.UserContextHolder;
 import io.mifos.core.command.annotation.Aggregate;
 import io.mifos.core.command.annotation.CommandHandler;
 import io.mifos.core.command.annotation.CommandLogLevel;
@@ -39,9 +40,7 @@
 import io.mifos.portfolio.api.v1.domain.CostComponent;
 import io.mifos.portfolio.api.v1.events.EventConstants;
 import io.mifos.portfolio.service.internal.mapper.CaseMapper;
-import io.mifos.portfolio.service.internal.repository.CaseEntity;
-import io.mifos.portfolio.service.internal.repository.CaseRepository;
-import io.mifos.portfolio.service.internal.repository.TaskInstanceRepository;
+import io.mifos.portfolio.service.internal.repository.*;
 import io.mifos.portfolio.service.internal.util.AccountingAdapter;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.transaction.annotation.Transactional;
@@ -77,6 +76,7 @@
   private final WriteOffPaymentBuilderService writeOffPaymentBuilderService;
   private final RecoverPaymentBuilderService recoverPaymentBuilderService;
   private final AccountingAdapter accountingAdapter;
+  private final CaseCommandRepository caseCommandRepository;
   private final TaskInstanceRepository taskInstanceRepository;
   private final CaseParametersRepository caseParametersRepository;
 
@@ -95,7 +95,7 @@
       final WriteOffPaymentBuilderService writeOffPaymentBuilderService,
       final RecoverPaymentBuilderService recoverPaymentBuilderService,
       final AccountingAdapter accountingAdapter,
-      final TaskInstanceRepository taskInstanceRepository,
+      CaseCommandRepository caseCommandRepository, final TaskInstanceRepository taskInstanceRepository,
       final CaseParametersRepository caseParametersRepository) {
     this.caseRepository = caseRepository;
     this.dataContextService = dataContextService;
@@ -110,6 +110,7 @@
     this.writeOffPaymentBuilderService = writeOffPaymentBuilderService;
     this.recoverPaymentBuilderService = recoverPaymentBuilderService;
     this.accountingAdapter = accountingAdapter;
+    this.caseCommandRepository = caseCommandRepository;
     this.taskInstanceRepository = taskInstanceRepository;
     this.caseParametersRepository = caseParametersRepository;
   }
@@ -139,14 +140,22 @@
 
     final LocalDateTime today = today();
 
-    accountingAdapter.bookCharges(paymentBuilder.getBalanceAdjustments(),
+    final Optional<String> transactionUniqueifier = accountingAdapter.bookCharges(paymentBuilder.getBalanceAdjustments(),
         designatorToAccountIdentifierMapper,
         command.getCommand().getNote(),
         command.getCommand().getCreatedOn(),
         dataContextOfAction.getMessageForCharge(Action.OPEN),
         Action.OPEN.getTransactionType());
-    //Only move to new state if book charges command was accepted.
+
     final CaseEntity customerCase = dataContextOfAction.getCustomerCaseEntity();
+
+    recordCommand(
+        command.getCommand().getCreatedOn(),
+        customerCase.getId(),
+        Action.OPEN,
+        transactionUniqueifier);
+
+    //Only move to new state if book charges command was accepted.
     customerCase.setCurrentState(Case.State.PENDING.name());
     caseRepository.save(customerCase);
 
@@ -178,7 +187,21 @@
 
     final LocalDateTime today = today();
 
+    final Optional<String> transactionUniqueifier = accountingAdapter.bookCharges(paymentBuilder.getBalanceAdjustments(),
+        designatorToAccountIdentifierMapper,
+        command.getCommand().getNote(),
+        command.getCommand().getCreatedOn(),
+        dataContextOfAction.getMessageForCharge(Action.DENY),
+        Action.DENY.getTransactionType());
+
     final CaseEntity customerCase = dataContextOfAction.getCustomerCaseEntity();
+
+    recordCommand(
+        command.getCommand().getCreatedOn(),
+        customerCase.getId(),
+        Action.DENY,
+        transactionUniqueifier);
+
     customerCase.setCurrentState(Case.State.CLOSED.name());
     caseRepository.save(customerCase);
 
@@ -259,15 +282,22 @@
 
     final LocalDateTime today = today();
 
-    accountingAdapter.bookCharges(paymentBuilder.getBalanceAdjustments(),
+    final Optional<String> transactionUniqueifier = accountingAdapter.bookCharges(paymentBuilder.getBalanceAdjustments(),
         designatorToAccountIdentifierMapper,
         command.getCommand().getNote(),
         command.getCommand().getCreatedOn(),
         dataContextOfAction.getMessageForCharge(Action.APPROVE),
         Action.APPROVE.getTransactionType());
 
-    //Only move to new state if book charges command was accepted.
     final CaseEntity customerCase = dataContextOfAction.getCustomerCaseEntity();
+
+    recordCommand(
+        command.getCommand().getCreatedOn(),
+        customerCase.getId(),
+        Action.APPROVE,
+        transactionUniqueifier);
+
+    //Only move to new state if book charges command was accepted.
     customerCase.setCurrentState(Case.State.APPROVED.name());
     caseRepository.save(customerCase);
 
@@ -299,15 +329,23 @@
 
     final LocalDateTime today = today();
 
-    accountingAdapter.bookCharges(paymentBuilder.getBalanceAdjustments(),
+    final Optional<String> transactionUniqueifier = accountingAdapter.bookCharges(paymentBuilder.getBalanceAdjustments(),
         designatorToAccountIdentifierMapper,
         command.getCommand().getNote(),
         command.getCommand().getCreatedOn(),
         dataContextOfAction.getMessageForCharge(Action.DISBURSE),
         Action.DISBURSE.getTransactionType());
+
+    final CaseEntity customerCase = dataContextOfAction.getCustomerCaseEntity();
+
+    recordCommand(
+        command.getCommand().getCreatedOn(),
+        customerCase.getId(),
+        Action.DISBURSE,
+        transactionUniqueifier);
+
     //Only move to new state if book charges command was accepted.
     if (Case.State.valueOf(dataContextOfAction.getCustomerCaseEntity().getCurrentState()) != Case.State.ACTIVE) {
-      final CaseEntity customerCase = dataContextOfAction.getCustomerCaseEntity();
       final LocalDateTime endOfTerm
           = ScheduledActionHelpers.getRoughEndDate(today.toLocalDate(), dataContextOfAction.getCaseParameters())
           .atTime(LocalTime.MIDNIGHT);
@@ -352,13 +390,21 @@
     final PaymentBuilder paymentBuilder =
         applyInterestPaymentBuilderService.getPaymentBuilder(dataContextOfAction, BigDecimal.ZERO, CostComponentService.today(), runningBalances);
 
-    accountingAdapter.bookCharges(paymentBuilder.getBalanceAdjustments(),
+    final Optional<String> transactionUniqueifier = accountingAdapter.bookCharges(paymentBuilder.getBalanceAdjustments(),
         designatorToAccountIdentifierMapper,
         "Applied interest on " + command.getForTime(),
         command.getForTime(),
         dataContextOfAction.getMessageForCharge(Action.APPLY_INTEREST),
         Action.APPLY_INTEREST.getTransactionType());
 
+    final CaseEntity customerCase = dataContextOfAction.getCustomerCaseEntity();
+
+    recordCommand(
+        command.getForTime(),
+        customerCase.getId(),
+        Action.APPLY_INTEREST,
+        transactionUniqueifier);
+
     return new IndividualLoanCommandEvent(productIdentifier, caseIdentifier, command.getForTime());
   }
 
@@ -395,13 +441,21 @@
 
     final LocalDateTime today = today();
 
-    accountingAdapter.bookCharges(paymentBuilder.getBalanceAdjustments(),
+    final Optional<String> transactionUniqueifier = accountingAdapter.bookCharges(paymentBuilder.getBalanceAdjustments(),
         designatorToAccountIdentifierMapper,
         command.getCommand().getNote(),
         command.getCommand().getCreatedOn(),
         dataContextOfAction.getMessageForCharge(Action.ACCEPT_PAYMENT),
         Action.ACCEPT_PAYMENT.getTransactionType());
 
+    final CaseEntity customerCase = dataContextOfAction.getCustomerCaseEntity();
+
+    recordCommand(
+        command.getCommand().getCreatedOn(),
+        customerCase.getId(),
+        Action.ACCEPT_PAYMENT,
+        transactionUniqueifier);
+
     return new IndividualLoanCommandEvent(productIdentifier, caseIdentifier, DateConverter.toIsoString(today));
   }
 
@@ -435,13 +489,21 @@
 
     final LocalDateTime today = today();
 
-    accountingAdapter.bookCharges(paymentBuilder.getBalanceAdjustments(),
+    final Optional<String> transactionUniqueifier = accountingAdapter.bookCharges(paymentBuilder.getBalanceAdjustments(),
         designatorToAccountIdentifierMapper,
         "Marked late on " + command.getForTime(),
         command.getForTime(),
         dataContextOfAction.getMessageForCharge(Action.MARK_LATE),
         Action.MARK_LATE.getTransactionType());
 
+    final CaseEntity customerCase = dataContextOfAction.getCustomerCaseEntity();
+
+    recordCommand(
+        command.getForTime(),
+        customerCase.getId(),
+        Action.MARK_LATE,
+        transactionUniqueifier);
+
     return new IndividualLoanCommandEvent(productIdentifier, caseIdentifier, DateConverter.toIsoString(today));
   }
 
@@ -456,10 +518,35 @@
     IndividualLendingPatternFactory.checkActionCanBeExecuted(Case.State.valueOf(dataContextOfAction.getCustomerCaseEntity().getCurrentState()), Action.WRITE_OFF);
 
     checkIfTasksAreOutstanding(dataContextOfAction, Action.WRITE_OFF);
+    final DesignatorToAccountIdentifierMapper designatorToAccountIdentifierMapper
+        = new DesignatorToAccountIdentifierMapper(dataContextOfAction);
+    final RealRunningBalances runningBalances = new RealRunningBalances(
+        accountingAdapter,
+        dataContextOfAction);
+
+    final PaymentBuilder paymentBuilder =
+        writeOffPaymentBuilderService.getPaymentBuilder(
+            dataContextOfAction,
+            command.getCommand().getPaymentSize(),
+            DateConverter.fromIsoString(command.getCommand().getCreatedOn()).toLocalDate(), runningBalances);
 
     final LocalDateTime today = today();
 
+    final Optional<String> transactionUniqueifier = accountingAdapter.bookCharges(paymentBuilder.getBalanceAdjustments(),
+        designatorToAccountIdentifierMapper,
+        command.getCommand().getNote(),
+        command.getCommand().getCreatedOn(),
+        dataContextOfAction.getMessageForCharge(Action.WRITE_OFF),
+        Action.WRITE_OFF.getTransactionType());
+
     final CaseEntity customerCase = dataContextOfAction.getCustomerCaseEntity();
+
+    recordCommand(
+        command.getCommand().getCreatedOn(),
+        customerCase.getId(),
+        Action.WRITE_OFF,
+        transactionUniqueifier);
+
     customerCase.setCurrentState(Case.State.CLOSED.name());
     caseRepository.save(customerCase);
 
@@ -489,7 +576,7 @@
 
     final LocalDateTime today = today();
 
-    accountingAdapter.bookCharges(paymentBuilder.getBalanceAdjustments(),
+    final Optional<String> transactionIdentifier = accountingAdapter.bookCharges(paymentBuilder.getBalanceAdjustments(),
         designatorToAccountIdentifierMapper,
         command.getCommand().getNote(),
         command.getCommand().getCreatedOn(),
@@ -515,9 +602,32 @@
 
     checkIfTasksAreOutstanding(dataContextOfAction, Action.RECOVER);
 
+    final DesignatorToAccountIdentifierMapper designatorToAccountIdentifierMapper
+        = new DesignatorToAccountIdentifierMapper(dataContextOfAction);
+    final RealRunningBalances runningBalances = new RealRunningBalances(
+        accountingAdapter,
+        dataContextOfAction);
+
+    final PaymentBuilder paymentBuilder =
+        recoverPaymentBuilderService.getPaymentBuilder(dataContextOfAction, BigDecimal.ZERO, CostComponentService.today(), runningBalances);
+
     final LocalDateTime today = today();
 
+    final Optional<String> transactionUniqueifier = accountingAdapter.bookCharges(paymentBuilder.getBalanceAdjustments(),
+        designatorToAccountIdentifierMapper,
+        command.getCommand().getNote(),
+        command.getCommand().getCreatedOn(),
+        dataContextOfAction.getMessageForCharge(Action.RECOVER),
+        Action.CLOSE.getTransactionType());
+
     final CaseEntity customerCase = dataContextOfAction.getCustomerCaseEntity();
+
+    recordCommand(
+        command.getCommand().getCreatedOn(),
+        customerCase.getId(),
+        Action.RECOVER,
+        transactionUniqueifier);
+
     customerCase.setCurrentState(Case.State.CLOSED.name());
     caseRepository.save(customerCase);
 
@@ -546,6 +656,20 @@
           action.name(), productIdentifier, caseIdentifier);
   }
 
+  private void recordCommand(
+      final String when,
+      final Long caseId,
+      final Action action,
+      @SuppressWarnings("OptionalUsedAsFieldOrParameterType") final Optional<String> transactionUniqueifier) {
+    final CaseCommandEntity caseCommandEntity = new CaseCommandEntity();
+    caseCommandEntity.setCaseId(caseId);
+    caseCommandEntity.setActionName(action.name());
+    caseCommandEntity.setCreatedBy(UserContextHolder.checkedGetUser());
+    caseCommandEntity.setCreatedOn(DateConverter.fromIsoString(when));
+    caseCommandEntity.setTransactionUniqueifier(transactionUniqueifier.orElse(""));
+    caseCommandRepository.save(caseCommandEntity);
+  }
+
   private static LocalDateTime today() {
     return LocalDate.now(Clock.systemUTC()).atStartOfDay();
   }
diff --git a/service/src/main/java/io/mifos/individuallending/internal/service/costcomponent/RecoverPaymentBuilderService.java b/service/src/main/java/io/mifos/individuallending/internal/service/costcomponent/RecoverPaymentBuilderService.java
index 2ca9814..f79fa28 100644
--- a/service/src/main/java/io/mifos/individuallending/internal/service/costcomponent/RecoverPaymentBuilderService.java
+++ b/service/src/main/java/io/mifos/individuallending/internal/service/costcomponent/RecoverPaymentBuilderService.java
@@ -15,25 +15,58 @@
  */
 package io.mifos.individuallending.internal.service.costcomponent;
 
+import io.mifos.individuallending.api.v1.domain.workflow.Action;
+import io.mifos.individuallending.internal.repository.CaseParametersEntity;
 import io.mifos.individuallending.internal.service.DataContextOfAction;
+import io.mifos.individuallending.internal.service.schedule.ScheduledAction;
+import io.mifos.individuallending.internal.service.schedule.ScheduledCharge;
+import io.mifos.individuallending.internal.service.schedule.ScheduledChargesService;
+import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 
 import javax.annotation.Nonnull;
 import javax.annotation.Nullable;
 import java.math.BigDecimal;
 import java.time.LocalDate;
+import java.util.Collections;
+import java.util.List;
 
 /**
  * @author Myrle Krantz
  */
 @Service
 public class RecoverPaymentBuilderService implements PaymentBuilderService {
+  private final ScheduledChargesService scheduledChargesService;
+
+  @Autowired
+  public RecoverPaymentBuilderService(final ScheduledChargesService scheduledChargesService) {
+    this.scheduledChargesService = scheduledChargesService;
+  }
+
   @Override
   public PaymentBuilder getPaymentBuilder(
       final @Nonnull DataContextOfAction dataContextOfAction,
       final @Nullable BigDecimal requestedDisbursalSize,
       final LocalDate forDate,
       final RunningBalances runningBalances) {
-    return null;
+    final CaseParametersEntity caseParameters = dataContextOfAction.getCaseParametersEntity();
+    final String productIdentifier = dataContextOfAction.getProductEntity().getIdentifier();
+    final int minorCurrencyUnitDigits = dataContextOfAction.getProductEntity().getMinorCurrencyUnitDigits();
+    final List<ScheduledAction> scheduledActions = Collections.singletonList(new ScheduledAction(Action.RECOVER, forDate));
+    final List<ScheduledCharge> scheduledCharges = scheduledChargesService.getScheduledCharges(
+        productIdentifier, scheduledActions);
+
+    final BigDecimal loanPaymentSize = dataContextOfAction.getCaseParametersEntity().getPaymentSize();
+
+    return CostComponentService.getCostComponentsForScheduledCharges(
+        scheduledCharges,
+        caseParameters.getBalanceRangeMaximum(),
+        runningBalances,
+        loanPaymentSize,
+        BigDecimal.ZERO,
+        BigDecimal.ZERO,
+        dataContextOfAction.getInterest(),
+        minorCurrencyUnitDigits,
+        true);
   }
 }
diff --git a/service/src/main/java/io/mifos/individuallending/internal/service/costcomponent/WriteOffPaymentBuilderService.java b/service/src/main/java/io/mifos/individuallending/internal/service/costcomponent/WriteOffPaymentBuilderService.java
index 41faf76..3dab27b 100644
--- a/service/src/main/java/io/mifos/individuallending/internal/service/costcomponent/WriteOffPaymentBuilderService.java
+++ b/service/src/main/java/io/mifos/individuallending/internal/service/costcomponent/WriteOffPaymentBuilderService.java
@@ -15,19 +15,34 @@
  */
 package io.mifos.individuallending.internal.service.costcomponent;
 
+import io.mifos.individuallending.api.v1.domain.workflow.Action;
+import io.mifos.individuallending.internal.repository.CaseParametersEntity;
 import io.mifos.individuallending.internal.service.DataContextOfAction;
+import io.mifos.individuallending.internal.service.schedule.ScheduledAction;
+import io.mifos.individuallending.internal.service.schedule.ScheduledCharge;
+import io.mifos.individuallending.internal.service.schedule.ScheduledChargesService;
+import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 
 import javax.annotation.Nonnull;
 import javax.annotation.Nullable;
 import java.math.BigDecimal;
 import java.time.LocalDate;
+import java.util.Collections;
+import java.util.List;
 
 /**
  * @author Myrle Krantz
  */
 @Service
 public class WriteOffPaymentBuilderService implements PaymentBuilderService {
+  private final ScheduledChargesService scheduledChargesService;
+
+  @Autowired
+  public WriteOffPaymentBuilderService(final ScheduledChargesService scheduledChargesService) {
+    this.scheduledChargesService = scheduledChargesService;
+  }
+
   @Override
   public PaymentBuilder getPaymentBuilder(
       final @Nonnull DataContextOfAction dataContextOfAction,
@@ -35,6 +50,24 @@
       final LocalDate forDate,
       final RunningBalances runningBalances)
   {
-    return null;
+    final CaseParametersEntity caseParameters = dataContextOfAction.getCaseParametersEntity();
+    final String productIdentifier = dataContextOfAction.getProductEntity().getIdentifier();
+    final int minorCurrencyUnitDigits = dataContextOfAction.getProductEntity().getMinorCurrencyUnitDigits();
+    final List<ScheduledAction> scheduledActions = Collections.singletonList(new ScheduledAction(Action.WRITE_OFF, forDate));
+    final List<ScheduledCharge> scheduledCharges = scheduledChargesService.getScheduledCharges(
+        productIdentifier, scheduledActions);
+
+    final BigDecimal loanPaymentSize = dataContextOfAction.getCaseParametersEntity().getPaymentSize();
+
+    return CostComponentService.getCostComponentsForScheduledCharges(
+        scheduledCharges,
+        caseParameters.getBalanceRangeMaximum(),
+        runningBalances,
+        loanPaymentSize,
+        BigDecimal.ZERO,
+        BigDecimal.ZERO,
+        dataContextOfAction.getInterest(),
+        minorCurrencyUnitDigits,
+        true);
   }
 }
diff --git a/service/src/main/java/io/mifos/portfolio/service/internal/repository/CaseCommandEntity.java b/service/src/main/java/io/mifos/portfolio/service/internal/repository/CaseCommandEntity.java
new file mode 100644
index 0000000..062726e
--- /dev/null
+++ b/service/src/main/java/io/mifos/portfolio/service/internal/repository/CaseCommandEntity.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright 2017 Kuelap, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.mifos.portfolio.service.internal.repository;
+
+import io.mifos.core.mariadb.util.LocalDateTimeConverter;
+
+import javax.persistence.*;
+import java.time.LocalDateTime;
+import java.util.Objects;
+
+/**
+ * @author Myrle Krantz
+ */
+@SuppressWarnings("unused")
+@Entity
+@Table(name = "bastet_case_commands")
+public class CaseCommandEntity {
+  @Id
+  @GeneratedValue(strategy = GenerationType.IDENTITY)
+  @Column(name = "id")
+  private Long id;
+
+  @Column(name = "case_id")
+  private Long caseId;
+
+  @Column(name = "action_name")
+  private String actionName;
+
+  @Column(name = "created_on")
+  @Convert(converter = LocalDateTimeConverter.class)
+  private LocalDateTime createdOn;
+
+  @Column(name = "created_by")
+  private String createdBy;
+
+  @Column(name = "thoth_transaction_uq")
+  private String transactionUniqueifier;
+
+  public Long getId() {
+    return id;
+  }
+
+  public void setId(Long id) {
+    this.id = id;
+  }
+
+  public Long getCaseId() {
+    return caseId;
+  }
+
+  public void setCaseId(Long caseId) {
+    this.caseId = caseId;
+  }
+
+  public String getActionName() {
+    return actionName;
+  }
+
+  public void setActionName(String actionName) {
+    this.actionName = actionName;
+  }
+
+  public LocalDateTime getCreatedOn() {
+    return createdOn;
+  }
+
+  public void setCreatedOn(LocalDateTime createdOn) {
+    this.createdOn = createdOn;
+  }
+
+  public String getCreatedBy() {
+    return createdBy;
+  }
+
+  public void setCreatedBy(String createdBy) {
+    this.createdBy = createdBy;
+  }
+
+  public String getTransactionUniqueifier() {
+    return transactionUniqueifier;
+  }
+
+  public void setTransactionUniqueifier(String transactionUniqueifier) {
+    this.transactionUniqueifier = transactionUniqueifier;
+  }
+
+  @Override
+  public boolean equals(Object o) {
+    if (this == o) return true;
+    if (o == null || getClass() != o.getClass()) return false;
+    CaseCommandEntity that = (CaseCommandEntity) o;
+    return Objects.equals(caseId, that.caseId) &&
+        Objects.equals(actionName, that.actionName) &&
+        Objects.equals(transactionUniqueifier, that.transactionUniqueifier);
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hash(caseId, actionName, transactionUniqueifier);
+  }
+}
diff --git a/service/src/main/java/io/mifos/portfolio/service/internal/repository/CaseCommandRepository.java b/service/src/main/java/io/mifos/portfolio/service/internal/repository/CaseCommandRepository.java
new file mode 100644
index 0000000..fa78532
--- /dev/null
+++ b/service/src/main/java/io/mifos/portfolio/service/internal/repository/CaseCommandRepository.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2017 Kuelap, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.mifos.portfolio.service.internal.repository;
+
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.Pageable;
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.stereotype.Repository;
+
+/**
+ * @author Myrle Krantz
+ */
+@Repository
+public interface CaseCommandRepository extends JpaRepository<CaseCommandEntity, Long> {
+  Page<CaseCommandEntity> findByCaseIdAndActionName(Long caseId, String actionName, Pageable pageable);
+}
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 b2a2067..a0a136b 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
@@ -69,12 +69,13 @@
     this.logger = logger;
   }
 
-  public void bookCharges(final Map<String, BigDecimal> balanceAdjustments,
-                          final DesignatorToAccountIdentifierMapper designatorToAccountIdentifierMapper,
-                          final String note,
-                          final String transactionDate,
-                          final String message,
-                          final String transactionType) {
+  public Optional<String> bookCharges(
+      final Map<String, BigDecimal> balanceAdjustments,
+      final DesignatorToAccountIdentifierMapper designatorToAccountIdentifierMapper,
+      final String note,
+      final String transactionDate,
+      final String message,
+      final String transactionType) {
     final JournalEntry journalEntry = new JournalEntry();
     final Set<Creditor> creditors = new HashSet<>();
     journalEntry.setCreditors(creditors);
@@ -106,8 +107,10 @@
 
     //noinspection ConstantConditions
     if (creditors.isEmpty() && debtors.isEmpty())
-      return;
+      return Optional.empty();
 
+    final String transactionUniqueifier = RandomStringUtils.random(26, true, true);
+    final String transactionIdentifier = "portfolio." + message + "." + transactionUniqueifier;
     journalEntry.setCreditors(creditors);
     journalEntry.setDebtors(debtors);
     journalEntry.setClerk(UserContextHolder.checkedGetUser());
@@ -115,9 +118,10 @@
     journalEntry.setMessage(message);
     journalEntry.setTransactionType(transactionType);
     journalEntry.setNote(note);
-    journalEntry.setTransactionIdentifier("portfolio." + message + "." + RandomStringUtils.random(26, true, true));
+    journalEntry.setTransactionIdentifier(transactionIdentifier);
 
     ledgerManager.createJournalEntry(journalEntry);
+    return Optional.of(transactionUniqueifier);
   }
 
   public Optional<LocalDateTime> getDateOfOldestEntryContainingMessage(final String accountIdentifier,
diff --git a/service/src/main/resources/db/migrations/mariadb/V9__arrears_determination.sql b/service/src/main/resources/db/migrations/mariadb/V9__arrears_determination.sql
new file mode 100644
index 0000000..2c91530
--- /dev/null
+++ b/service/src/main/resources/db/migrations/mariadb/V9__arrears_determination.sql
@@ -0,0 +1,28 @@
+--
+-- 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.
+--
+
+CREATE TABLE bastet_case_commands (
+  id BIGINT NOT NULL AUTO_INCREMENT,
+  case_id                  BIGINT         NOT NULL,
+  action_name              VARCHAR(32)    NOT NULL,
+  created_on               TIMESTAMP(3)   NOT NULL,
+  created_by               VARCHAR(32)    NOT NULL,
+  thoth_transaction_uq     VARCHAR(26)  NOT NULL,
+
+  CONSTRAINT bastet_case_commands_pk PRIMARY KEY (id),
+  CONSTRAINT bastet_case_commands_uq UNIQUE (thoth_transaction_uq, action_name, case_id),
+  CONSTRAINT bastet_case_commands_fk FOREIGN KEY (case_id) REFERENCES bastet_cases (id)
+);
\ No newline at end of file