Merge pull request #1 from myrlen/develop

forPaymentSize and touchingaccounts parameters added to getCostComponentsForAction
diff --git a/api/src/main/java/io/mifos/portfolio/api/v1/client/PortfolioManager.java b/api/src/main/java/io/mifos/portfolio/api/v1/client/PortfolioManager.java
index b80fa76..32576ed 100644
--- a/api/src/main/java/io/mifos/portfolio/api/v1/client/PortfolioManager.java
+++ b/api/src/main/java/io/mifos/portfolio/api/v1/client/PortfolioManager.java
@@ -28,6 +28,7 @@
 import org.springframework.web.bind.annotation.RequestMethod;
 import org.springframework.web.bind.annotation.RequestParam;
 
+import java.math.BigDecimal;
 import java.util.List;
 import java.util.Set;
 
@@ -273,6 +274,7 @@
   Set<String> getActionsForCase(@PathVariable("productidentifier") final String productIdentifier,
                                 @PathVariable("caseidentifier") final String caseIdentifier);
 
+
   @RequestMapping(
       value = "/products/{productidentifier}/cases/{caseidentifier}/actions/{actionidentifier}/costcomponents",
       method = RequestMethod.GET,
@@ -282,7 +284,21 @@
   List<CostComponent> getCostComponentsForAction(@PathVariable("productidentifier") final String productIdentifier,
                                                  @PathVariable("caseidentifier") final String caseIdentifier,
                                                  @PathVariable("actionidentifier") final String actionIdentifier,
-                                                 @RequestParam(value="touchingaccounts", required = false, defaultValue = "") final String forAccountDesignators);
+                                                 @RequestParam(value="touchingaccounts", required = false, defaultValue = "") final Set<String> forAccountDesignators,
+                                                 @RequestParam(value="forpaymentsize", required = false, defaultValue = "") final BigDecimal forPaymentSize);
+
+
+  @RequestMapping(
+      value = "/products/{productidentifier}/cases/{caseidentifier}/actions/{actionidentifier}/costcomponents",
+      method = RequestMethod.GET,
+      produces = MediaType.ALL_VALUE,
+      consumes = MediaType.APPLICATION_JSON_VALUE
+  )
+  List<CostComponent> getCostComponentsForAction(@PathVariable("productidentifier") final String productIdentifier,
+                                                 @PathVariable("caseidentifier") final String caseIdentifier,
+                                                 @PathVariable("actionidentifier") final String actionIdentifier,
+                                                 @RequestParam(value="touchingaccounts", required = false, defaultValue = "") final Set<String> forAccountDesignators);
+
 
   @RequestMapping(
       value = "/products/{productidentifier}/cases/{caseidentifier}/actions/{actionidentifier}/costcomponents",
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 cf92e9d..adadf68 100644
--- a/component-test/src/main/java/io/mifos/portfolio/AbstractPortfolioTest.java
+++ b/component-test/src/main/java/io/mifos/portfolio/AbstractPortfolioTest.java
@@ -242,8 +242,16 @@
   void checkCostComponentForActionCorrect(final String productIdentifier,
                                           final String customerCaseIdentifier,
                                           final Action action,
+                                          final Set<String> accountDesignators,
+                                          final BigDecimal amount,
                                           final CostComponent... expectedCostComponents) {
-    final List<CostComponent> costComponents = portfolioManager.getCostComponentsForAction(productIdentifier, customerCaseIdentifier, action.name());
+    final List<CostComponent> costComponents = portfolioManager.getCostComponentsForAction(
+        productIdentifier,
+        customerCaseIdentifier,
+        action.name(),
+        accountDesignators,
+        amount
+    );
     final Set<CostComponent> setOfCostComponents = new HashSet<>(costComponents);
     final Set<CostComponent> setOfExpectedCostComponents = new HashSet<>(Arrays.asList(expectedCostComponents));
     Assert.assertEquals(setOfExpectedCostComponents, setOfCostComponents);
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 739e354..cc2c74c 100644
--- a/component-test/src/main/java/io/mifos/portfolio/TestAccountingInteractionInLoanWorkflow.java
+++ b/component-test/src/main/java/io/mifos/portfolio/TestAccountingInteractionInLoanWorkflow.java
@@ -22,6 +22,7 @@
 import io.mifos.core.api.util.ApiFactory;
 import io.mifos.core.lang.DateConverter;
 import io.mifos.individuallending.api.v1.domain.caseinstance.CaseParameters;
+import io.mifos.individuallending.api.v1.domain.product.AccountDesignators;
 import io.mifos.individuallending.api.v1.domain.product.ChargeIdentifiers;
 import io.mifos.individuallending.api.v1.domain.workflow.Action;
 import io.mifos.individuallending.api.v1.events.IndividualLoanCommandEvent;
@@ -37,8 +38,10 @@
 import org.junit.Test;
 
 import java.math.BigDecimal;
+import java.math.RoundingMode;
 import java.time.LocalDateTime;
 import java.time.temporal.ChronoUnit;
+import java.util.Arrays;
 import java.util.Collections;
 import java.util.HashSet;
 import java.util.Set;
@@ -63,7 +66,7 @@
   private String customerLoanAccountIdentifier = null;
 
   private BigDecimal expectedCurrentBalance = null;
-  private BigDecimal interestAccrued = BigDecimal.ZERO;
+  private BigDecimal interestAccrued = BigDecimal.ZERO.setScale(MINOR_CURRENCY_UNIT_DIGITS, RoundingMode.HALF_EVEN);
 
 
   @Before
@@ -85,27 +88,35 @@
     step2CreateCase();
     step3OpenCase();
     step4ApproveCase();
-    step5Disburse(BigDecimal.valueOf(2000L));
+    step5Disburse(BigDecimal.valueOf(2000L).setScale(MINOR_CURRENCY_UNIT_DIGITS, BigDecimal.ROUND_HALF_EVEN));
     step6CalculateInterestAccrual();
     step7PaybackPartialAmount(expectedCurrentBalance);
     step8Close();
   }
 
-
   @Test
   public void workflowWithTwoNearlyEqualRepayments() throws InterruptedException {
     step1CreateProduct();
     step2CreateCase();
     step3OpenCase();
     step4ApproveCase();
-    step5Disburse(BigDecimal.valueOf(2000L));
+    step5Disburse(BigDecimal.valueOf(2000L).setScale(MINOR_CURRENCY_UNIT_DIGITS, BigDecimal.ROUND_HALF_EVEN));
     step6CalculateInterestAccrual();
     final BigDecimal repayment1 = expectedCurrentBalance.divide(BigDecimal.valueOf(2), BigDecimal.ROUND_HALF_EVEN);
-    step7PaybackPartialAmount(repayment1);
+    step7PaybackPartialAmount(repayment1.setScale(MINOR_CURRENCY_UNIT_DIGITS, BigDecimal.ROUND_HALF_EVEN));
     step7PaybackPartialAmount(expectedCurrentBalance);
     step8Close();
   }
 
+  @Test(expected = IllegalArgumentException.class)
+  public void workflowWithNegativePaymentSize() throws InterruptedException {
+    step1CreateProduct();
+    step2CreateCase();
+    step3OpenCase();
+    step4ApproveCase();
+    step5Disburse(BigDecimal.valueOf(-2).setScale(MINOR_CURRENCY_UNIT_DIGITS, BigDecimal.ROUND_HALF_EVEN));
+  }
+
   //Create product and set charges to fixed fees.
   private void step1CreateProduct() throws InterruptedException {
     logger.info("step1CreateProduct");
@@ -136,13 +147,18 @@
     customerCase = createAdjustedCase(product.getIdentifier(), x -> x.setParameters(caseParametersAsString));
 
     checkNextActionsCorrect(product.getIdentifier(), customerCase.getIdentifier(), Action.OPEN);
-    checkCostComponentForActionCorrect(product.getIdentifier(), customerCase.getIdentifier(), Action.OPEN,
-        new CostComponent(ChargeIdentifiers.PROCESSING_FEE_ID, PROCESSING_FEE_AMOUNT));
   }
 
   //Open the case and accept a processing fee.
   private void step3OpenCase() throws InterruptedException {
     logger.info("step3OpenCase");
+    checkCostComponentForActionCorrect(
+        product.getIdentifier(),
+        customerCase.getIdentifier(),
+        Action.OPEN,
+        Collections.singleton(AccountDesignators.ENTRY),
+        null,
+        new CostComponent(ChargeIdentifiers.PROCESSING_FEE_ID, PROCESSING_FEE_AMOUNT));
     checkStateTransfer(
         product.getIdentifier(),
         customerCase.getIdentifier(),
@@ -151,9 +167,6 @@
         IndividualLoanEventConstants.OPEN_INDIVIDUALLOAN_CASE,
         Case.State.PENDING);
     checkNextActionsCorrect(product.getIdentifier(), customerCase.getIdentifier(), Action.APPROVE, Action.DENY);
-    checkCostComponentForActionCorrect(product.getIdentifier(), customerCase.getIdentifier(), Action.APPROVE,
-        new CostComponent(ChargeIdentifiers.LOAN_ORIGINATION_FEE_ID, LOAN_ORIGINATION_FEE_AMOUNT),
-        new CostComponent(ChargeIdentifiers.LOAN_FUNDS_ALLOCATION_ID, caseParameters.getMaximumBalance().setScale(product.getMinorCurrencyUnitDigits(), BigDecimal.ROUND_UNNECESSARY)));
 
     AccountingFixture.verifyTransfer(ledgerManager,
         AccountingFixture.TELLER_ONE_ACCOUNT_IDENTIFIER, AccountingFixture.PROCESSING_FEE_INCOME_ACCOUNT_IDENTIFIER,
@@ -165,6 +178,12 @@
   //Deny the case. Once this is done, no more actions are possible for the case.
   private void step4DenyCase() throws InterruptedException {
     logger.info("step4DenyCase");
+    checkCostComponentForActionCorrect(
+        product.getIdentifier(),
+        customerCase.getIdentifier(),
+        Action.DENY,
+        Collections.singleton(AccountDesignators.ENTRY),
+        null);
     checkStateTransfer(
         product.getIdentifier(),
         customerCase.getIdentifier(),
@@ -182,6 +201,13 @@
 
     markTaskExecuted(product, customerCase, taskDefinition);
 
+    checkCostComponentForActionCorrect(
+        product.getIdentifier(),
+        customerCase.getIdentifier(),
+        Action.APPROVE,
+        Collections.singleton(AccountDesignators.ENTRY),
+        null,
+        new CostComponent(ChargeIdentifiers.LOAN_ORIGINATION_FEE_ID, LOAN_ORIGINATION_FEE_AMOUNT));
     checkStateTransfer(
         product.getIdentifier(),
         customerCase.getIdentifier(),
@@ -211,6 +237,13 @@
   //Approve the case, accept a loan origination fee, and prepare to disburse the loan by earmarking the funds.
   private void step5Disburse(final BigDecimal amount) throws InterruptedException {
     logger.info("step5Disburse");
+    checkCostComponentForActionCorrect(
+        product.getIdentifier(),
+        customerCase.getIdentifier(),
+        Action.DISBURSE,
+        Collections.singleton(AccountDesignators.ENTRY),
+        amount, new CostComponent(ChargeIdentifiers.DISBURSEMENT_FEE_ID, DISBURSEMENT_FEE_AMOUNT),
+        new CostComponent(ChargeIdentifiers.DISBURSE_PAYMENT_ID, amount));
     checkStateTransfer(
         product.getIdentifier(),
         customerCase.getIdentifier(),
@@ -224,17 +257,17 @@
 
 
     final Set<Debtor> debtors = new HashSet<>();
-    debtors.add(new Debtor(pendingDisbursalAccountIdentifier, caseParameters.getMaximumBalance().toPlainString()));
-    debtors.add(new Debtor(AccountingFixture.LOANS_PAYABLE_ACCOUNT_IDENTIFIER, caseParameters.getMaximumBalance().toPlainString()));
+    debtors.add(new Debtor(pendingDisbursalAccountIdentifier, amount.toPlainString()));
+    debtors.add(new Debtor(AccountingFixture.LOANS_PAYABLE_ACCOUNT_IDENTIFIER, amount.toPlainString()));
     debtors.add(new Debtor(AccountingFixture.TELLER_ONE_ACCOUNT_IDENTIFIER, DISBURSEMENT_FEE_AMOUNT.toPlainString()));
 
     final Set<Creditor> creditors = new HashSet<>();
-    creditors.add(new Creditor(customerLoanAccountIdentifier, caseParameters.getMaximumBalance().toPlainString()));
-    creditors.add(new Creditor(AccountingFixture.TELLER_ONE_ACCOUNT_IDENTIFIER, caseParameters.getMaximumBalance().toPlainString()));
+    creditors.add(new Creditor(customerLoanAccountIdentifier, amount.toPlainString()));
+    creditors.add(new Creditor(AccountingFixture.TELLER_ONE_ACCOUNT_IDENTIFIER, amount.toPlainString()));
     creditors.add(new Creditor(AccountingFixture.DISBURSEMENT_FEE_INCOME_ACCOUNT_IDENTIFIER, DISBURSEMENT_FEE_AMOUNT.toPlainString()));
     AccountingFixture.verifyTransfer(ledgerManager, debtors, creditors, product.getIdentifier(), customerCase.getIdentifier(), Action.DISBURSE);
 
-    expectedCurrentBalance = expectedCurrentBalance.add(caseParameters.getMaximumBalance());
+    expectedCurrentBalance = expectedCurrentBalance.add(amount);
   }
 
   //Perform daily interest calculation.
@@ -245,6 +278,16 @@
 
     AccountingFixture.mockBalance(customerLoanAccountIdentifier, expectedCurrentBalance.negate());
 
+    final BigDecimal calculatedInterest = expectedCurrentBalance.multiply(Fixture.INTEREST_RATE.divide(Fixture.ACCRUAL_PERIODS, 8, BigDecimal.ROUND_HALF_EVEN))
+        .setScale(MINOR_CURRENCY_UNIT_DIGITS, BigDecimal.ROUND_HALF_EVEN);
+
+    checkCostComponentForActionCorrect(
+        product.getIdentifier(),
+        customerCase.getIdentifier(),
+        Action.APPLY_INTEREST,
+        Collections.singleton(AccountDesignators.CUSTOMER_LOAN),
+        null,
+        new CostComponent(ChargeIdentifiers.INTEREST_ID, calculatedInterest));
     final BeatPublish interestBeat = new BeatPublish(beatIdentifier, midnightTimeStamp);
     portfolioBeatListener.publishBeat(interestBeat);
     Assert.assertTrue(this.eventRecorder.wait(io.mifos.rhythm.spi.v1.events.EventConstants.POST_PUBLISHEDBEAT,
@@ -253,11 +296,10 @@
     Assert.assertTrue(eventRecorder.wait(IndividualLoanEventConstants.APPLY_INTEREST_INDIVIDUALLOAN_CASE,
         new IndividualLoanCommandEvent(product.getIdentifier(), customerCase.getIdentifier())));
 
+
     final Case customerCaseAfterStateChange = portfolioManager.getCase(product.getIdentifier(), customerCase.getIdentifier());
     Assert.assertEquals(customerCaseAfterStateChange.getCurrentState(), Case.State.ACTIVE.name());
 
-    final BigDecimal calculatedInterest = caseParameters.getMaximumBalance().multiply(Fixture.INTEREST_RATE.divide(Fixture.ACCRUAL_PERIODS, 8, BigDecimal.ROUND_HALF_EVEN))
-        .setScale(MINOR_CURRENCY_UNIT_DIGITS, BigDecimal.ROUND_HALF_EVEN);
 
     interestAccrued = interestAccrued.add(calculatedInterest);
 
@@ -276,10 +318,21 @@
   }
 
   private void step7PaybackPartialAmount(final BigDecimal amount) throws InterruptedException {
-    logger.info("step7PaybackPartialAmount");
+    logger.info("step7PaybackPartialAmount '{}'", amount);
 
     AccountingFixture.mockBalance(customerLoanAccountIdentifier, expectedCurrentBalance.negate());
 
+    final BigDecimal principal = amount.subtract(interestAccrued);
+
+    checkCostComponentForActionCorrect(
+        product.getIdentifier(),
+        customerCase.getIdentifier(),
+        Action.ACCEPT_PAYMENT,
+        new HashSet<>(Arrays.asList(AccountDesignators.ENTRY, AccountDesignators.CUSTOMER_LOAN, AccountDesignators.LOAN_FUNDS_SOURCE)),
+        amount,
+        new CostComponent(ChargeIdentifiers.REPAYMENT_ID, amount),
+        new CostComponent(ChargeIdentifiers.TRACK_RETURN_PRINCIPAL_ID, principal),
+        new CostComponent(ChargeIdentifiers.INTEREST_ID, interestAccrued));
     checkStateTransfer(
         product.getIdentifier(),
         customerCase.getIdentifier(),
@@ -291,8 +344,6 @@
     checkNextActionsCorrect(product.getIdentifier(), customerCase.getIdentifier(), Action.APPLY_INTEREST,
         Action.APPLY_INTEREST, Action.MARK_LATE, Action.ACCEPT_PAYMENT, Action.DISBURSE, Action.WRITE_OFF, Action.CLOSE);
 
-    final BigDecimal principal = amount.subtract(interestAccrued);
-
     final Set<Debtor> debtors = new HashSet<>();
     debtors.add(new Debtor(customerLoanAccountIdentifier, amount.toPlainString()));
     debtors.add(new Debtor(AccountingFixture.LOAN_FUNDS_SOURCE_ACCOUNT_IDENTIFIER, principal.toPlainString()));
@@ -308,7 +359,7 @@
     AccountingFixture.verifyTransfer(ledgerManager, debtors, creditors, product.getIdentifier(), customerCase.getIdentifier(), Action.ACCEPT_PAYMENT);
 
     expectedCurrentBalance = expectedCurrentBalance.subtract(amount);
-    interestAccrued = BigDecimal.ZERO;
+    interestAccrued = BigDecimal.ZERO.setScale(MINOR_CURRENCY_UNIT_DIGITS, RoundingMode.HALF_EVEN);
   }
 
   private void step8Close() throws InterruptedException {
@@ -316,6 +367,12 @@
 
     AccountingFixture.mockBalance(customerLoanAccountIdentifier, expectedCurrentBalance.negate());
 
+    checkCostComponentForActionCorrect(
+        product.getIdentifier(),
+        customerCase.getIdentifier(),
+        Action.CLOSE,
+        Collections.singleton(AccountDesignators.ENTRY),
+        null);
     checkStateTransfer(
         product.getIdentifier(),
         customerCase.getIdentifier(),
diff --git a/component-test/src/main/java/io/mifos/portfolio/TestCases.java b/component-test/src/main/java/io/mifos/portfolio/TestCases.java
index 6bfc9f1..7493e81 100644
--- a/component-test/src/main/java/io/mifos/portfolio/TestCases.java
+++ b/component-test/src/main/java/io/mifos/portfolio/TestCases.java
@@ -16,6 +16,7 @@
 package io.mifos.portfolio;
 
 import com.google.gson.Gson;
+import io.mifos.core.api.util.NotFoundException;
 import io.mifos.core.test.domain.TimeStampChecker;
 import io.mifos.individuallending.api.v1.domain.caseinstance.CaseParameters;
 import io.mifos.individuallending.api.v1.domain.caseinstance.CreditWorthinessFactor;
@@ -46,6 +47,22 @@
   public TestCases() { }
 
   @Test
+  public void shouldFailToCreateCaseForNonexistentProduct() throws InterruptedException {
+    try {
+      final String productIdentifier = "nonexistantProduct";
+      final Case caseInstance = Fixture.getTestCase(productIdentifier);
+
+      portfolioManager.createCase(productIdentifier, caseInstance);
+      Assert.fail("Should fail because product doesn't exist.");
+
+      portfolioManager.getCase(productIdentifier, caseInstance.getIdentifier());
+      Assert.fail("Should fail because product doesn't exist.");
+    }
+    catch (final NotFoundException ignored) {
+    }
+  }
+
+  @Test
   public void shouldCreateCase() throws InterruptedException {
     final Product product = createAndEnableProduct();
 
diff --git a/service/src/main/java/io/mifos/individuallending/IndividualLendingPatternFactory.java b/service/src/main/java/io/mifos/individuallending/IndividualLendingPatternFactory.java
index 3de3a47..07c8d76 100644
--- a/service/src/main/java/io/mifos/individuallending/IndividualLendingPatternFactory.java
+++ b/service/src/main/java/io/mifos/individuallending/IndividualLendingPatternFactory.java
@@ -15,6 +15,7 @@
  */
 package io.mifos.individuallending;
 
+import com.google.common.collect.Sets;
 import com.google.gson.Gson;
 import io.mifos.core.lang.ServiceException;
 import io.mifos.individuallending.api.v1.domain.caseinstance.CaseParameters;
@@ -324,16 +325,19 @@
 
   @Override
   public List<CostComponent> getCostComponentsForAction(
-          final String productIdentifier,
-          final String caseIdentifier,
-          final String actionIdentifier) {
+      final String productIdentifier,
+      final String caseIdentifier,
+      final String actionIdentifier,
+      final Set<String> forAccountDesignators,
+      final BigDecimal forPaymentSize) {
     final Action action = Action.valueOf(actionIdentifier);
     final DataContextOfAction dataContextOfAction = costComponentService.checkedGetDataContext(productIdentifier, caseIdentifier, Collections.emptyList());
     final Case.State caseState = Case.State.valueOf(dataContextOfAction.getCustomerCase().getCurrentState());
     checkActionCanBeExecuted(caseState, action);
 
-    return costComponentService.getCostComponentsForAction(action, dataContextOfAction)
+    return costComponentService.getCostComponentsForAction(action, dataContextOfAction, forPaymentSize)
         .stream()
+        .filter(costComponentEntry -> chargeReferencesAccountDesignators(costComponentEntry.getKey(), action, forAccountDesignators))
         .map(costComponentEntry -> new CostComponent(costComponentEntry.getKey().getIdentifier(), costComponentEntry.getValue().getAmount()))
         .collect(Collectors.toList())
             .stream()
@@ -341,6 +345,20 @@
             .collect(Collectors.toList());
   }
 
+  private boolean chargeReferencesAccountDesignators(
+      final ChargeDefinition chargeDefinition,
+      final Action action,
+      final Set<String> forAccountDesignators) {
+    final Set<String> accountsToCompare = Sets.newHashSet(
+        chargeDefinition.getFromAccountDesignator(),
+        chargeDefinition.getToAccountDesignator()
+    );
+    if (chargeDefinition.getAccrualAccountDesignator() != null)
+      accountsToCompare.add(chargeDefinition.getAccrualAccountDesignator());
+
+    return !Sets.intersection(accountsToCompare, forAccountDesignators).isEmpty();
+  }
+
   public static void checkActionCanBeExecuted(final Case.State state, final Action action) {
     if (!getAllowedNextActionsForState(state).contains(action))
       throw ServiceException.badRequest("Cannot call action {0} from state {1}", action.name(), state.name());
diff --git a/service/src/main/java/io/mifos/individuallending/internal/service/CostComponentService.java b/service/src/main/java/io/mifos/individuallending/internal/service/CostComponentService.java
index cc20d09..fb76cbc 100644
--- a/service/src/main/java/io/mifos/individuallending/internal/service/CostComponentService.java
+++ b/service/src/main/java/io/mifos/individuallending/internal/service/CostComponentService.java
@@ -98,7 +98,8 @@
 
   public CostComponentsForRepaymentPeriod getCostComponentsForAction(
       final Action action,
-      final DataContextOfAction dataContextOfAction) {
+      final DataContextOfAction dataContextOfAction,
+      final BigDecimal forPaymentSize) {
     switch (action) {
       case OPEN:
         return getCostComponentsForOpen(dataContextOfAction);
@@ -107,11 +108,11 @@
       case DENY:
         return getCostComponentsForDeny(dataContextOfAction);
       case DISBURSE:
-        return getCostComponentsForDisburse(dataContextOfAction, dataContextOfAction.getCaseParameters().getMaximumBalance());
+        return getCostComponentsForDisburse(dataContextOfAction, forPaymentSize);
       case APPLY_INTEREST:
         return getCostComponentsForApplyInterest(dataContextOfAction);
       case ACCEPT_PAYMENT:
-        return getCostComponentsForAcceptPayment(dataContextOfAction, null);
+        return getCostComponentsForAcceptPayment(dataContextOfAction, forPaymentSize);
       case CLOSE:
         return getCostComponentsForClose(dataContextOfAction);
       case MARK_LATE:
@@ -182,13 +183,14 @@
 
   public CostComponentsForRepaymentPeriod getCostComponentsForDisburse(
       final @Nonnull DataContextOfAction dataContextOfAction,
-      final @Nonnull BigDecimal requestedDisbursalSize) {
+      final @Nullable BigDecimal requestedDisbursalSize) {
     final DesignatorToAccountIdentifierMapper designatorToAccountIdentifierMapper
         = new DesignatorToAccountIdentifierMapper(dataContextOfAction);
     final String customerLoanAccountIdentifier = designatorToAccountIdentifierMapper.mapOrThrow(AccountDesignators.CUSTOMER_LOAN);
     final BigDecimal currentBalance = accountingAdapter.getCurrentBalance(customerLoanAccountIdentifier).negate();
 
-    if (dataContextOfAction.getCaseParameters().getMaximumBalance().compareTo(
+    if (requestedDisbursalSize != null &&
+        dataContextOfAction.getCaseParameters().getMaximumBalance().compareTo(
         currentBalance.add(requestedDisbursalSize)) < 0)
       throw ServiceException.conflict("Cannot disburse over the maximum balance.");
 
@@ -200,7 +202,11 @@
     final int minorCurrencyUnitDigits = dataContextOfAction.getProduct().getMinorCurrencyUnitDigits();
     final List<ScheduledAction> scheduledActions = Collections.singletonList(new ScheduledAction(Action.DISBURSE, today()));
 
-    final BigDecimal disbursalSize = requestedDisbursalSize.negate();
+    final BigDecimal disbursalSize;
+    if (requestedDisbursalSize == null)
+      disbursalSize = dataContextOfAction.getCaseParameters().getMaximumBalance().negate();
+    else
+      disbursalSize = requestedDisbursalSize.negate();
 
     final List<ScheduledCharge> scheduledCharges = individualLoanService.getScheduledCharges(
         productIdentifier, scheduledActions);
@@ -329,12 +335,12 @@
         true);
   }
 
-  private static boolean isAccruedChargeForAction(final ChargeDefinition chargeDefinition, final Action action) {
+  public static boolean isAccruedChargeForAction(final ChargeDefinition chargeDefinition, final Action action) {
     return chargeDefinition.getAccrueAction() != null &&
         chargeDefinition.getChargeAction().equals(action.name());
   }
 
-  private static boolean isAccrualChargeForAction(final ChargeDefinition chargeDefinition, final Action action) {
+  public static boolean isAccrualChargeForAction(final ChargeDefinition chargeDefinition, final Action action) {
     return chargeDefinition.getAccrueAction() != null &&
         chargeDefinition.getAccrueAction().equals(action.name());
   }
@@ -605,4 +611,5 @@
   private static LocalDate today() {
     return LocalDate.now(Clock.systemUTC());
   }
+
 }
diff --git a/service/src/main/java/io/mifos/portfolio/service/internal/service/CaseService.java b/service/src/main/java/io/mifos/portfolio/service/internal/service/CaseService.java
index 9250ee0..c1305b8 100644
--- a/service/src/main/java/io/mifos/portfolio/service/internal/service/CaseService.java
+++ b/service/src/main/java/io/mifos/portfolio/service/internal/service/CaseService.java
@@ -34,6 +34,7 @@
 import org.springframework.data.domain.Sort;
 import org.springframework.stereotype.Service;
 
+import java.math.BigDecimal;
 import java.util.Arrays;
 import java.util.List;
 import java.util.Optional;
@@ -126,11 +127,22 @@
     return caseRepository.existsByProductIdentifier(productIdentifier);
   }
 
+  public boolean existsByIdentifier(final String productIdentifier,
+                                    final String caseIdentifier) {
+    return this.findByIdentifier(productIdentifier, caseIdentifier).isPresent();
+  }
+
   public List<CostComponent> getActionCostComponentsForCase(final String productIdentifier,
                                                             final String caseIdentifier,
-                                                            final String actionIdentifier) {
-    return getPatternFactoryOrThrow(productIdentifier)
-            .getCostComponentsForAction(productIdentifier, caseIdentifier, actionIdentifier);
+                                                            final String actionIdentifier,
+                                                            final Set<String> forAccountDesignatorsList,
+                                                            final BigDecimal forPaymentSize) {
+    return getPatternFactoryOrThrow(productIdentifier).getCostComponentsForAction(
+        productIdentifier,
+        caseIdentifier,
+        actionIdentifier,
+        forAccountDesignatorsList,
+        forPaymentSize);
   }
 
   private int getMinorCurrencyUnitDigits(final String productIdentifier) {
diff --git a/service/src/main/java/io/mifos/portfolio/service/internal/service/ProductService.java b/service/src/main/java/io/mifos/portfolio/service/internal/service/ProductService.java
index 5d0d92b..1bc10d4 100644
--- a/service/src/main/java/io/mifos/portfolio/service/internal/service/ProductService.java
+++ b/service/src/main/java/io/mifos/portfolio/service/internal/service/ProductService.java
@@ -99,6 +99,12 @@
     return sortColumn;
   }
 
+  public boolean existsByIdentifier(final String identifier)
+  {
+    //TODO: replace with existsBy once we've upgraded to spring data 1.11 or later.
+    return productRepository.findByIdentifier(identifier).isPresent();
+  }
+
   public Optional<Product> findByIdentifier(final String identifier)
   {
     return productRepository.findByIdentifier(identifier).map(ProductMapper::map);
diff --git a/service/src/main/java/io/mifos/portfolio/service/rest/CaseRestController.java b/service/src/main/java/io/mifos/portfolio/service/rest/CaseRestController.java
index fb4750f..f2a4ed5 100644
--- a/service/src/main/java/io/mifos/portfolio/service/rest/CaseRestController.java
+++ b/service/src/main/java/io/mifos/portfolio/service/rest/CaseRestController.java
@@ -38,6 +38,7 @@
 import org.springframework.web.bind.annotation.*;
 
 import javax.validation.Valid;
+import java.math.BigDecimal;
 import java.util.List;
 import java.util.Optional;
 import java.util.Set;
@@ -189,11 +190,17 @@
   @ResponseBody
   List<CostComponent> getCostComponentsForAction(@PathVariable("productidentifier") final String productIdentifier,
                                                  @PathVariable("caseidentifier") final String caseIdentifier,
-                                                 @PathVariable("actionidentifier") final String actionIdentifier)
+                                                 @PathVariable("actionidentifier") final String actionIdentifier,
+                                                 @RequestParam(value="touchingaccounts", required = false, defaultValue = "") final Set<String> forAccountDesignators,
+                                                 @RequestParam(value="forpaymentsize", required = false, defaultValue = "") final BigDecimal forPaymentSize)
   {
     checkThatCaseExists(productIdentifier, caseIdentifier);
 
-    return caseService.getActionCostComponentsForCase(productIdentifier, caseIdentifier, actionIdentifier);
+    if (forPaymentSize != null && forPaymentSize.compareTo(BigDecimal.ZERO) < 0)
+      throw ServiceException.badRequest("forpaymentsize can''t be negative.");
+
+
+    return caseService.getActionCostComponentsForCase(productIdentifier, caseIdentifier, actionIdentifier, forAccountDesignators, forPaymentSize);
   }
 
   @Permittable(value = AcceptedTokenType.TENANT, groupId = PermittableGroupIds.CASE_MANAGEMENT)
@@ -226,16 +233,16 @@
     return new ResponseEntity<>(HttpStatus.ACCEPTED);
   }
 
-  private Case checkThatCaseExists(final String productIdentifier, final String caseIdentifier) {
+  private void checkThatCaseExists(final String productIdentifier, final String caseIdentifier) {
     checkThatProductExists(productIdentifier);
 
-    return caseService.findByIdentifier(productIdentifier, caseIdentifier)
-            .orElseThrow(() -> ServiceException.notFound("Case with identifier " + productIdentifier + "." + caseIdentifier + " doesn't exist."));
+    if (!caseService.existsByIdentifier(productIdentifier, caseIdentifier))
+      throw ServiceException.notFound("Case with identifier ''{0}.{1}'' doesn''t exist.", productIdentifier, caseIdentifier);
   }
 
   private void checkThatProductExists(final String productIdentifier) {
-    productService.findByIdentifier(productIdentifier)
-            .orElseThrow(() -> ServiceException.notFound("Product with identifier " + productIdentifier + " doesn't exist."));
+    if (!productService.existsByIdentifier(productIdentifier))
+      throw ServiceException.notFound("Product with identifier ''{0}'' doesn''t exist.", productIdentifier);
   }
 
   //TODO: check that case parameters are within product parameters in put and post.
diff --git a/service/src/main/java/io/mifos/products/spi/PatternFactory.java b/service/src/main/java/io/mifos/products/spi/PatternFactory.java
index a035aa1..5357e39 100644
--- a/service/src/main/java/io/mifos/products/spi/PatternFactory.java
+++ b/service/src/main/java/io/mifos/products/spi/PatternFactory.java
@@ -21,6 +21,7 @@
 import io.mifos.portfolio.api.v1.domain.CostComponent;
 import io.mifos.portfolio.api.v1.domain.Pattern;
 
+import java.math.BigDecimal;
 import java.util.List;
 import java.util.Optional;
 import java.util.Set;
@@ -35,6 +36,11 @@
   void changeParameters(Long caseId, String parameters);
   Optional<String> getParameters(Long caseId, int minorCurrencyUnitDigits);
   Set<String> getNextActionsForState(Case.State state);
-  List<CostComponent> getCostComponentsForAction(String productIdentifier, String caseIdentifier, String actionIdentifier);
+  List<CostComponent> getCostComponentsForAction(
+      String productIdentifier,
+      String caseIdentifier,
+      String actionIdentifier,
+      Set<String> forAccountDesignators,
+      BigDecimal forPaymentSize);
   ProductCommandDispatcher getIndividualLendingCommandDispatcher();
 }