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();
}