Merge pull request #14 from myrlen/arrearsAndWriteOff

Arrears and write off
diff --git a/api/src/main/java/io/mifos/individuallending/api/v1/domain/product/AccountDesignators.java b/api/src/main/java/io/mifos/individuallending/api/v1/domain/product/AccountDesignators.java
index fb78e89..424d937 100644
--- a/api/src/main/java/io/mifos/individuallending/api/v1/domain/product/AccountDesignators.java
+++ b/api/src/main/java/io/mifos/individuallending/api/v1/domain/product/AccountDesignators.java
@@ -37,6 +37,6 @@
   String LATE_FEE_ACCRUAL = "lfa";
   String PRODUCT_LOSS_ALLOWANCE = "pa";
   String GENERAL_LOSS_ALLOWANCE = "aa";
-  String GENERAL_EXPENSE = "ge";
+  String EXPENSE = "ee";
   String ENTRY = "ey";
 }
diff --git a/api/src/main/java/io/mifos/individuallending/api/v1/domain/product/ChargeIdentifiers.java b/api/src/main/java/io/mifos/individuallending/api/v1/domain/product/ChargeIdentifiers.java
index 8167c85..ce9600b 100644
--- a/api/src/main/java/io/mifos/individuallending/api/v1/domain/product/ChargeIdentifiers.java
+++ b/api/src/main/java/io/mifos/individuallending/api/v1/domain/product/ChargeIdentifiers.java
@@ -24,8 +24,6 @@
 public interface ChargeIdentifiers {
   String INTEREST_NAME = "Interest";
   String INTEREST_ID = "interest";
-  String ALLOW_FOR_WRITE_OFF_NAME = "Allow for write-off";
-  String ALLOW_FOR_WRITE_OFF_ID = "allow-for-write-off";
   String LATE_FEE_NAME = "Late fee";
   String LATE_FEE_ID = "late-fee";
   String DISBURSEMENT_FEE_NAME = "Disbursement fee";
@@ -44,6 +42,8 @@
   String REPAY_FEES_ID = "repay-fees";
   String PROVISION_FOR_LOSSES_NAME = "Provision for losses";
   String PROVISION_FOR_LOSSES_ID = "loss-provisioning";
+  String WRITE_OFF_NAME = "Write off";
+  String WRITE_OFF_ID = "write-off";
 
   static String nameToIdentifier(String name) {
     return name.toLowerCase(Locale.US).replace(" ", "-");
diff --git a/api/src/main/java/io/mifos/individuallending/api/v1/domain/workflow/Action.java b/api/src/main/java/io/mifos/individuallending/api/v1/domain/workflow/Action.java
index cd51961..1fc3dc7 100644
--- a/api/src/main/java/io/mifos/individuallending/api/v1/domain/workflow/Action.java
+++ b/api/src/main/java/io/mifos/individuallending/api/v1/domain/workflow/Action.java
@@ -30,6 +30,7 @@
   APPLY_INTEREST("INTR"),
   ACCEPT_PAYMENT("PPAY"),
   MARK_LATE("ICCT"),
+  MARK_IN_ARREARS("ICCT"),
   WRITE_OFF("ICCT"),
   CLOSE("ICCT"),
   RECOVER("ICCT");
diff --git a/api/src/main/java/io/mifos/individuallending/api/v1/events/IndividualLoanEventConstants.java b/api/src/main/java/io/mifos/individuallending/api/v1/events/IndividualLoanEventConstants.java
index 6264e74..219a016 100644
--- a/api/src/main/java/io/mifos/individuallending/api/v1/events/IndividualLoanEventConstants.java
+++ b/api/src/main/java/io/mifos/individuallending/api/v1/events/IndividualLoanEventConstants.java
@@ -31,6 +31,7 @@
   String ACCEPT_PAYMENT_INDIVIDUALLOAN_CASE = "accept-payment-individualloan-case";
   String CHECK_LATE_INDIVIDUALLOAN_CASE = "check-late-individualloan-case";
   String MARK_LATE_INDIVIDUALLOAN_CASE = "mark-late-individualloan-case";
+  String MARK_IN_ARREARS_INDIVIDUALLOAN_CASE = "mark-in-arrears-individualloan-case";
   String WRITE_OFF_INDIVIDUALLOAN_CASE = "write-off-individualloan-case";
   String CLOSE_INDIVIDUALLOAN_CASE = "close-individualloan-case";
   String RECOVER_INDIVIDUALLOAN_CASE = "recover-individualloan-case";
@@ -44,6 +45,7 @@
   String SELECTOR_ACCEPT_PAYMENT_INDIVIDUALLOAN_CASE = SELECTOR_NAME + " = '" + ACCEPT_PAYMENT_INDIVIDUALLOAN_CASE + "'";
   String SELECTOR_CHECK_LATE_INDIVIDUALLOAN_CASE = SELECTOR_NAME + " = '" + CHECK_LATE_INDIVIDUALLOAN_CASE + "'";
   String SELECTOR_MARK_LATE_INDIVIDUALLOAN_CASE = SELECTOR_NAME + " = '" + MARK_LATE_INDIVIDUALLOAN_CASE + "'";
+  String SELECTOR_MARK_IN_ARREARS_INDIVIDUALLOAN_CASE = SELECTOR_NAME + " = '" + MARK_IN_ARREARS_INDIVIDUALLOAN_CASE + "'";
   String SELECTOR_WRITE_OFF_INDIVIDUALLOAN_CASE = SELECTOR_NAME + " = '" + WRITE_OFF_INDIVIDUALLOAN_CASE + "'";
   String SELECTOR_CLOSE_INDIVIDUALLOAN_CASE = SELECTOR_NAME + " = '" + CLOSE_INDIVIDUALLOAN_CASE + "'";
   String SELECTOR_RECOVER_INDIVIDUALLOAN_CASE = SELECTOR_NAME + " = '" + RECOVER_INDIVIDUALLOAN_CASE + "'";
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 8d46e36..1362783 100644
--- a/component-test/src/main/java/io/mifos/portfolio/AbstractPortfolioTest.java
+++ b/component-test/src/main/java/io/mifos/portfolio/AbstractPortfolioTest.java
@@ -56,7 +56,6 @@
 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.*;
@@ -192,40 +191,41 @@
     return caseInstance;
   }
 
-  void checkStateTransfer(final String productIdentifier,
-                          final String caseIdentifier,
-                          final Action action,
-                          final List<AccountAssignment> oneTimeAccountAssignments,
-                          final String event,
-                          final Case.State nextState) throws InterruptedException {
+  void checkStateTransfer(
+      final String productIdentifier,
+      final String caseIdentifier,
+      final Action action,
+      final LocalDateTime actionDateTime,
+      final List<AccountAssignment> oneTimeAccountAssignments,
+      final String event,
+      final Case.State nextState) throws InterruptedException {
     checkStateTransfer(
         productIdentifier,
         caseIdentifier,
         action,
-        LocalDateTime.now(Clock.systemUTC()),
+        actionDateTime,
         oneTimeAccountAssignments,
         BigDecimal.ZERO,
         event,
-        midnightToday(),
         nextState);
   }
 
-  void checkStateTransfer(final String productIdentifier,
-                          final String caseIdentifier,
-                          final Action action,
-                          final LocalDateTime actionDateTime,
-                          final List<AccountAssignment> oneTimeAccountAssignments,
-                          final BigDecimal paymentSize,
-                          final String event,
-                          final LocalDateTime eventDateTime,
-                          final Case.State nextState) throws InterruptedException {
+  void checkStateTransfer(
+      final String productIdentifier,
+      final String caseIdentifier,
+      final Action action,
+      final LocalDateTime actionDateTime,
+      final List<AccountAssignment> oneTimeAccountAssignments,
+      final BigDecimal paymentSize,
+      final String event,
+      final Case.State nextState) throws InterruptedException {
     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))));
+    Assert.assertTrue(eventRecorder.wait(event, new IndividualLoanCommandEvent(productIdentifier, caseIdentifier, DateConverter.toIsoString(actionDateTime))));
 
     final Case customerCase = portfolioManager.getCase(productIdentifier, caseIdentifier);
     Assert.assertEquals(nextState.name(), customerCase.getCurrentState());
@@ -314,6 +314,13 @@
     return entryAccountAssignment;
   }
 
+  AccountAssignment assignExpenseToGeneralExpense() {
+    final AccountAssignment entryAccountAssignment = new AccountAssignment();
+    entryAccountAssignment.setDesignator(AccountDesignators.EXPENSE);
+    entryAccountAssignment.setAccountIdentifier(AccountingFixture.GENERAL_EXPENSE_ACCOUNT_IDENTIFIER);
+    return entryAccountAssignment;
+  }
+
   TaskDefinition createTaskDefinition(Product product) throws InterruptedException {
     final TaskDefinition taskDefinition = getTaskDefinition();
     portfolioManager.createTaskDefinition(product.getIdentifier(), taskDefinition);
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 892a27d..13ecedd 100644
--- a/component-test/src/main/java/io/mifos/portfolio/AccountingFixture.java
+++ b/component-test/src/main/java/io/mifos/portfolio/AccountingFixture.java
@@ -245,6 +245,7 @@
   private static Account productLossAllowanceAccount() {
     final Account ret = new Account();
     ret.setIdentifier(PRODUCT_LOSS_ALLOWANCE_ACCOUNT_IDENTIFIER);
+    ret.setLedger(CUSTOMER_LOAN_LEDGER_IDENTIFIER);
     ret.setType(AccountType.ASSET.name());
     return ret;
   }
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 092052b..ffd241c 100644
--- a/component-test/src/main/java/io/mifos/portfolio/Fixture.java
+++ b/component-test/src/main/java/io/mifos/portfolio/Fixture.java
@@ -69,9 +69,9 @@
     accountAssignments.add(new AccountAssignment(LATE_FEE_ACCRUAL, LATE_FEE_ACCRUAL_ACCOUNT_IDENTIFIER));
     accountAssignments.add(new AccountAssignment(PRODUCT_LOSS_ALLOWANCE, PRODUCT_LOSS_ALLOWANCE_ACCOUNT_IDENTIFIER));
     accountAssignments.add(new AccountAssignment(GENERAL_LOSS_ALLOWANCE, GENERAL_LOSS_ALLOWANCE_ACCOUNT_IDENTIFIER));
-    accountAssignments.add(new AccountAssignment(GENERAL_EXPENSE, GENERAL_EXPENSE_ACCOUNT_IDENTIFIER));
+    //accountAssignments.add(new AccountAssignment(EXPENSE, ...));
     //accountAssignments.add(new AccountAssignment(ENTRY, ...));
-    // Don't assign entry account in test since it usually will not be assigned IRL.
+    // Don't assign entry and expense accounts in test since they usually will not be assigned IRL.
     accountAssignments.add(new AccountAssignment(LOAN_FUNDS_SOURCE, LOAN_FUNDS_SOURCE_ACCOUNT_IDENTIFIER));
     final AccountAssignment customerLoanPrincipalAccountAssignment = new AccountAssignment();
     customerLoanPrincipalAccountAssignment.setDesignator(AccountDesignators.CUSTOMER_LOAN_PRINCIPAL);
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 63f71b3..9ac056b 100644
--- a/component-test/src/main/java/io/mifos/portfolio/TestAccountingInteractionInLoanWorkflow.java
+++ b/component-test/src/main/java/io/mifos/portfolio/TestAccountingInteractionInLoanWorkflow.java
@@ -23,9 +23,7 @@
 import io.mifos.core.lang.DateConverter;
 import io.mifos.individuallending.api.v1.domain.caseinstance.CaseParameters;
 import io.mifos.individuallending.api.v1.domain.caseinstance.PlannedPayment;
-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.product.ChargeProportionalDesignator;
+import io.mifos.individuallending.api.v1.domain.product.*;
 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;
@@ -41,14 +39,15 @@
 import org.junit.Before;
 import org.junit.Test;
 
+import javax.annotation.Nullable;
 import java.math.BigDecimal;
 import java.math.RoundingMode;
-import java.time.Clock;
 import java.time.LocalDateTime;
 import java.time.temporal.ChronoUnit;
 import java.util.*;
 import java.util.stream.Collectors;
 import java.util.stream.IntStream;
+import java.util.stream.Stream;
 
 import static io.mifos.portfolio.Fixture.MINOR_CURRENCY_UNIT_DIGITS;
 
@@ -78,6 +77,7 @@
   private BigDecimal interestAccrued = BigDecimal.ZERO.setScale(MINOR_CURRENCY_UNIT_DIGITS, RoundingMode.HALF_EVEN);
   private BigDecimal nonLateFees = BigDecimal.ZERO.setScale(MINOR_CURRENCY_UNIT_DIGITS, RoundingMode.HALF_EVEN);
   private BigDecimal lateFees = BigDecimal.ZERO.setScale(MINOR_CURRENCY_UNIT_DIGITS, RoundingMode.HALF_EVEN);
+  private BigDecimal productLossAllowance = BigDecimal.ZERO.setScale(MINOR_CURRENCY_UNIT_DIGITS, RoundingMode.HALF_EVEN);
 
 
   @Before
@@ -235,14 +235,12 @@
         UPPER_RANGE_DISBURSEMENT_FEE_ID, BigDecimal.valueOf(20_00, MINOR_CURRENCY_UNIT_DIGITS));
 
     int week = 0;
-    final List<Payment> payments = new ArrayList<>();
     while (expectedCurrentPrincipal.compareTo(BigDecimal.ZERO) > 0) {
       logger.info("Simulating week {}. Expected current principal {}.", week, expectedCurrentPrincipal);
       step6CalculateInterestAndCheckForLatenessForWeek(today, week);
       final BigDecimal interestAccruedBeforePayment = interestAccrued;
       final BigDecimal nextRepaymentAmount = findNextRepaymentAmount(today.plusDays((week+1)*7));
       final Payment payment = step7PaybackPartialAmount(nextRepaymentAmount, today.plusDays((week + 1) * 7), BigDecimal.ZERO);
-      payments.add(payment);
       final BigDecimal interestAccrual = payment.getBalanceAdjustments().remove(AccountDesignators.INTEREST_ACCRUAL); //Don't compare these with planned payment.
       final BigDecimal customerLoanInterest = payment.getBalanceAdjustments().remove(AccountDesignators.CUSTOMER_LOAN_INTEREST);
       Assert.assertEquals("week " + week, interestAccrual.negate(), customerLoanInterest);
@@ -284,7 +282,7 @@
             today,
             (week * 7) + 1,
             (week + 1) * 7 + 2,
-            8,
+            7,
             lateFee);
         final BigDecimal nextRepaymentAmount = findNextRepaymentAmount(today.plusDays((week + 1) * 7 + 2));
         step7PaybackPartialAmount(nextRepaymentAmount, today.plusDays((week + 1) * 7 + 2), lateFee);
@@ -304,6 +302,38 @@
     step8Close(DateConverter.fromIsoString(plannedPayments.get(plannedPayments.size()-1).getPayment().getDate()));
   }
 
+  @Test
+  public void workflowTerminatingInWriteOff() throws InterruptedException {
+    final LocalDateTime today = midnightToday();
+
+    step1CreateProduct();
+    step2CreateCase();
+    step3OpenCase(today);
+    step4ApproveCase(today);
+
+    step5Disburse(
+        BigDecimal.valueOf(2_000_00, MINOR_CURRENCY_UNIT_DIGITS),
+        today,
+        UPPER_RANGE_DISBURSEMENT_FEE_ID, BigDecimal.valueOf(20_00, MINOR_CURRENCY_UNIT_DIGITS));
+
+    final BigDecimal lateFee = BigDecimal.valueOf(15_36, MINOR_CURRENCY_UNIT_DIGITS); //??? TODO: check the late fee value.
+    step6CalculateInterestAndCheckForLatenessForRangeOfDays(
+        today,
+        1,
+        8,
+        7,
+        lateFee);
+    step6ICalculateInterestAndLossAllowancesForLateLoanForRangeOfDays(
+        today,
+        new LossProvisionStep(0, BigDecimal.valueOf(1)),
+        new LossProvisionStep(1, BigDecimal.valueOf(9)),
+        new LossProvisionStep(30, BigDecimal.valueOf(30)),
+        new LossProvisionStep(60, BigDecimal.valueOf(60))
+    );
+
+    step8IWriteOff(today.plusDays(68));
+  }
+
   private BigDecimal findNextRepaymentAmount(
       final LocalDateTime forDateTime) {
     final Payment nextPayment = portfolioManager.getCostComponentsForAction(
@@ -371,6 +401,15 @@
 
     portfolioManager.enableProduct(product.getIdentifier(), true);
     Assert.assertTrue(this.eventRecorder.wait(EventConstants.PUT_PRODUCT_ENABLE, product.getIdentifier()));
+
+    final List<LossProvisionStep> lossProvisionSteps = Arrays.asList(
+        new LossProvisionStep(0, BigDecimal.ONE),
+        new LossProvisionStep(1, BigDecimal.valueOf(9)),
+        new LossProvisionStep(30, BigDecimal.valueOf(30)),
+        new LossProvisionStep(60, BigDecimal.valueOf(60)));
+    final LossProvisionConfiguration lossProvisionConfiguration = new LossProvisionConfiguration(lossProvisionSteps);
+    individualLending.changeLossProvisionConfiguration(product.getIdentifier(), lossProvisionConfiguration);
+    Assert.assertTrue(this.eventRecorder.wait(IndividualLoanEventConstants.PUT_LOSS_PROVISION_STEPS, product.getIdentifier()));
   }
 
   private void step2CreateCase() throws InterruptedException {
@@ -399,6 +438,7 @@
         product.getIdentifier(),
         customerCase.getIdentifier(),
         Action.OPEN,
+        forDateTime,
         Collections.singletonList(assignEntryToTeller()),
         IndividualLoanEventConstants.OPEN_INDIVIDUALLOAN_CASE,
         Case.State.PENDING);
@@ -421,6 +461,7 @@
         product.getIdentifier(),
         customerCase.getIdentifier(),
         Action.DENY,
+        forDateTime,
         Collections.singletonList(assignEntryToTeller()),
         IndividualLoanEventConstants.DENY_INDIVIDUALLOAN_CASE,
         Case.State.CLOSED);
@@ -447,6 +488,7 @@
         product.getIdentifier(),
         customerCase.getIdentifier(),
         Action.APPROVE,
+        forDateTime,
         Collections.singletonList(assignEntryToTeller()),
         IndividualLoanEventConstants.APPROVE_INDIVIDUALLOAN_CASE,
         Case.State.APPROVED);
@@ -478,6 +520,7 @@
       final String whichDisbursementFee,
       final BigDecimal disbursementFeeAmount) throws InterruptedException {
     logger.info("step5Disburse  '{}'", amount);
+    final BigDecimal provisionForLosses = amount.multiply(BigDecimal.valueOf(0.01)).setScale(MINOR_CURRENCY_UNIT_DIGITS, BigDecimal.ROUND_HALF_EVEN);
     checkCostComponentForActionCorrect(
         product.getIdentifier(),
         customerCase.getIdentifier(),
@@ -494,30 +537,32 @@
         product.getIdentifier(),
         customerCase.getIdentifier(),
         Action.DISBURSE,
-        LocalDateTime.now(Clock.systemUTC()),
+        forDateTime,
         Collections.singletonList(assignEntryToTeller()),
         amount,
         IndividualLoanEventConstants.DISBURSE_INDIVIDUALLOAN_CASE,
-        midnightToday(),
         Case.State.ACTIVE);
     checkNextActionsCorrect(product.getIdentifier(), customerCase.getIdentifier(), Action.APPLY_INTEREST,
-        Action.APPLY_INTEREST, Action.MARK_LATE, Action.ACCEPT_PAYMENT, Action.DISBURSE, Action.WRITE_OFF, Action.CLOSE);
+        Action.APPLY_INTEREST, Action.MARK_LATE, Action.ACCEPT_PAYMENT, Action.DISBURSE, Action.MARK_IN_ARREARS, Action.WRITE_OFF, Action.CLOSE);
 
     final Set<Debtor> debtors = new HashSet<>();
     debtors.add(new Debtor(customerLoanPrincipalIdentifier, amount.toPlainString()));
     debtors.add(new Debtor(customerLoanFeeIdentifier, PROCESSING_FEE_AMOUNT.add(disbursementFeeAmount).add(LOAN_ORIGINATION_FEE_AMOUNT).toPlainString()));
+    debtors.add(new Debtor(AccountingFixture.GENERAL_LOSS_ALLOWANCE_ACCOUNT_IDENTIFIER, provisionForLosses.toPlainString()));
 
     final Set<Creditor> creditors = new HashSet<>();
     creditors.add(new Creditor(AccountingFixture.TELLER_ONE_ACCOUNT_IDENTIFIER, amount.toString()));
     creditors.add(new Creditor(AccountingFixture.PROCESSING_FEE_INCOME_ACCOUNT_IDENTIFIER, PROCESSING_FEE_AMOUNT.toPlainString()));
     creditors.add(new Creditor(AccountingFixture.DISBURSEMENT_FEE_INCOME_ACCOUNT_IDENTIFIER, disbursementFeeAmount.toPlainString()));
     creditors.add(new Creditor(AccountingFixture.LOAN_ORIGINATION_FEES_ACCOUNT_IDENTIFIER, LOAN_ORIGINATION_FEE_AMOUNT.toPlainString()));
+    creditors.add(new Creditor(AccountingFixture.PRODUCT_LOSS_ALLOWANCE_ACCOUNT_IDENTIFIER, provisionForLosses.toPlainString()));
     AccountingFixture.verifyTransfer(ledgerManager, debtors, creditors, product.getIdentifier(), customerCase.getIdentifier(), Action.DISBURSE);
 
     expectedCurrentPrincipal = expectedCurrentPrincipal.add(amount);
     interestAccrued = BigDecimal.ZERO;
     nonLateFees = nonLateFees.add(disbursementFeeAmount).add(PROCESSING_FEE_AMOUNT).add(LOAN_ORIGINATION_FEE_AMOUNT);
     lateFees = BigDecimal.ZERO;
+    productLossAllowance = provisionForLosses;
 
     updateBalanceMock();
   }
@@ -581,8 +626,14 @@
         .multiply(dailyInterestRate)
         .setScale(MINOR_CURRENCY_UNIT_DIGITS, BigDecimal.ROUND_HALF_EVEN);
 
+    final BigDecimal provisionForLosses = calculatedLateFee.equals(BigDecimal.ZERO) ?
+        BigDecimal.ZERO :
+        expectedCurrentPrincipal.multiply(BigDecimal.valueOf(0.09))
+            .setScale(MINOR_CURRENCY_UNIT_DIGITS, BigDecimal.ROUND_HALF_EVEN);
+
     logger.info("calculatedInterest '{}'", calculatedInterest);
     logger.info("calculatedLateFee '{}'", calculatedLateFee);
+    logger.info("provisionForLosses '{}'", provisionForLosses);
 
 
     checkCostComponentForActionCorrect(
@@ -602,7 +653,8 @@
           null,
           null,
           forDateTime,
-          MINOR_CURRENCY_UNIT_DIGITS);
+          MINOR_CURRENCY_UNIT_DIGITS,
+          new CostComponent(ChargeIdentifiers.PROVISION_FOR_LOSSES_ID, provisionForLosses.negate()));
     }
     final BeatPublish interestBeat = new BeatPublish(beatIdentifier, midnightTimeStamp);
     portfolioBeatListener.publishBeat(interestBeat);
@@ -615,6 +667,10 @@
     Assert.assertTrue(eventRecorder.wait(IndividualLoanEventConstants.APPLY_INTEREST_INDIVIDUALLOAN_CASE,
         new IndividualLoanCommandEvent(product.getIdentifier(), customerCase.getIdentifier(), midnightTimeStamp)));
 
+    if (calculatedLateFee.compareTo(BigDecimal.ZERO) != 0)
+      Assert.assertTrue(eventRecorder.wait(IndividualLoanEventConstants.MARK_LATE_INDIVIDUALLOAN_CASE,
+          new IndividualLoanCommandEvent(product.getIdentifier(), customerCase.getIdentifier(), midnightTimeStamp)));
+
 
     final Case customerCaseAfterStateChange = portfolioManager.getCase(product.getIdentifier(), customerCase.getIdentifier());
     Assert.assertEquals(customerCaseAfterStateChange.getCurrentState(), Case.State.ACTIVE.name());
@@ -641,11 +697,17 @@
       lateFeeDebtors.add(new Debtor(
           customerLoanFeeIdentifier,
           calculatedLateFee.toPlainString()));
+      lateFeeDebtors.add(new Debtor(
+          AccountingFixture.GENERAL_LOSS_ALLOWANCE_ACCOUNT_IDENTIFIER,
+          provisionForLosses.toPlainString()));
 
       final Set<Creditor> lateFeeCreditors = new HashSet<>();
       lateFeeCreditors.add(new Creditor(
           AccountingFixture.LATE_FEE_ACCRUAL_ACCOUNT_IDENTIFIER,
           calculatedLateFee.toPlainString()));
+      lateFeeCreditors.add(new Creditor(
+          AccountingFixture.PRODUCT_LOSS_ALLOWANCE_ACCOUNT_IDENTIFIER,
+          provisionForLosses.toPlainString()));
       AccountingFixture.verifyTransfer(
           ledgerManager,
           lateFeeDebtors,
@@ -654,6 +716,7 @@
           customerCase.getIdentifier(),
           Action.MARK_LATE);
       lateFees = lateFees.add(calculatedLateFee);
+      productLossAllowance = productLossAllowance.add(provisionForLosses);
     }
     interestAccrued = interestAccrued.add(calculatedInterest);
 
@@ -661,6 +724,144 @@
     logger.info("Completed step6CalculateInterestAccrualAndCheckForLateness");
   }
 
+  private void step6ICalculateInterestAndLossAllowancesForLateLoanForRangeOfDays(
+      final LocalDateTime referenceDate,
+      final LossProvisionStep... lossProvisionSteps) throws InterruptedException
+  {
+    try {
+      final Map<Integer, BigDecimal> lossProvisionConfiguration = Stream.of(lossProvisionSteps)
+          .collect(Collectors.toMap(LossProvisionStep::getDaysLate, LossProvisionStep::getPercentProvision));
+
+      IntStream.rangeClosed(9, 67)
+          .forEach(day -> {
+            try {
+              final int daysLate = day - 7;
+              step6ICalculateInterestAndLossAllowancesForLateLoan(
+                  referenceDate.plusDays(day),
+                  daysLate,
+                  lossProvisionConfiguration.get(daysLate));
+            } 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;
+    }
+  }
+
+  private void step6ICalculateInterestAndLossAllowancesForLateLoan(
+      final LocalDateTime forDateTime,
+      final int daysLate,
+      final @Nullable BigDecimal percentProvision) throws InterruptedException
+  {
+    logger.info("step6ICalculateInterestAndLossAllowancesForLateLoan  '{}'", forDateTime);
+    final String beatIdentifier = "alignment0";
+    final String midnightTimeStamp = DateConverter.toIsoString(forDateTime);
+
+    final BigDecimal dailyInterestRate = Fixture.INTEREST_RATE
+        .divide(BigDecimal.valueOf(100), 8, BigDecimal.ROUND_HALF_EVEN)
+        .divide(Fixture.ACCRUAL_PERIODS, 8, BigDecimal.ROUND_HALF_EVEN);
+
+    final BigDecimal calculatedInterest = expectedCurrentPrincipal
+        .multiply(dailyInterestRate)
+        .setScale(MINOR_CURRENCY_UNIT_DIGITS, BigDecimal.ROUND_HALF_EVEN);
+
+    final BigDecimal provisionForLosses = percentProvision == null ?
+        BigDecimal.ZERO :
+        expectedCurrentPrincipal.multiply(percentProvision.divide(BigDecimal.valueOf(100), 2, BigDecimal.ROUND_HALF_EVEN))
+            .setScale(MINOR_CURRENCY_UNIT_DIGITS, BigDecimal.ROUND_HALF_EVEN);
+
+    logger.info("calculatedInterest '{}'", calculatedInterest);
+    logger.info("percentProvision '{}'", percentProvision);
+    logger.info("provisionForLosses '{}'", provisionForLosses);
+
+
+    checkCostComponentForActionCorrect(
+        product.getIdentifier(),
+        customerCase.getIdentifier(),
+        Action.APPLY_INTEREST,
+        null,
+        null,
+        forDateTime,
+        MINOR_CURRENCY_UNIT_DIGITS);
+
+    checkCostComponentForActionCorrect(
+        product.getIdentifier(),
+        customerCase.getIdentifier(),
+        Action.MARK_IN_ARREARS,
+        null,
+        BigDecimal.valueOf(daysLate),
+        forDateTime,
+        MINOR_CURRENCY_UNIT_DIGITS,
+        new CostComponent(ChargeIdentifiers.PROVISION_FOR_LOSSES_ID, provisionForLosses.negate()));
+
+    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)));
+
+    if (percentProvision != null) {
+      Assert.assertTrue(eventRecorder.wait(IndividualLoanEventConstants.MARK_IN_ARREARS_INDIVIDUALLOAN_CASE,
+          new IndividualLoanCommandEvent(product.getIdentifier(), customerCase.getIdentifier(), midnightTimeStamp)));
+    }
+
+
+    final Case customerCaseAfterStateChange = portfolioManager.getCase(product.getIdentifier(), customerCase.getIdentifier());
+    Assert.assertEquals(customerCaseAfterStateChange.getCurrentState(), Case.State.ACTIVE.name());
+
+    final Set<Debtor> debtors = new HashSet<>();
+    debtors.add(new Debtor(
+        customerLoanInterestIdentifier,
+        calculatedInterest.toPlainString()));
+
+    final Set<Creditor> creditors = new HashSet<>();
+    creditors.add(new Creditor(
+        AccountingFixture.LOAN_INTEREST_ACCRUAL_ACCOUNT_IDENTIFIER,
+        calculatedInterest.toPlainString()));
+    AccountingFixture.verifyTransfer(
+        ledgerManager,
+        debtors,
+        creditors,
+        product.getIdentifier(),
+        customerCase.getIdentifier(), Action.APPLY_INTEREST);
+
+    if (percentProvision != null) {
+      final Set<Debtor> lateFeeDebtors = new HashSet<>();
+      lateFeeDebtors.add(new Debtor(
+          AccountingFixture.GENERAL_LOSS_ALLOWANCE_ACCOUNT_IDENTIFIER,
+          provisionForLosses.toPlainString()));
+
+      final Set<Creditor> lateFeeCreditors = new HashSet<>();
+      lateFeeCreditors.add(new Creditor(
+          AccountingFixture.PRODUCT_LOSS_ALLOWANCE_ACCOUNT_IDENTIFIER,
+          provisionForLosses.toPlainString()));
+      AccountingFixture.verifyTransfer(
+          ledgerManager,
+          lateFeeDebtors,
+          lateFeeCreditors,
+          product.getIdentifier(),
+          customerCase.getIdentifier(),
+          Action.MARK_IN_ARREARS);
+      productLossAllowance = productLossAllowance.add(provisionForLosses);
+    }
+    interestAccrued = interestAccrued.add(calculatedInterest);
+
+    updateBalanceMock();
+    logger.info("Completed step6ICalculateInterestAndLossAllowancesForLateLoan");
+
+  }
+
   private Payment step7PaybackPartialAmount(
       final BigDecimal amount,
       final LocalDateTime forDateTime,
@@ -689,10 +890,9 @@
         Collections.singletonList(assignEntryToTeller()),
         amount,
         IndividualLoanEventConstants.ACCEPT_PAYMENT_INDIVIDUALLOAN_CASE,
-        midnightToday(),
         Case.State.ACTIVE); //Close has to be done explicitly.
     checkNextActionsCorrect(product.getIdentifier(), customerCase.getIdentifier(), Action.APPLY_INTEREST,
-        Action.APPLY_INTEREST, Action.MARK_LATE, Action.ACCEPT_PAYMENT, Action.DISBURSE, Action.WRITE_OFF, Action.CLOSE);
+        Action.APPLY_INTEREST, Action.MARK_LATE, Action.ACCEPT_PAYMENT, Action.DISBURSE, Action.MARK_IN_ARREARS, Action.WRITE_OFF, Action.CLOSE);
 
     final Set<Debtor> debtors = new HashSet<>();
     BigDecimal tellerOneDebit = principal;
@@ -736,7 +936,7 @@
   private void step8Close(
       final LocalDateTime forDateTime) throws InterruptedException
   {
-    logger.info("step8Close");
+    logger.info("step8Close for '{}'", forDateTime);
 
     checkCostComponentForActionCorrect(
         product.getIdentifier(),
@@ -750,6 +950,7 @@
         product.getIdentifier(),
         customerCase.getIdentifier(),
         Action.CLOSE,
+        forDateTime,
         Collections.singletonList(assignEntryToTeller()),
         IndividualLoanEventConstants.CLOSE_INDIVIDUALLOAN_CASE,
         Case.State.CLOSED); //Close has to be done explicitly.
@@ -757,6 +958,47 @@
     checkNextActionsCorrect(product.getIdentifier(), customerCase.getIdentifier());
   }
 
+  private void step8IWriteOff(
+      final LocalDateTime forDateTime) throws InterruptedException {
+    logger.info("step8IWriteOff for '{}'", forDateTime);
+
+    checkCostComponentForActionCorrect(
+        product.getIdentifier(),
+        customerCase.getIdentifier(),
+        Action.WRITE_OFF,
+        null,
+        null,
+        forDateTime,
+        MINOR_CURRENCY_UNIT_DIGITS,
+        new CostComponent(ChargeIdentifiers.WRITE_OFF_ID, expectedCurrentPrincipal),
+        new CostComponent(ChargeIdentifiers.INTEREST_ID, interestAccrued),
+        new CostComponent(ChargeIdentifiers.LATE_FEE_ID, lateFees));
+    checkStateTransfer(
+        product.getIdentifier(),
+        customerCase.getIdentifier(),
+        Action.WRITE_OFF,
+        forDateTime,
+        Collections.singletonList(assignExpenseToGeneralExpense()),
+        IndividualLoanEventConstants.WRITE_OFF_INDIVIDUALLOAN_CASE,
+        Case.State.CLOSED); //Close has to be done explicitly.
+
+    checkNextActionsCorrect(product.getIdentifier(), customerCase.getIdentifier());
+
+    final Set<Debtor> debtors = new HashSet<>();
+    debtors.add(new Debtor(AccountingFixture.GENERAL_EXPENSE_ACCOUNT_IDENTIFIER, expectedCurrentPrincipal.toPlainString()));
+    debtors.add(new Debtor(AccountingFixture.LATE_FEE_ACCRUAL_ACCOUNT_IDENTIFIER, lateFees.toPlainString()));
+    debtors.add(new Debtor(AccountingFixture.LOAN_INTEREST_ACCRUAL_ACCOUNT_IDENTIFIER, interestAccrued.toPlainString()));
+
+    final Set<Creditor> creditors = new HashSet<>();
+    creditors.add(new Creditor(AccountingFixture.GENERAL_LOSS_ALLOWANCE_ACCOUNT_IDENTIFIER, expectedCurrentPrincipal.toPlainString()));
+    creditors.add(new Creditor(AccountingFixture.PRODUCT_LOSS_ALLOWANCE_ACCOUNT_IDENTIFIER, lateFees.add(interestAccrued).toPlainString()));
+
+    AccountingFixture.verifyTransfer(ledgerManager, debtors, creditors, product.getIdentifier(), customerCase.getIdentifier(), Action.WRITE_OFF);
+
+    productLossAllowance = BigDecimal.ZERO;
+    updateBalanceMock();
+  }
+
   private void updateBalanceMock() {
     logger.info("Updating balance mocks");
     final BigDecimal allFees = lateFees.add(nonLateFees);
@@ -765,9 +1007,12 @@
     AccountingFixture.mockBalance(customerLoanInterestIdentifier, interestAccrued);
     AccountingFixture.mockBalance(AccountingFixture.LOAN_INTEREST_ACCRUAL_ACCOUNT_IDENTIFIER, interestAccrued);
     AccountingFixture.mockBalance(AccountingFixture.LATE_FEE_ACCRUAL_ACCOUNT_IDENTIFIER, lateFees);
+    AccountingFixture.mockBalance(AccountingFixture.PRODUCT_LOSS_ALLOWANCE_ACCOUNT_IDENTIFIER, productLossAllowance.negate());
+    AccountingFixture.mockBalance(AccountingFixture.GENERAL_LOSS_ALLOWANCE_ACCOUNT_IDENTIFIER, productLossAllowance);
     logger.info("updated currentPrincipal '{}'", expectedCurrentPrincipal);
     logger.info("updated interestAccrued '{}'", interestAccrued);
     logger.info("updated nonLateFees '{}'", nonLateFees);
     logger.info("updated lateFees '{}'", lateFees);
+    logger.info("updated productLossAllowance '{}'", productLossAllowance);
   }
 }
\ No newline at end of file
diff --git a/component-test/src/main/java/io/mifos/portfolio/TestCommands.java b/component-test/src/main/java/io/mifos/portfolio/TestCommands.java
index 541f490..e6a2828 100644
--- a/component-test/src/main/java/io/mifos/portfolio/TestCommands.java
+++ b/component-test/src/main/java/io/mifos/portfolio/TestCommands.java
@@ -20,7 +20,6 @@
 import io.mifos.portfolio.api.v1.domain.Product;
 import org.junit.Test;
 
-import java.math.BigDecimal;
 import java.time.Clock;
 import java.time.LocalDateTime;
 import java.util.Collections;
@@ -36,57 +35,6 @@
   //public void testHappyWorkflow() throws InterruptedException
 
   @Test
-  public void testBadCustomerWorkflow() throws InterruptedException {
-    final Product product = createAndEnableProduct();
-    final Case customerCase = createCase(product.getIdentifier());
-
-    checkNextActionsCorrect(product.getIdentifier(), customerCase.getIdentifier(), Action.OPEN);
-
-
-    checkStateTransfer(
-        product.getIdentifier(),
-        customerCase.getIdentifier(),
-        Action.OPEN,
-        Collections.singletonList(assignEntryToTeller()),
-        OPEN_INDIVIDUALLOAN_CASE,
-        Case.State.PENDING);
-    checkNextActionsCorrect(product.getIdentifier(), customerCase.getIdentifier(), Action.APPROVE, Action.DENY);
-
-
-    checkStateTransfer(
-        product.getIdentifier(),
-        customerCase.getIdentifier(),
-        Action.APPROVE,
-        Collections.singletonList(assignEntryToTeller()),
-        APPROVE_INDIVIDUALLOAN_CASE,
-        Case.State.APPROVED);
-    checkNextActionsCorrect(product.getIdentifier(), customerCase.getIdentifier(), Action.DISBURSE, Action.CLOSE);
-
-
-    checkStateTransfer(
-        product.getIdentifier(),
-        customerCase.getIdentifier(),
-        Action.DISBURSE,
-        LocalDateTime.now(Clock.systemUTC()),
-        Collections.singletonList(assignEntryToTeller()),
-        BigDecimal.valueOf(2000L),
-        DISBURSE_INDIVIDUALLOAN_CASE,
-        midnightToday(),
-        Case.State.ACTIVE);
-    checkNextActionsCorrect(product.getIdentifier(), customerCase.getIdentifier(),
-            Action.APPLY_INTEREST, Action.MARK_LATE, Action.ACCEPT_PAYMENT, Action.DISBURSE, Action.WRITE_OFF, Action.CLOSE);
-
-    checkStateTransfer(
-        product.getIdentifier(),
-        customerCase.getIdentifier(),
-        Action.WRITE_OFF,
-        Collections.singletonList(assignEntryToTeller()),
-        WRITE_OFF_INDIVIDUALLOAN_CASE,
-        Case.State.CLOSED);
-    checkNextActionsCorrect(product.getIdentifier(), customerCase.getIdentifier());
-  }
-
-  @Test
   public void testApproveBeforeOpen() throws InterruptedException {
     final Product product = createAndEnableProduct();
     final Case customerCase = createCase(product.getIdentifier());
@@ -109,6 +57,7 @@
         product.getIdentifier(),
         customerCase.getIdentifier(),
         Action.OPEN,
+        LocalDateTime.now(Clock.systemUTC()),
         Collections.singletonList(assignEntryToTeller()),
         OPEN_INDIVIDUALLOAN_CASE,
         Case.State.PENDING);
diff --git a/component-test/src/main/java/io/mifos/portfolio/TestTaskInstances.java b/component-test/src/main/java/io/mifos/portfolio/TestTaskInstances.java
index 0fef315..5aa6e7a 100644
--- a/component-test/src/main/java/io/mifos/portfolio/TestTaskInstances.java
+++ b/component-test/src/main/java/io/mifos/portfolio/TestTaskInstances.java
@@ -32,6 +32,8 @@
 import org.junit.Assert;
 import org.junit.Test;
 
+import java.time.Clock;
+import java.time.LocalDateTime;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.HashSet;
@@ -204,6 +206,7 @@
         product.getIdentifier(),
         customerCase.getIdentifier(),
         Action.OPEN,
+        LocalDateTime.now(Clock.systemUTC()),
         Collections.singletonList(assignEntryToTeller()),
         IndividualLoanEventConstants.OPEN_INDIVIDUALLOAN_CASE,
         Case.State.PENDING);
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 e198db2..7107590 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
@@ -119,6 +119,16 @@
   }
 
   @JmsListener(
+      subscription = IndividualLoanEventConstants.DESTINATION,
+      destination = IndividualLoanEventConstants.DESTINATION,
+      selector = IndividualLoanEventConstants.SELECTOR_MARK_IN_ARREARS_INDIVIDUALLOAN_CASE
+  )
+  public void onMarkInArrears(@Header(TenantHeaderFilter.TENANT_HEADER) final String tenant,
+                         final String payload) {
+    this.eventRecorder.event(tenant, IndividualLoanEventConstants.MARK_IN_ARREARS_INDIVIDUALLOAN_CASE, payload, IndividualLoanCommandEvent.class);
+  }
+
+  @JmsListener(
           subscription = IndividualLoanEventConstants.DESTINATION,
           destination = IndividualLoanEventConstants.DESTINATION,
           selector = IndividualLoanEventConstants.SELECTOR_WRITE_OFF_INDIVIDUALLOAN_CASE
diff --git a/service/src/main/java/io/mifos/individuallending/IndividualLendingPatternFactory.java b/service/src/main/java/io/mifos/individuallending/IndividualLendingPatternFactory.java
index 59c2f47..bf37c9c 100644
--- a/service/src/main/java/io/mifos/individuallending/IndividualLendingPatternFactory.java
+++ b/service/src/main/java/io/mifos/individuallending/IndividualLendingPatternFactory.java
@@ -106,7 +106,7 @@
         AccountDesignators.GENERAL_LOSS_ALLOWANCE,
         AccountType.EXPENSE.name()));
     individualLendingRequiredAccounts.add(new RequiredAccountAssignment(
-        AccountDesignators.GENERAL_EXPENSE,
+        AccountDesignators.EXPENSE,
         AccountType.EXPENSE.name()));
     individualLendingRequiredAccounts.add(new RequiredAccountAssignment(
         AccountDesignators.ENTRY,
@@ -129,6 +129,7 @@
   private final AcceptPaymentBuilderService acceptPaymentBuilderService;
   private final ClosePaymentBuilderService closePaymentBuilderService;
   private final MarkLatePaymentBuilderService markLatePaymentBuilderService;
+  private final MarkInArrearsPaymentBuilderService  markInArrearsBuilderService;
   private final WriteOffPaymentBuilderService writeOffPaymentBuilderService;
   private final RecoverPaymentBuilderService recoverPaymentBuilderService;
   private final AccountingAdapter accountingAdapter;
@@ -148,7 +149,7 @@
       final AcceptPaymentBuilderService acceptPaymentBuilderService,
       final ClosePaymentBuilderService closePaymentBuilderService,
       final MarkLatePaymentBuilderService markLatePaymentBuilderService,
-      final WriteOffPaymentBuilderService writeOffPaymentBuilderService,
+      MarkInArrearsPaymentBuilderService markInArrearsBuilderService, final WriteOffPaymentBuilderService writeOffPaymentBuilderService,
       final RecoverPaymentBuilderService recoverPaymentBuilderService,
       AccountingAdapter accountingAdapter, final CustomerManager customerManager,
       final IndividualLendingCommandDispatcher individualLendingCommandDispatcher,
@@ -164,6 +165,7 @@
     this.acceptPaymentBuilderService = acceptPaymentBuilderService;
     this.closePaymentBuilderService = closePaymentBuilderService;
     this.markLatePaymentBuilderService = markLatePaymentBuilderService;
+    this.markInArrearsBuilderService = markInArrearsBuilderService;
     this.writeOffPaymentBuilderService = writeOffPaymentBuilderService;
     this.recoverPaymentBuilderService = recoverPaymentBuilderService;
     this.accountingAdapter = accountingAdapter;
@@ -329,6 +331,9 @@
       case MARK_LATE:
         paymentBuilderService = markLatePaymentBuilderService;
         break;
+      case MARK_IN_ARREARS:
+        paymentBuilderService = markInArrearsBuilderService;
+        break;
       case WRITE_OFF:
         paymentBuilderService = writeOffPaymentBuilderService;
         break;
@@ -368,7 +373,7 @@
       case APPROVED:
         return new HashSet<>(Arrays.asList(Action.DISBURSE, Action.CLOSE));
       case ACTIVE:
-        return new HashSet<>(Arrays.asList(Action.CLOSE, Action.ACCEPT_PAYMENT, Action.MARK_LATE, Action.APPLY_INTEREST, Action.DISBURSE, Action.WRITE_OFF));
+        return new HashSet<>(Arrays.asList(Action.CLOSE, Action.ACCEPT_PAYMENT, Action.MARK_LATE, Action.APPLY_INTEREST, Action.DISBURSE, Action.MARK_IN_ARREARS, Action.WRITE_OFF));
       case CLOSED:
         return Collections.emptySet();
       default:
diff --git a/service/src/main/java/io/mifos/individuallending/internal/command/MarkInArrearsCommand.java b/service/src/main/java/io/mifos/individuallending/internal/command/MarkInArrearsCommand.java
new file mode 100644
index 0000000..86dbf22
--- /dev/null
+++ b/service/src/main/java/io/mifos/individuallending/internal/command/MarkInArrearsCommand.java
@@ -0,0 +1,59 @@
+/*
+ * 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 MarkInArrearsCommand {
+  private final String productIdentifier;
+  private final String caseIdentifier;
+  private final String forTime;
+  private final long daysLate;
+
+  public MarkInArrearsCommand(String productIdentifier, String caseIdentifier, String forTime, long daysLate) {
+    this.productIdentifier = productIdentifier;
+    this.caseIdentifier = caseIdentifier;
+    this.forTime = forTime;
+    this.daysLate = daysLate;
+  }
+
+  public String getProductIdentifier() {
+    return productIdentifier;
+  }
+
+  public String getCaseIdentifier() {
+    return caseIdentifier;
+  }
+
+  public String getForTime() {
+    return forTime;
+  }
+
+  public long getDaysLate() {
+    return daysLate;
+  }
+
+  @Override
+  public String toString() {
+    return "MarkInArrearsCommand{" +
+        "productIdentifier='" + productIdentifier + '\'' +
+        ", caseIdentifier='" + caseIdentifier + '\'' +
+        ", forTime='" + forTime + '\'' +
+        ", daysLate=" + daysLate +
+        '}';
+  }
+}
\ 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 b476d47..ef3812d 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
@@ -29,9 +29,15 @@
 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.MarkInArrearsCommand;
 import io.mifos.individuallending.internal.command.MarkLateCommand;
-import io.mifos.individuallending.internal.service.*;
+import io.mifos.individuallending.internal.repository.LateCaseEntity;
+import io.mifos.individuallending.internal.repository.LateCaseRepository;
+import io.mifos.individuallending.internal.repository.LossProvisionStepEntity;
+import io.mifos.individuallending.internal.repository.LossProvisionStepRepository;
 import io.mifos.individuallending.internal.service.DataContextOfAction;
+import io.mifos.individuallending.internal.service.DataContextService;
+import io.mifos.individuallending.internal.service.costcomponent.RealRunningBalances;
 import io.mifos.individuallending.internal.service.schedule.Period;
 import io.mifos.individuallending.internal.service.schedule.ScheduledActionHelpers;
 import io.mifos.portfolio.api.v1.domain.Case;
@@ -53,7 +59,9 @@
 import org.springframework.transaction.annotation.Transactional;
 
 import java.math.BigDecimal;
+import java.time.LocalDate;
 import java.time.LocalDateTime;
+import java.time.temporal.ChronoUnit;
 import java.util.Collections;
 import java.util.List;
 import java.util.Optional;
@@ -73,6 +81,8 @@
   private final ApplicationName applicationName;
   private final CommandBus commandBus;
   private final AccountingAdapter accountingAdapter;
+  private final LateCaseRepository lateCaseRepository;
+  private final LossProvisionStepRepository lossProvisionStepRepository;
 
   @Autowired
   public BeatPublishCommandHandler(
@@ -82,7 +92,9 @@
       final DataContextService dataContextService,
       final ApplicationName applicationName,
       final CommandBus commandBus,
-      final AccountingAdapter accountingAdapter) {
+      final AccountingAdapter accountingAdapter,
+      final LateCaseRepository lateCaseRepository,
+      final LossProvisionStepRepository lossProvisionStepRepository) {
     this.caseRepository = caseRepository;
     this.caseCommandRepository = caseCommandRepository;
     this.portfolioProperties = portfolioProperties;
@@ -90,6 +102,8 @@
     this.applicationName = applicationName;
     this.commandBus = commandBus;
     this.accountingAdapter = accountingAdapter;
+    this.lateCaseRepository = lateCaseRepository;
+    this.lossProvisionStepRepository = lossProvisionStepRepository;
   }
 
   @Transactional
@@ -133,30 +147,26 @@
   public IndividualLoanCommandEvent process(final CheckLateCommand command) {
     final String productIdentifier = command.getProductIdentifier();
     final String caseIdentifier = command.getCaseIdentifier();
-    final LocalDateTime forTime = DateConverter.fromIsoString(command.getForTime());
+    final LocalDateTime forDateTime = DateConverter.fromIsoString(command.getForTime());
+    final LocalDate forDate = forDateTime.toLocalDate();
     final DataContextOfAction dataContextOfAction = dataContextService.checkedGetDataContext(
         productIdentifier, caseIdentifier, Collections.emptyList());
 
-    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 RealRunningBalances balances = new RealRunningBalances(accountingAdapter, dataContextOfAction);
 
-    final BigDecimal currentBalance = accountingAdapter.getCurrentAccountBalance(customerLoanPrincipalAccountIdentifier);
+    final BigDecimal currentBalance = balances.getAccountBalance(AccountDesignators.CUSTOMER_LOAN_PRINCIPAL);
     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(customerLoanPrincipalAccountIdentifier, dataContextOfAction.getMessageForCharge(Action.DISBURSE))
+    final LocalDateTime dateOfMostRecentDisbursement = dateOfMostRecentDisburse(dataContextOfAction.getCustomerCaseEntity().getId())
             .orElseThrow(() ->
                 ServiceException.badRequest("No last disbursal date for ''{0}.{1}'' could be determined.  " +
                     "Therefore it cannot be checked for lateness.", productIdentifier, caseIdentifier));
 
     final List<Period> repaymentPeriods = ScheduledActionHelpers.generateRepaymentPeriods(
         dateOfMostRecentDisbursement.toLocalDate(),
-        forTime.toLocalDate(),
+        forDate,
         dataContextOfAction.getCaseParameters())
         .collect(Collectors.toList());
 
@@ -167,37 +177,59 @@
         .getPaymentSize()
         .multiply(BigDecimal.valueOf(repaymentPeriodsBetweenBeginningAndToday));
 
-    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));
+    final BigDecimal principalPaymentSum = balances.getSumOfChargesForActionSinceDate(
+        AccountDesignators.CUSTOMER_LOAN_PRINCIPAL,
+        Action.ACCEPT_PAYMENT,
+        dateOfMostRecentDisbursement);
+    final BigDecimal interestPaymentSum = balances.getSumOfChargesForActionSinceDate(
+        AccountDesignators.CUSTOMER_LOAN_INTEREST,
+        Action.ACCEPT_PAYMENT,
+        dateOfMostRecentDisbursement);
+    final BigDecimal feesPaymentSum = balances.getSumOfChargesForActionSinceDate(
+        AccountDesignators.CUSTOMER_LOAN_FEES,
+        Action.ACCEPT_PAYMENT,
+        dateOfMostRecentDisbursement);
+    final BigDecimal lateFeesSum = balances.getSumOfChargesForActionSinceDate(
+        AccountDesignators.LATE_FEE_INCOME,
+        Action.ACCEPT_PAYMENT,
+        dateOfMostRecentDisbursement);
+    final BigDecimal paymentsSum = principalPaymentSum.add(interestPaymentSum).add(feesPaymentSum.subtract(lateFeesSum));
 
     if (paymentsSum.compareTo(expectedPaymentSum) < 0) {
-      final Optional<LocalDateTime> dateOfMostRecentLateFee = dateOfMostRecentMarkLate(dataContextOfAction.getCustomerCaseEntity().getId());
-      if (!dateOfMostRecentLateFee.isPresent() ||
-          mostRecentLateFeeIsBeforeMostRecentRepaymentPeriod(repaymentPeriods, dateOfMostRecentLateFee.get())) {
+      final Optional<LocalDateTime> dateLateSince = dateLateSince(dataContextOfAction.getCustomerCaseEntity().getId());
+      if (!dateLateSince.isPresent()) {
         commandBus.dispatch(new MarkLateCommand(productIdentifier, caseIdentifier, command.getForTime()));
       }
+
+      if (dateLateSince.isPresent()) {
+        int daysLate;
+        try {
+          daysLate = Math.toIntExact(dateLateSince.get().until(forDateTime, ChronoUnit.DAYS)) + 1;
+        }
+        catch (ArithmeticException e) {
+          daysLate = -1;
+        }
+        if (daysLate > 1) {
+          final Optional<LossProvisionStepEntity> lossStepEntity = lossProvisionStepRepository.findByProductIdAndDaysLate(dataContextOfAction.getProductEntity().getId(), daysLate);
+          if (lossStepEntity.isPresent()) {
+            commandBus.dispatch(new MarkInArrearsCommand(productIdentifier, caseIdentifier, command.getForTime(), daysLate));
+          }
+        }
+      }
     }
 
     return new IndividualLoanCommandEvent(productIdentifier, caseIdentifier, command.getForTime());
   }
 
-  private Optional<LocalDateTime> dateOfMostRecentMarkLate(final Long caseId) {
+  private Optional<LocalDateTime> dateLateSince(final Long caseId) {
+    return lateCaseRepository.findByCaseId(caseId).map(LateCaseEntity::getLateSince);
+  }
+
+  private Optional<LocalDateTime> dateOfMostRecentDisburse(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(),
+        Action.DISBURSE.name(),
         pageRequest);
 
     return page.getContent().stream().findFirst().map(CaseCommandEntity::getCreatedOn);
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 23f313f..a12b605 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
@@ -30,6 +30,8 @@
 import io.mifos.individuallending.api.v1.events.IndividualLoanEventConstants;
 import io.mifos.individuallending.internal.command.*;
 import io.mifos.individuallending.internal.repository.CaseParametersRepository;
+import io.mifos.individuallending.internal.repository.LateCaseEntity;
+import io.mifos.individuallending.internal.repository.LateCaseRepository;
 import io.mifos.individuallending.internal.service.DataContextOfAction;
 import io.mifos.individuallending.internal.service.DataContextService;
 import io.mifos.individuallending.internal.service.DesignatorToAccountIdentifierMapper;
@@ -46,8 +48,6 @@
 
 import javax.annotation.Nullable;
 import java.math.BigDecimal;
-import java.time.Clock;
-import java.time.LocalDate;
 import java.time.LocalDateTime;
 import java.time.LocalTime;
 import java.util.Collections;
@@ -72,12 +72,14 @@
   private final AcceptPaymentBuilderService acceptPaymentBuilderService;
   private final ClosePaymentBuilderService closePaymentBuilderService;
   private final MarkLatePaymentBuilderService markLatePaymentBuilderService;
+  private final MarkInArrearsPaymentBuilderService markInArrearsPaymentBuilderService;
   private final WriteOffPaymentBuilderService writeOffPaymentBuilderService;
   private final RecoverPaymentBuilderService recoverPaymentBuilderService;
   private final AccountingAdapter accountingAdapter;
   private final CaseCommandRepository caseCommandRepository;
   private final TaskInstanceRepository taskInstanceRepository;
   private final CaseParametersRepository caseParametersRepository;
+  private final LateCaseRepository lateCaseRepository;
 
   @Autowired
   public IndividualLoanCommandHandler(
@@ -91,11 +93,14 @@
       final AcceptPaymentBuilderService acceptPaymentBuilderService,
       final ClosePaymentBuilderService closePaymentBuilderService,
       final MarkLatePaymentBuilderService markLatePaymentBuilderService,
+      final MarkInArrearsPaymentBuilderService markInArrearsPaymentBuilderService,
       final WriteOffPaymentBuilderService writeOffPaymentBuilderService,
       final RecoverPaymentBuilderService recoverPaymentBuilderService,
       final AccountingAdapter accountingAdapter,
-      CaseCommandRepository caseCommandRepository, final TaskInstanceRepository taskInstanceRepository,
-      final CaseParametersRepository caseParametersRepository) {
+      final CaseCommandRepository caseCommandRepository,
+      final TaskInstanceRepository taskInstanceRepository,
+      final CaseParametersRepository caseParametersRepository,
+      final LateCaseRepository lateCaseRepository) {
     this.caseRepository = caseRepository;
     this.dataContextService = dataContextService;
     this.openPaymentBuilderService = openPaymentBuilderService;
@@ -106,12 +111,14 @@
     this.acceptPaymentBuilderService = acceptPaymentBuilderService;
     this.closePaymentBuilderService = closePaymentBuilderService;
     this.markLatePaymentBuilderService = markLatePaymentBuilderService;
+    this.markInArrearsPaymentBuilderService = markInArrearsPaymentBuilderService;
     this.writeOffPaymentBuilderService = writeOffPaymentBuilderService;
     this.recoverPaymentBuilderService = recoverPaymentBuilderService;
     this.accountingAdapter = accountingAdapter;
     this.caseCommandRepository = caseCommandRepository;
     this.taskInstanceRepository = taskInstanceRepository;
     this.caseParametersRepository = caseParametersRepository;
+    this.lateCaseRepository = lateCaseRepository;
   }
 
   @Transactional
@@ -137,7 +144,6 @@
     final PaymentBuilder paymentBuilder
         = openPaymentBuilderService.getPaymentBuilder(dataContextOfAction, BigDecimal.ZERO, CostComponentService.today(), runningBalances);
 
-    final LocalDateTime today = today();
 
     final Optional<String> transactionUniqueifier = accountingAdapter.bookCharges(paymentBuilder.getBalanceAdjustments(),
         designatorToAccountIdentifierMapper,
@@ -158,7 +164,7 @@
     customerCase.setCurrentState(Case.State.PENDING.name());
     caseRepository.save(customerCase);
 
-    return new IndividualLoanCommandEvent(productIdentifier, caseIdentifier, DateConverter.toIsoString(today));
+    return new IndividualLoanCommandEvent(productIdentifier, caseIdentifier, command.getCommand().getCreatedOn());
   }
 
   @Transactional
@@ -184,7 +190,6 @@
     final PaymentBuilder paymentBuilder
         = denyPaymentBuilderService.getPaymentBuilder(dataContextOfAction, BigDecimal.ZERO, CostComponentService.today(), runningBalances);
 
-    final LocalDateTime today = today();
 
     final Optional<String> transactionUniqueifier = accountingAdapter.bookCharges(paymentBuilder.getBalanceAdjustments(),
         designatorToAccountIdentifierMapper,
@@ -204,7 +209,7 @@
     customerCase.setCurrentState(Case.State.CLOSED.name());
     caseRepository.save(customerCase);
 
-    return new IndividualLoanCommandEvent(productIdentifier, caseIdentifier, DateConverter.toIsoString(today));
+    return new IndividualLoanCommandEvent(productIdentifier, caseIdentifier, command.getCommand().getCreatedOn());
   }
 
   static class InterruptedInALambdaException extends RuntimeException {
@@ -279,8 +284,6 @@
     final PaymentBuilder paymentBuilder =
         approvePaymentBuilderService.getPaymentBuilder(dataContextOfAction, BigDecimal.ZERO, CostComponentService.today(), runningBalances);
 
-    final LocalDateTime today = today();
-
     final Optional<String> transactionUniqueifier = accountingAdapter.bookCharges(paymentBuilder.getBalanceAdjustments(),
         designatorToAccountIdentifierMapper,
         command.getCommand().getNote(),
@@ -300,7 +303,7 @@
     customerCase.setCurrentState(Case.State.APPROVED.name());
     caseRepository.save(customerCase);
 
-    return new IndividualLoanCommandEvent(productIdentifier, caseIdentifier, DateConverter.toIsoString(today));
+    return new IndividualLoanCommandEvent(productIdentifier, caseIdentifier, command.getCommand().getCreatedOn());
   }
 
   @Transactional
@@ -326,8 +329,6 @@
     final PaymentBuilder paymentBuilder =
         disbursePaymentBuilderService.getPaymentBuilder(dataContextOfAction, disbursalAmount, CostComponentService.today(), runningBalances);
 
-    final LocalDateTime today = today();
-
     final Optional<String> transactionUniqueifier = accountingAdapter.bookCharges(paymentBuilder.getBalanceAdjustments(),
         designatorToAccountIdentifierMapper,
         command.getCommand().getNote(),
@@ -346,7 +347,7 @@
     //Only move to new state if book charges command was accepted.
     if (Case.State.valueOf(dataContextOfAction.getCustomerCaseEntity().getCurrentState()) != Case.State.ACTIVE) {
       final LocalDateTime endOfTerm
-          = ScheduledActionHelpers.getRoughEndDate(today.toLocalDate(), dataContextOfAction.getCaseParameters())
+          = ScheduledActionHelpers.getRoughEndDate(DateConverter.fromIsoString(command.getCommand().getCreatedOn()).toLocalDate(), dataContextOfAction.getCaseParameters())
           .atTime(LocalTime.MIDNIGHT);
       customerCase.setEndOfTerm(endOfTerm);
       customerCase.setCurrentState(Case.State.ACTIVE.name());
@@ -361,7 +362,7 @@
     dataContextOfAction.getCaseParametersEntity().setPaymentSize(newLoanPaymentSize);
     caseParametersRepository.save(dataContextOfAction.getCaseParametersEntity());
 
-    return new IndividualLoanCommandEvent(productIdentifier, caseIdentifier, DateConverter.toIsoString(today));
+    return new IndividualLoanCommandEvent(productIdentifier, caseIdentifier, command.getCommand().getCreatedOn());
   }
 
   @Transactional
@@ -438,8 +439,6 @@
             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(),
@@ -455,7 +454,10 @@
         Action.ACCEPT_PAYMENT,
         transactionUniqueifier);
 
-    return new IndividualLoanCommandEvent(productIdentifier, caseIdentifier, DateConverter.toIsoString(today));
+    //TODO: Should this be more sophisticated?  Take into account what the payment amount was?
+    markCaseNotLate(dataContextOfAction);
+
+    return new IndividualLoanCommandEvent(productIdentifier, caseIdentifier, command.getCommand().getCreatedOn());
   }
 
   @Transactional
@@ -486,8 +488,6 @@
         markLatePaymentBuilderService.getPaymentBuilder(dataContextOfAction, BigDecimal.ZERO, DateConverter.fromIsoString(command.getForTime()).toLocalDate(),
             runningBalances);
 
-    final LocalDateTime today = today();
-
     final Optional<String> transactionUniqueifier = accountingAdapter.bookCharges(paymentBuilder.getBalanceAdjustments(),
         designatorToAccountIdentifierMapper,
         "Marked late on " + command.getForTime(),
@@ -503,7 +503,58 @@
         Action.MARK_LATE,
         transactionUniqueifier);
 
-    return new IndividualLoanCommandEvent(productIdentifier, caseIdentifier, DateConverter.toIsoString(today));
+    markCaseLate(dataContextOfAction, command.getForTime());
+
+    return new IndividualLoanCommandEvent(productIdentifier, caseIdentifier, command.getForTime());
+  }
+
+  @Transactional
+  @CommandHandler(logStart = CommandLogLevel.INFO, logFinish = CommandLogLevel.INFO)
+  @EventEmitter(
+      selectorName = IndividualLoanEventConstants.SELECTOR_NAME,
+      selectorValue = IndividualLoanEventConstants.MARK_IN_ARREARS_INDIVIDUALLOAN_CASE)
+  public IndividualLoanCommandEvent process(final MarkInArrearsCommand 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_IN_ARREARS);
+
+    if (dataContextOfAction.getCustomerCaseEntity().getEndOfTerm() == null)
+      throw ServiceException.internalError(
+          "End of term not set for active case ''{0}.{1}.''", productIdentifier, caseIdentifier);
+
+    final DesignatorToAccountIdentifierMapper designatorToAccountIdentifierMapper
+        = new DesignatorToAccountIdentifierMapper(dataContextOfAction);
+    final RealRunningBalances runningBalances = new RealRunningBalances(
+        accountingAdapter,
+        dataContextOfAction);
+
+    final PaymentBuilder paymentBuilder =
+        markInArrearsPaymentBuilderService.getPaymentBuilder(
+            dataContextOfAction,
+            BigDecimal.valueOf(command.getDaysLate()),
+            DateConverter.fromIsoString(command.getForTime()).toLocalDate(),
+            runningBalances);
+
+    final Optional<String> transactionUniqueifier = accountingAdapter.bookCharges(paymentBuilder.getBalanceAdjustments(),
+        designatorToAccountIdentifierMapper,
+        "Marked in arrears on " + command.getForTime(),
+        command.getForTime(),
+        dataContextOfAction.getMessageForCharge(Action.MARK_IN_ARREARS),
+        Action.MARK_IN_ARREARS.getTransactionType());
+
+    final CaseEntity customerCase = dataContextOfAction.getCustomerCaseEntity();
+
+    recordCommand(
+        command.getForTime(),
+        customerCase.getId(),
+        Action.MARK_IN_ARREARS,
+        transactionUniqueifier);
+
+    return new IndividualLoanCommandEvent(productIdentifier, caseIdentifier, command.getForTime());
   }
 
   @Transactional
@@ -529,8 +580,6 @@
             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(),
@@ -549,7 +598,7 @@
     customerCase.setCurrentState(Case.State.CLOSED.name());
     caseRepository.save(customerCase);
 
-    return new IndividualLoanCommandEvent(productIdentifier, caseIdentifier, DateConverter.toIsoString(today));
+    return new IndividualLoanCommandEvent(productIdentifier, caseIdentifier, command.getCommand().getCreatedOn());
   }
 
   @Transactional
@@ -573,8 +622,6 @@
     final PaymentBuilder paymentBuilder =
         closePaymentBuilderService.getPaymentBuilder(dataContextOfAction, BigDecimal.ZERO, CostComponentService.today(), runningBalances);
 
-    final LocalDateTime today = today();
-
     final Optional<String> transactionIdentifier = accountingAdapter.bookCharges(paymentBuilder.getBalanceAdjustments(),
         designatorToAccountIdentifierMapper,
         command.getCommand().getNote(),
@@ -586,7 +633,7 @@
     customerCase.setCurrentState(Case.State.CLOSED.name());
     caseRepository.save(customerCase);
 
-    return new IndividualLoanCommandEvent(productIdentifier, caseIdentifier, DateConverter.toIsoString(today));
+    return new IndividualLoanCommandEvent(productIdentifier, caseIdentifier, command.getCommand().getCreatedOn());
   }
 
   @Transactional
@@ -610,8 +657,6 @@
     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(),
@@ -630,7 +675,7 @@
     customerCase.setCurrentState(Case.State.CLOSED.name());
     caseRepository.save(customerCase);
 
-    return new IndividualLoanCommandEvent(productIdentifier, caseIdentifier, DateConverter.toIsoString(today));
+    return new IndividualLoanCommandEvent(productIdentifier, caseIdentifier, command.getCommand().getCreatedOn());
   }
 
   private Map<String, BigDecimal> getRequestedChargeAmounts(final @Nullable List<CostComponent> costComponents) {
@@ -669,7 +714,20 @@
     caseCommandRepository.save(caseCommandEntity);
   }
 
-  private static LocalDateTime today() {
-    return LocalDate.now(Clock.systemUTC()).atStartOfDay();
+  private void markCaseLate(
+      final DataContextOfAction dataContextOfAction,
+      final String forTime) {
+    final Optional<LateCaseEntity> lateCaseEntity = lateCaseRepository.findByCaseId(dataContextOfAction.getCustomerCaseEntity().getId());
+    if (!lateCaseEntity.isPresent()) {
+      final LateCaseEntity markCaseLate = new LateCaseEntity();
+      markCaseLate.setCaseId(dataContextOfAction.getCustomerCaseEntity().getId());
+      markCaseLate.setLateSince(DateConverter.fromIsoString(forTime));
+      lateCaseRepository.save(markCaseLate);
+    }
+  }
+
+  private void markCaseNotLate(
+      final DataContextOfAction dataContextOfAction) {
+    lateCaseRepository.deleteByCaseId(dataContextOfAction.getCustomerCaseEntity().getId());
   }
 }
\ No newline at end of file
diff --git a/service/src/main/java/io/mifos/individuallending/internal/repository/LateCaseEntity.java b/service/src/main/java/io/mifos/individuallending/internal/repository/LateCaseEntity.java
new file mode 100644
index 0000000..8fed47a
--- /dev/null
+++ b/service/src/main/java/io/mifos/individuallending/internal/repository/LateCaseEntity.java
@@ -0,0 +1,80 @@
+/*
+ * 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.repository;
+
+import io.mifos.core.mariadb.util.LocalDateTimeConverter;
+
+import javax.persistence.*;
+import java.time.LocalDateTime;
+import java.util.Objects;
+
+/**
+ * @author Myrle Krantz
+ */
+@Entity
+@Table(name = "bastet_il_late_cases")
+public class LateCaseEntity {
+  @Id
+  @GeneratedValue(strategy = GenerationType.IDENTITY)
+  @Column(name = "id")
+  private Long id;
+
+  @Column(name = "case_id")
+  private Long caseId;
+
+  /** The date after the most recent payment due date at the time at which lateness was determined.
+   */
+  @Column(name = "late_since")
+  @Convert(converter = LocalDateTimeConverter.class)
+  private LocalDateTime lateSince;
+
+  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 LocalDateTime getLateSince() {
+    return lateSince;
+  }
+
+  public void setLateSince(LocalDateTime lateSince) {
+    this.lateSince = lateSince;
+  }
+
+  @Override
+  public boolean equals(Object o) {
+    if (this == o) return true;
+    if (o == null || getClass() != o.getClass()) return false;
+    LateCaseEntity that = (LateCaseEntity) o;
+    return Objects.equals(caseId, that.caseId);
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hash(caseId);
+  }
+}
diff --git a/service/src/main/java/io/mifos/individuallending/internal/repository/LateCaseRepository.java b/service/src/main/java/io/mifos/individuallending/internal/repository/LateCaseRepository.java
new file mode 100644
index 0000000..028cec6
--- /dev/null
+++ b/service/src/main/java/io/mifos/individuallending/internal/repository/LateCaseRepository.java
@@ -0,0 +1,30 @@
+/*
+ * 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.repository;
+
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.stereotype.Repository;
+
+import java.util.Optional;
+
+/**
+ * @author Myrle Krantz
+ */
+@Repository
+public interface LateCaseRepository extends JpaRepository<LateCaseEntity, Long> {
+  Optional<LateCaseEntity> findByCaseId(Long caseId);
+  void deleteByCaseId(Long caseId);
+}
diff --git a/service/src/main/java/io/mifos/individuallending/internal/service/LossProvisionStepService.java b/service/src/main/java/io/mifos/individuallending/internal/service/LossProvisionStepService.java
index 258f209..80d4dc9 100644
--- a/service/src/main/java/io/mifos/individuallending/internal/service/LossProvisionStepService.java
+++ b/service/src/main/java/io/mifos/individuallending/internal/service/LossProvisionStepService.java
@@ -23,8 +23,6 @@
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 
-import java.math.BigDecimal;
-import java.util.Arrays;
 import java.util.List;
 import java.util.Optional;
 import java.util.stream.Collectors;
@@ -34,12 +32,6 @@
  */
 @Service
 public class LossProvisionStepService {
-  private final static List<LossProvisionStep> DEFAULT_LOSS_PROVISION_STEPS = Arrays.asList(
-      new LossProvisionStep(0, BigDecimal.ONE),
-      new LossProvisionStep(1, BigDecimal.valueOf(9)),
-      new LossProvisionStep(30, BigDecimal.valueOf(30)),
-      new LossProvisionStep(60, BigDecimal.valueOf(60)));
-
   private final ProductRepository productRepository;
   private final LossProvisionStepRepository lossProvisionStepRepository;
 
@@ -62,12 +54,8 @@
     final Long productId = productRepository.findByIdentifier(productIdentifier)
         .orElseThrow(() -> ServiceException.notFound("Product ''{}'' doesn''t exist.", productIdentifier))
         .getId();
-    final List<LossProvisionStep> ret = lossProvisionStepRepository.findByProductIdOrderByDaysLateAsc(productId)
+    return lossProvisionStepRepository.findByProductIdOrderByDaysLateAsc(productId)
         .map(LossProvisionStepMapper::map)
         .collect(Collectors.toList());
-    if (!ret.isEmpty())
-      return ret;
-    else
-      return DEFAULT_LOSS_PROVISION_STEPS;
   }
 }
\ No newline at end of file
diff --git a/service/src/main/java/io/mifos/individuallending/internal/service/costcomponent/MarkInArrearsPaymentBuilderService.java b/service/src/main/java/io/mifos/individuallending/internal/service/costcomponent/MarkInArrearsPaymentBuilderService.java
new file mode 100644
index 0000000..fe66e9e
--- /dev/null
+++ b/service/src/main/java/io/mifos/individuallending/internal/service/costcomponent/MarkInArrearsPaymentBuilderService.java
@@ -0,0 +1,71 @@
+/*
+ * 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.costcomponent;
+
+import io.mifos.individuallending.internal.repository.CaseParametersEntity;
+import io.mifos.individuallending.internal.service.DataContextOfAction;
+import io.mifos.individuallending.internal.service.schedule.LossProvisionChargesService;
+import io.mifos.individuallending.internal.service.schedule.ScheduledCharge;
+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 MarkInArrearsPaymentBuilderService implements PaymentBuilderService {
+  private final LossProvisionChargesService lossProvisionChargesService;
+
+  public MarkInArrearsPaymentBuilderService(
+      final LossProvisionChargesService lossProvisionChargesService) {
+    this.lossProvisionChargesService = lossProvisionChargesService;
+  }
+
+  @Override
+  public PaymentBuilder getPaymentBuilder(
+      final @Nonnull DataContextOfAction dataContextOfAction,
+      final @Nullable BigDecimal bigDecimalDaysLate,
+      final LocalDate forDate,
+      final RunningBalances runningBalances)
+  {
+    final CaseParametersEntity caseParameters = dataContextOfAction.getCaseParametersEntity();
+    final int minorCurrencyUnitDigits = dataContextOfAction.getProductEntity().getMinorCurrencyUnitDigits();
+
+    final BigDecimal loanPaymentSize = dataContextOfAction.getCaseParametersEntity().getPaymentSize();
+
+    int daysLate = bigDecimalDaysLate == null ? 0 : bigDecimalDaysLate.intValueExact();
+
+    final List<ScheduledCharge> scheduledCharges = lossProvisionChargesService.getScheduledChargeForMarkInArrears(
+        dataContextOfAction, forDate, daysLate).map(Collections::singletonList).orElse(Collections.emptyList());
+
+    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/MarkLatePaymentBuilderService.java b/service/src/main/java/io/mifos/individuallending/internal/service/costcomponent/MarkLatePaymentBuilderService.java
index c02e10e..5406d2c 100644
--- a/service/src/main/java/io/mifos/individuallending/internal/service/costcomponent/MarkLatePaymentBuilderService.java
+++ b/service/src/main/java/io/mifos/individuallending/internal/service/costcomponent/MarkLatePaymentBuilderService.java
@@ -18,6 +18,7 @@
 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.LossProvisionChargesService;
 import io.mifos.individuallending.internal.service.schedule.ScheduledAction;
 import io.mifos.individuallending.internal.service.schedule.ScheduledCharge;
 import io.mifos.individuallending.internal.service.schedule.ScheduledChargesService;
@@ -30,6 +31,7 @@
 import java.time.LocalDate;
 import java.util.Collections;
 import java.util.List;
+import java.util.Optional;
 
 /**
  * @author Myrle Krantz
@@ -37,10 +39,14 @@
 @Service
 public class MarkLatePaymentBuilderService implements PaymentBuilderService {
   private final ScheduledChargesService scheduledChargesService;
+  private final LossProvisionChargesService lossProvisionChargesService;
 
   @Autowired
-  public MarkLatePaymentBuilderService(final ScheduledChargesService scheduledChargesService) {
+  public MarkLatePaymentBuilderService(
+      final ScheduledChargesService scheduledChargesService,
+      final LossProvisionChargesService lossProvisionChargesService) {
     this.scheduledChargesService = scheduledChargesService;
+    this.lossProvisionChargesService = lossProvisionChargesService;
   }
 
   @Override
@@ -57,12 +63,15 @@
 
     final BigDecimal loanPaymentSize = dataContextOfAction.getCaseParametersEntity().getPaymentSize();
 
-    final List<ScheduledCharge> scheduledChargesForThisAction = scheduledChargesService.getScheduledCharges(
+    final List<ScheduledCharge> scheduledCharges = scheduledChargesService.getScheduledCharges(
         productIdentifier,
         Collections.singletonList(scheduledAction));
+    final Optional<ScheduledCharge> initialLossProvisionCharge = lossProvisionChargesService.getScheduledChargeForMarkLate(
+        dataContextOfAction, forDate);
+    initialLossProvisionCharge.ifPresent(scheduledCharges::add);
 
     return CostComponentService.getCostComponentsForScheduledCharges(
-        scheduledChargesForThisAction,
+        scheduledCharges,
         caseParameters.getBalanceRangeMaximum(),
         runningBalances,
         loanPaymentSize,
diff --git a/service/src/main/java/io/mifos/individuallending/internal/service/costcomponent/PaymentBuilder.java b/service/src/main/java/io/mifos/individuallending/internal/service/costcomponent/PaymentBuilder.java
index 4c2d646..da18f03 100644
--- a/service/src/main/java/io/mifos/individuallending/internal/service/costcomponent/PaymentBuilder.java
+++ b/service/src/main/java/io/mifos/individuallending/internal/service/costcomponent/PaymentBuilder.java
@@ -19,6 +19,7 @@
 import io.mifos.core.lang.DateConverter;
 import io.mifos.individuallending.IndividualLendingPatternFactory;
 import io.mifos.individuallending.api.v1.domain.caseinstance.PlannedPayment;
+import io.mifos.individuallending.api.v1.domain.product.AccountDesignators;
 import io.mifos.individuallending.api.v1.domain.workflow.Action;
 import io.mifos.portfolio.api.v1.domain.ChargeDefinition;
 import io.mifos.portfolio.api.v1.domain.CostComponent;
@@ -154,18 +155,20 @@
       final BigDecimal plannedCharge) {
     final BigDecimal expectedImpactOnDebitAccount = plannedCharge.subtract(this.getBalanceAdjustment(fromAccountDesignator));
     final BigDecimal maxImpactOnDebitAccount = prePaymentBalances.getMaxDebit(fromAccountDesignator, expectedImpactOnDebitAccount);
-    final BigDecimal maxDebit = maxImpactOnDebitAccount.add(this.getBalanceAdjustment(fromAccountDesignator))
-        .max(BigDecimal.ZERO);
+    final BigDecimal maxDebit = (!fromAccountDesignator.equals(AccountDesignators.PRODUCT_LOSS_ALLOWANCE)) ?
+        maxImpactOnDebitAccount.add(this.getBalanceAdjustment(fromAccountDesignator)).max(BigDecimal.ZERO) :
+        maxImpactOnDebitAccount.add(this.getBalanceAdjustment(fromAccountDesignator));
 
     final BigDecimal expectedImpactOnCreditAccount = plannedCharge.add(this.getBalanceAdjustment(toAccountDesignator));
     final BigDecimal maxImpactOnCreditAccount = prePaymentBalances.getMaxCredit(toAccountDesignator, expectedImpactOnCreditAccount);
-    final BigDecimal maxCredit = maxImpactOnCreditAccount.subtract(this.getBalanceAdjustment(toAccountDesignator))
-        .max(BigDecimal.ZERO);
+    final BigDecimal maxCredit = (!toAccountDesignator.equals(AccountDesignators.GENERAL_LOSS_ALLOWANCE)) ?
+        maxImpactOnCreditAccount.subtract(this.getBalanceAdjustment(toAccountDesignator)).max(BigDecimal.ZERO) :
+        maxImpactOnCreditAccount.subtract(this.getBalanceAdjustment(toAccountDesignator));
     return maxCredit.min(maxDebit);
   }
 
   private static boolean chargeIsAccrued(final ChargeDefinition chargeDefinition) {
-    return chargeDefinition.getAccrualAccountDesignator() != null;
+    return chargeDefinition.getAccrualAccountDesignator() != null && chargeDefinition.getAccrueAction() != null;
   }
 
   private void addToBalance(
diff --git a/service/src/main/java/io/mifos/individuallending/internal/service/costcomponent/PaymentBuilderService.java b/service/src/main/java/io/mifos/individuallending/internal/service/costcomponent/PaymentBuilderService.java
index 913982c..17350f5 100644
--- a/service/src/main/java/io/mifos/individuallending/internal/service/costcomponent/PaymentBuilderService.java
+++ b/service/src/main/java/io/mifos/individuallending/internal/service/costcomponent/PaymentBuilderService.java
@@ -21,6 +21,9 @@
 import java.math.BigDecimal;
 import java.time.LocalDate;
 
+/**
+ * @author Myrle Krantz
+ */
 public interface PaymentBuilderService {
 
   PaymentBuilder getPaymentBuilder(
diff --git a/service/src/main/java/io/mifos/individuallending/internal/service/costcomponent/RealRunningBalances.java b/service/src/main/java/io/mifos/individuallending/internal/service/costcomponent/RealRunningBalances.java
index 40b5095..2821285 100644
--- a/service/src/main/java/io/mifos/individuallending/internal/service/costcomponent/RealRunningBalances.java
+++ b/service/src/main/java/io/mifos/individuallending/internal/service/costcomponent/RealRunningBalances.java
@@ -90,14 +90,26 @@
 
   @Override
   public Optional<LocalDateTime> getStartOfTerm(final DataContextOfAction dataContextOfAction) {
-     if (!startOfTerm.isPresent()) {
-       final String customerLoanPrincipalAccountIdentifier = designatorToAccountIdentifierMapper.mapOrThrow(AccountDesignators.CUSTOMER_LOAN_PRINCIPAL);
+    if (!startOfTerm.isPresent()) {
+      final String customerLoanPrincipalAccountIdentifier = designatorToAccountIdentifierMapper.mapOrThrow(AccountDesignators.CUSTOMER_LOAN_PRINCIPAL);
 
-       this.startOfTerm = accountingAdapter.getDateOfOldestEntryContainingMessage(
-           customerLoanPrincipalAccountIdentifier,
-           dataContextOfAction.getMessageForCharge(Action.DISBURSE));
-     }
+      this.startOfTerm = accountingAdapter.getDateOfOldestEntryContainingMessage(
+          customerLoanPrincipalAccountIdentifier,
+          dataContextOfAction.getMessageForCharge(Action.DISBURSE));
+    }
 
     return this.startOfTerm;
   }
+
+  public BigDecimal getSumOfChargesForActionSinceDate(
+      final String accountDesignator,
+      final Action action,
+      final LocalDateTime since) {
+    final String accountIdentifier = designatorToAccountIdentifierMapper.mapOrThrow(accountDesignator);
+    return accountingAdapter.sumMatchingEntriesSinceDate(
+        accountIdentifier,
+        since.toLocalDate(),
+        dataContextOfAction.getMessageForCharge(action));
+
+  }
 }
diff --git a/service/src/main/java/io/mifos/individuallending/internal/service/costcomponent/RunningBalances.java b/service/src/main/java/io/mifos/individuallending/internal/service/costcomponent/RunningBalances.java
index 31f325f..580293f 100644
--- a/service/src/main/java/io/mifos/individuallending/internal/service/costcomponent/RunningBalances.java
+++ b/service/src/main/java/io/mifos/individuallending/internal/service/costcomponent/RunningBalances.java
@@ -50,7 +50,7 @@
     this.put(AccountDesignators.LATE_FEE_ACCRUAL, positive);
     this.put(AccountDesignators.PRODUCT_LOSS_ALLOWANCE, negative);
     this.put(AccountDesignators.GENERAL_LOSS_ALLOWANCE, negative);
-    this.put(AccountDesignators.GENERAL_EXPENSE, negative);
+    this.put(AccountDesignators.EXPENSE, negative);
     this.put(AccountDesignators.ENTRY, positive);
     //TODO: derive signs from IndividualLendingPatternFactory.individualLendingRequiredAccounts instead.
   }};
@@ -87,7 +87,8 @@
   }
 
   default BigDecimal getMaxDebit(final String accountDesignator, final BigDecimal amount) {
-    if (accountDesignator.equals(AccountDesignators.ENTRY))
+    if (accountDesignator.equals(AccountDesignators.ENTRY) ||
+        accountDesignator.equals(AccountDesignators.PRODUCT_LOSS_ALLOWANCE))
       return amount;
 
     if (ACCOUNT_SIGNS.get(accountDesignator).signum() == -1)
@@ -97,8 +98,12 @@
   }
 
   default BigDecimal getMaxCredit(final String accountDesignator, final BigDecimal amount) {
-    if (accountDesignator.equals(AccountDesignators.ENTRY))
-      return amount; //don't guard the entry account.
+    if (accountDesignator.equals(AccountDesignators.ENTRY) ||
+        accountDesignator.equals(AccountDesignators.EXPENSE) ||
+        accountDesignator.equals(AccountDesignators.PRODUCT_LOSS_ALLOWANCE))
+      return amount;
+    //entry account can achieve a "relative" negative balance, and
+    // product loss allowance can achieve an "absolute" negative balance.
 
     if (ACCOUNT_SIGNS.get(accountDesignator).signum() != -1)
       return amount;
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 3dab27b..a9a6588 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,12 +15,15 @@
  */
 package io.mifos.individuallending.internal.service.costcomponent;
 
+import io.mifos.individuallending.api.v1.domain.product.AccountDesignators;
+import io.mifos.individuallending.api.v1.domain.product.ChargeProportionalDesignator;
 import io.mifos.individuallending.api.v1.domain.workflow.Action;
 import io.mifos.individuallending.internal.repository.CaseParametersEntity;
+import io.mifos.individuallending.internal.service.ChargeDefinitionService;
 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 io.mifos.portfolio.api.v1.domain.ChargeDefinition;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 
@@ -28,19 +31,23 @@
 import javax.annotation.Nullable;
 import java.math.BigDecimal;
 import java.time.LocalDate;
-import java.util.Collections;
-import java.util.List;
+import java.util.*;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import static io.mifos.individuallending.api.v1.domain.product.ChargeIdentifiers.*;
 
 /**
  * @author Myrle Krantz
  */
 @Service
 public class WriteOffPaymentBuilderService implements PaymentBuilderService {
-  private final ScheduledChargesService scheduledChargesService;
+  final private ChargeDefinitionService chargeDefinitionService;
 
   @Autowired
-  public WriteOffPaymentBuilderService(final ScheduledChargesService scheduledChargesService) {
-    this.scheduledChargesService = scheduledChargesService;
+  public WriteOffPaymentBuilderService(
+      final ChargeDefinitionService chargeDefinitionService) {
+    this.chargeDefinitionService = chargeDefinitionService;
   }
 
   @Override
@@ -51,16 +58,21 @@
       final RunningBalances runningBalances)
   {
     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 Stream<ScheduledCharge> scheduledChargesForAccruals
+        = chargeDefinitionService.getChargeDefinitionsMappedByAccrueAction(dataContextOfAction.getProductEntity().getIdentifier())
+        .values().stream().flatMap(Collection::stream)
+        .map(x -> getReverseAccrualScheduledCharge(x, forDate));
+
+    final List<ScheduledCharge> scheduledChargesForAccrualsAndWriteOff = Stream.concat(scheduledChargesForAccruals,
+        Stream.of(getScheduledChargeForWriteOff(forDate)))
+        .collect(Collectors.toList());
 
     final BigDecimal loanPaymentSize = dataContextOfAction.getCaseParametersEntity().getPaymentSize();
 
     return CostComponentService.getCostComponentsForScheduledCharges(
-        scheduledCharges,
+        scheduledChargesForAccrualsAndWriteOff,
         caseParameters.getBalanceRangeMaximum(),
         runningBalances,
         loanPaymentSize,
@@ -70,4 +82,43 @@
         minorCurrencyUnitDigits,
         true);
   }
+
+
+  private ScheduledCharge getScheduledChargeForWriteOff(final LocalDate forDate) {
+
+    final ChargeDefinition chargeDefinition = new ChargeDefinition();
+    chargeDefinition.setChargeAction(Action.WRITE_OFF.name());
+    chargeDefinition.setIdentifier(WRITE_OFF_ID);
+    chargeDefinition.setName(WRITE_OFF_NAME);
+    chargeDefinition.setDescription(WRITE_OFF_NAME);
+    chargeDefinition.setFromAccountDesignator(AccountDesignators.EXPENSE);
+    chargeDefinition.setToAccountDesignator(AccountDesignators.GENERAL_LOSS_ALLOWANCE);
+    chargeDefinition.setProportionalTo(ChargeProportionalDesignator.PRINCIPAL_DESIGNATOR.getValue());
+    chargeDefinition.setChargeMethod(ChargeDefinition.ChargeMethod.PROPORTIONAL);
+    chargeDefinition.setAmount(BigDecimal.valueOf(100));
+    chargeDefinition.setReadOnly(true);
+    final ScheduledAction scheduledAction = new ScheduledAction(Action.WRITE_OFF, forDate);
+    return new ScheduledCharge(scheduledAction, chargeDefinition, Optional.empty());
+  }
+
+  private ScheduledCharge getReverseAccrualScheduledCharge(
+      final ChargeDefinition accrualChargeDefinition,
+      final LocalDate forDate) {
+
+    final ChargeDefinition chargeDefinition = new ChargeDefinition();
+    chargeDefinition.setChargeAction(Action.WRITE_OFF.name());
+    chargeDefinition.setIdentifier(accrualChargeDefinition.getIdentifier());
+    chargeDefinition.setName(accrualChargeDefinition.getName());
+    chargeDefinition.setDescription(accrualChargeDefinition.getDescription());
+    chargeDefinition.setFromAccountDesignator(accrualChargeDefinition.getFromAccountDesignator());
+    chargeDefinition.setAccrualAccountDesignator(accrualChargeDefinition.getAccrualAccountDesignator());
+    chargeDefinition.setAccrueAction(accrualChargeDefinition.getAccrueAction());
+    chargeDefinition.setToAccountDesignator(AccountDesignators.PRODUCT_LOSS_ALLOWANCE);
+    chargeDefinition.setChargeMethod(accrualChargeDefinition.getChargeMethod());
+    chargeDefinition.setAmount(accrualChargeDefinition.getAmount());
+    chargeDefinition.setReadOnly(true);
+    final ScheduledAction scheduledAction = new ScheduledAction(Action.WRITE_OFF, forDate);
+    return new ScheduledCharge(scheduledAction, chargeDefinition, Optional.empty());
+
+  }
 }
diff --git a/service/src/main/java/io/mifos/individuallending/internal/service/schedule/LossProvisionChargesService.java b/service/src/main/java/io/mifos/individuallending/internal/service/schedule/LossProvisionChargesService.java
index 7bb2eb7..a06707c 100644
--- a/service/src/main/java/io/mifos/individuallending/internal/service/schedule/LossProvisionChargesService.java
+++ b/service/src/main/java/io/mifos/individuallending/internal/service/schedule/LossProvisionChargesService.java
@@ -29,8 +29,7 @@
 import java.time.LocalDate;
 import java.util.Optional;
 
-import static io.mifos.individuallending.api.v1.domain.product.ChargeIdentifiers.PROVISION_FOR_LOSSES_ID;
-import static io.mifos.individuallending.api.v1.domain.product.ChargeIdentifiers.PROVISION_FOR_LOSSES_NAME;
+import static io.mifos.individuallending.api.v1.domain.product.ChargeIdentifiers.*;
 
 /**
  * @author Myrle Krantz
@@ -45,12 +44,19 @@
     this.lossProvisionStepService = lossProvisionStepService;
   }
 
-  public Optional<ScheduledCharge> getScheduledChargeForMarkLate(
+  public Optional<ScheduledCharge> getScheduledChargeForMarkInArrears(
       final DataContextOfAction dataContextOfAction,
       final LocalDate forDate,
       final int daysLate)
   {
-    return getScheduledLossProvisioningCharge(dataContextOfAction, forDate, daysLate, Action.MARK_LATE);
+    return getScheduledLossProvisioningCharge(dataContextOfAction, forDate, daysLate, Action.MARK_IN_ARREARS);
+  }
+
+  public Optional<ScheduledCharge> getScheduledChargeForMarkLate(
+      final DataContextOfAction dataContextOfAction,
+      final LocalDate forDate)
+  {
+    return getScheduledLossProvisioningCharge(dataContextOfAction, forDate, 1, Action.MARK_LATE);
   }
 
 
@@ -58,14 +64,17 @@
       final DataContextOfAction dataContextOfAction,
       final LocalDate forDate)
   {
-    return getScheduledLossProvisioningCharge(dataContextOfAction, forDate, 0, Action.DISBURSE);
+    final Optional<ScheduledCharge> ret = getScheduledLossProvisioningCharge(dataContextOfAction, forDate, 0, Action.DISBURSE);
+    ret.ifPresent(x -> x.getChargeDefinition().setProportionalTo(ChargeProportionalDesignator.REQUESTED_REPAYMENT_DESIGNATOR.getValue()));
+    return ret;
   }
 
   private Optional<ScheduledCharge> getScheduledLossProvisioningCharge(
       final DataContextOfAction dataContextOfAction,
       final LocalDate forDate,
       final int daysLate, Action action) {
-    final Optional<ChargeDefinition> optionalChargeDefinition = percentProvision(dataContextOfAction, daysLate)
+    final Optional<ChargeDefinition> optionalChargeDefinition = lossProvisionStepService.findByProductIdAndDaysLate(dataContextOfAction.getProductEntity().getId(), daysLate)
+        .map(LossProvisionStep::getPercentProvision)
         .map(percentProvision -> getLossProvisionCharge(percentProvision, action));
 
     return optionalChargeDefinition.map(chargeDefinition -> {
@@ -74,14 +83,6 @@
     });
   }
 
-  private Optional<BigDecimal> percentProvision(
-      final DataContextOfAction dataContextOfAction,
-      final int daysLate)
-  {
-    return lossProvisionStepService.findByProductIdAndDaysLate(dataContextOfAction.getProductEntity().getId(), daysLate)
-        .map(LossProvisionStep::getPercentProvision);
-  }
-
   private ChargeDefinition getLossProvisionCharge(
       final BigDecimal percentProvision,
       final Action action) {
@@ -91,11 +92,10 @@
     ret.setName(PROVISION_FOR_LOSSES_NAME);
     ret.setDescription(PROVISION_FOR_LOSSES_NAME);
     ret.setFromAccountDesignator(AccountDesignators.PRODUCT_LOSS_ALLOWANCE);
-    ret.setAccrualAccountDesignator(AccountDesignators.GENERAL_LOSS_ALLOWANCE);
-    ret.setToAccountDesignator(AccountDesignators.GENERAL_EXPENSE);
+    ret.setToAccountDesignator(AccountDesignators.GENERAL_LOSS_ALLOWANCE);
     ret.setProportionalTo(ChargeProportionalDesignator.PRINCIPAL_DESIGNATOR.getValue());
     ret.setChargeMethod(ChargeDefinition.ChargeMethod.PROPORTIONAL);
-    ret.setAmount(percentProvision);
+    ret.setAmount(percentProvision.negate());
     ret.setReadOnly(true);
     return ret;
   }
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 fba4800..a56fecd 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
@@ -77,7 +77,7 @@
     ret.setFromAccountDesignator(from.getFromAccountDesignator());
     ret.setAccrualAccountDesignator(from.getAccrualAccountDesignator());
     ret.setToAccountDesignator(from.getToAccountDesignator());
-    ret.setReadOnly(Optional.ofNullable(from.getReadOnly()).orElseGet(() -> readOnlyLegacyMapper(from.getIdentifier())));
+    ret.setReadOnly(Optional.ofNullable(from.getReadOnly()).orElse(false));
     if (from.getSegmentSet() != null && from.getFromSegment() != null && from.getToSegment() != null) {
       ret.setForSegmentSet(from.getSegmentSet());
       ret.setFromSegment(from.getFromSegment());
@@ -88,29 +88,6 @@
     return ret;
   }
 
-  private static Boolean readOnlyLegacyMapper(final String identifier) {
-    switch (identifier) {
-      case INTEREST_ID:
-        return false;
-      case ALLOW_FOR_WRITE_OFF_ID:
-        return false;
-      case LATE_FEE_ID:
-        return true;
-      case DISBURSEMENT_FEE_ID:
-        return false;
-      case DISBURSE_PAYMENT_ID:
-        return false;
-      case LOAN_ORIGINATION_FEE_ID:
-        return true;
-      case PROCESSING_FEE_ID:
-        return true;
-      case REPAY_PRINCIPAL_ID:
-        return false;
-      default:
-        return false;
-    }
-  }
-
   private static String proportionalToLegacyMapper(final ChargeDefinitionEntity from,
                                                    final ChargeDefinition.ChargeMethod chargeMethod,
                                                    final String identifier) {
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 4bb8623..a3a0bc2 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
@@ -180,20 +180,6 @@
         .map(DateConverter::fromIsoString);
   }
 
-  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")
-        .findFirst()
-        .map(AccountEntry::getTransactionDate)
-        .map(DateConverter::fromIsoString);
-  }
-
   public BigDecimal sumMatchingEntriesSinceDate(final String accountIdentifier, final LocalDate startDate, final String message)
   {
     final DateRange fromLastPaymentUntilNow = oneSidedDateRange(startDate);
@@ -245,7 +231,7 @@
         generatedLedger.setName(ledgerIdentifer.getIdentifier());
       }
     }
-    final boolean ledgerCreationDetected = expectation.waitForOccurrence(5, TimeUnit.SECONDS);
+    final boolean ledgerCreationDetected = expectation.waitForOccurrence(10, TimeUnit.SECONDS);
     if (!ledgerCreationDetected)
       logger.warn("Waited 5 seconds for creation of ledger '{}', but it was not detected. This could cause subsequent " +
               "account creations to fail. Is there something wrong with the accounting service? Is ActiveMQ setup properly?",
diff --git a/service/src/main/resources/db/migrations/mariadb/V10__arrears_determination2.sql b/service/src/main/resources/db/migrations/mariadb/V10__arrears_determination2.sql
new file mode 100644
index 0000000..af50113
--- /dev/null
+++ b/service/src/main/resources/db/migrations/mariadb/V10__arrears_determination2.sql
@@ -0,0 +1,25 @@
+--
+-- 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_il_late_cases (
+  id BIGINT NOT NULL AUTO_INCREMENT,
+  case_id                  BIGINT         NOT NULL,
+  late_since               TIMESTAMP(3)   NOT NULL,
+
+  CONSTRAINT bastet_il_late_cases_pk PRIMARY KEY (id),
+  CONSTRAINT bastet_il_late_cases_uq UNIQUE (case_id),
+  CONSTRAINT bastet_il_late_cases_fk FOREIGN KEY (case_id) REFERENCES bastet_cases (id)
+);
\ No newline at end of file
diff --git a/service/src/test/java/io/mifos/individuallending/internal/service/costcomponent/AcceptPaymentBuilderServiceTest.java b/service/src/test/java/io/mifos/individuallending/internal/service/costcomponent/AcceptPaymentBuilderServiceTest.java
index 905f1f8..8fbcda1 100644
--- a/service/src/test/java/io/mifos/individuallending/internal/service/costcomponent/AcceptPaymentBuilderServiceTest.java
+++ b/service/src/test/java/io/mifos/individuallending/internal/service/costcomponent/AcceptPaymentBuilderServiceTest.java
@@ -31,6 +31,9 @@
 import java.util.Map;
 import java.util.stream.Collectors;
 
+/**
+ * @author Myrle Krantz
+ */
 @RunWith(Parameterized.class)
 public class AcceptPaymentBuilderServiceTest {
 
diff --git a/service/src/test/java/io/mifos/individuallending/internal/service/costcomponent/ApplyInterestPaymentBuilderServiceTest.java b/service/src/test/java/io/mifos/individuallending/internal/service/costcomponent/ApplyInterestPaymentBuilderServiceTest.java
index 7771678..80ba2a7 100644
--- a/service/src/test/java/io/mifos/individuallending/internal/service/costcomponent/ApplyInterestPaymentBuilderServiceTest.java
+++ b/service/src/test/java/io/mifos/individuallending/internal/service/costcomponent/ApplyInterestPaymentBuilderServiceTest.java
@@ -24,6 +24,9 @@
 import java.math.BigDecimal;
 import java.util.Collections;
 
+/**
+ * @author Myrle Krantz
+ */
 public class ApplyInterestPaymentBuilderServiceTest {
   @Test
   public void getPaymentBuilder() throws Exception {
diff --git a/service/src/test/java/io/mifos/individuallending/internal/service/costcomponent/DisbursePaymentBuilderServiceTest.java b/service/src/test/java/io/mifos/individuallending/internal/service/costcomponent/DisbursePaymentBuilderServiceTest.java
new file mode 100644
index 0000000..4d3d907
--- /dev/null
+++ b/service/src/test/java/io/mifos/individuallending/internal/service/costcomponent/DisbursePaymentBuilderServiceTest.java
@@ -0,0 +1,84 @@
+/*
+ * 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.costcomponent;
+
+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.product.LossProvisionStep;
+import io.mifos.individuallending.api.v1.domain.workflow.Action;
+import io.mifos.individuallending.internal.service.LossProvisionStepService;
+import io.mifos.individuallending.internal.service.schedule.LossProvisionChargesService;
+import io.mifos.portfolio.api.v1.domain.CostComponent;
+import io.mifos.portfolio.api.v1.domain.Payment;
+import org.junit.Assert;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.mockito.Matchers;
+import org.mockito.Mockito;
+
+import java.math.BigDecimal;
+import java.util.*;
+import java.util.stream.Collectors;
+
+/**
+ * @author Myrle Krantz
+ */
+@RunWith(Parameterized.class)
+public class DisbursePaymentBuilderServiceTest {
+
+  @Parameterized.Parameters
+  public static Collection testCases() {
+    final Collection<PaymentBuilderServiceTestCase> ret = new ArrayList<>();
+    ret.add(simpleCase());
+    return ret;
+  }
+
+  private static PaymentBuilderServiceTestCase simpleCase() {
+    return new PaymentBuilderServiceTestCase("simple case");
+  }
+
+  private final PaymentBuilderServiceTestCase testCase;
+
+  public DisbursePaymentBuilderServiceTest(final PaymentBuilderServiceTestCase testCase) {
+    this.testCase = testCase;
+  }
+
+  @Test
+  public void getPaymentBuilder() throws Exception {
+    final LossProvisionStepService lossProvisionStepsService = Mockito.mock(LossProvisionStepService.class);
+    Mockito.doReturn(Optional.of(new LossProvisionStep(0, BigDecimal.ONE))).when(lossProvisionStepsService).findByProductIdAndDaysLate(Matchers.any(), Matchers.eq(0));
+    final LossProvisionChargesService lossProvisionChargesService = new LossProvisionChargesService(lossProvisionStepsService);
+    final PaymentBuilder paymentBuilder = PaymentBuilderServiceTestHarness.constructCallToPaymentBuilder(
+        (scheduledChargesService) -> new DisbursePaymentBuilderService(scheduledChargesService, lossProvisionChargesService), testCase);
+
+    final Payment payment = paymentBuilder.buildPayment(Action.DISBURSE, Collections.emptySet(), testCase.forDate.toLocalDate());
+    Assert.assertNotNull(payment);
+    final Map<String, CostComponent> mappedCostComponents = payment.getCostComponents().stream()
+        .collect(Collectors.toMap(CostComponent::getChargeIdentifier, x -> x));
+
+    Assert.assertEquals(
+        testCase.paymentSize,
+        mappedCostComponents.get(ChargeIdentifiers.DISBURSE_PAYMENT_ID).getAmount());
+    Assert.assertEquals(
+        testCase.paymentSize.multiply(BigDecimal.valueOf(1, 2)).setScale(2, BigDecimal.ROUND_HALF_EVEN),
+        paymentBuilder.getBalanceAdjustments().get(AccountDesignators.PRODUCT_LOSS_ALLOWANCE));
+    Assert.assertEquals(
+        testCase.paymentSize.multiply(BigDecimal.valueOf(1, 2)).negate().setScale(2, BigDecimal.ROUND_HALF_EVEN),
+        paymentBuilder.getBalanceAdjustments().get(AccountDesignators.GENERAL_LOSS_ALLOWANCE));
+  }
+
+}
\ No newline at end of file
diff --git a/service/src/test/java/io/mifos/individuallending/internal/service/costcomponent/WriteOffPaymentBuilderServiceTest.java b/service/src/test/java/io/mifos/individuallending/internal/service/costcomponent/WriteOffPaymentBuilderServiceTest.java
new file mode 100644
index 0000000..fe5f7d4
--- /dev/null
+++ b/service/src/test/java/io/mifos/individuallending/internal/service/costcomponent/WriteOffPaymentBuilderServiceTest.java
@@ -0,0 +1,77 @@
+/*
+ * 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.costcomponent;
+
+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.internal.service.DefaultChargeDefinitionsMocker;
+import io.mifos.portfolio.api.v1.domain.CostComponent;
+import io.mifos.portfolio.api.v1.domain.Payment;
+import org.junit.Assert;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+/**
+ * @author Myrle Krantz
+ */
+@RunWith(Parameterized.class)
+public class WriteOffPaymentBuilderServiceTest {
+
+  @Parameterized.Parameters
+  public static Collection testCases() {
+    final Collection<PaymentBuilderServiceTestCase> ret = new ArrayList<>();
+    ret.add(simpleCase());
+    //TODO: add use case for when the general loss allowance account doesn't have enough to cover the write off.
+    return ret;
+  }
+
+  private static PaymentBuilderServiceTestCase simpleCase() {
+    final PaymentBuilderServiceTestCase ret = new PaymentBuilderServiceTestCase("simple case");
+    ret.runningBalances.adjustBalance(AccountDesignators.CUSTOMER_LOAN_PRINCIPAL, ret.balance.negate());
+    ret.runningBalances.adjustBalance(AccountDesignators.GENERAL_LOSS_ALLOWANCE, ret.balance.negate());
+    return ret;
+  }
+
+  private final PaymentBuilderServiceTestCase testCase;
+
+  public WriteOffPaymentBuilderServiceTest(final PaymentBuilderServiceTestCase testCase) {
+    this.testCase = testCase;
+  }
+
+  @Test
+  public void getPaymentBuilder() throws Exception {
+    final PaymentBuilder paymentBuilder = PaymentBuilderServiceTestHarness.constructCallToPaymentBuilder(
+        (scheduledChargesService) -> new WriteOffPaymentBuilderService(DefaultChargeDefinitionsMocker.getChargeDefinitionService(Collections.emptyList())), testCase);
+
+    final Payment payment = paymentBuilder.buildPayment(Action.WRITE_OFF, Collections.emptySet(), testCase.forDate.toLocalDate());
+    Assert.assertNotNull(payment);
+    final Map<String, CostComponent> mappedCostComponents = payment.getCostComponents().stream()
+        .collect(Collectors.toMap(CostComponent::getChargeIdentifier, x -> x));
+
+    Assert.assertEquals(
+        testCase.balance,
+        mappedCostComponents.get(ChargeIdentifiers.WRITE_OFF_ID).getAmount());
+  }
+
+}