Implemented disbursement of full loan amount.
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 4498f46..4e7a98e 100644
--- a/component-test/src/main/java/io/mifos/portfolio/AbstractPortfolioTest.java
+++ b/component-test/src/main/java/io/mifos/portfolio/AbstractPortfolioTest.java
@@ -25,11 +25,13 @@
import io.mifos.core.test.listener.EnableEventRecording;
import io.mifos.core.test.listener.EventRecorder;
import io.mifos.individuallending.api.v1.client.IndividualLending;
+import io.mifos.individuallending.api.v1.domain.product.AccountDesignators;
import io.mifos.individuallending.api.v1.domain.workflow.Action;
import io.mifos.individuallending.api.v1.events.IndividualLoanCommandEvent;
import io.mifos.portfolio.api.v1.client.PortfolioManager;
import io.mifos.portfolio.api.v1.domain.*;
import io.mifos.portfolio.api.v1.events.CaseEvent;
+import io.mifos.portfolio.api.v1.events.ChargeDefinitionEvent;
import io.mifos.portfolio.api.v1.events.EventConstants;
import io.mifos.portfolio.service.config.PortfolioServiceConfiguration;
import io.mifos.portfolio.service.internal.util.AccountingAdapter;
@@ -42,6 +44,7 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.cloud.netflix.feign.EnableFeignClients;
@@ -56,6 +59,7 @@
import javax.validation.Validation;
import javax.validation.Validator;
import javax.validation.ValidatorFactory;
+import java.math.BigDecimal;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
@@ -74,6 +78,7 @@
classes = {AbstractPortfolioTest.TestConfiguration.class})
public class AbstractPortfolioTest {
private static final String APP_NAME = "portfolio-v1";
+ private static final String LOGGER_NAME = "test-logger";
@Configuration
@EnableEventRecording
@@ -86,9 +91,9 @@
super();
}
- @Bean()
+ @Bean(name = LOGGER_NAME)
public Logger logger() {
- return LoggerFactory.getLogger("test-logger");
+ return LoggerFactory.getLogger(LOGGER_NAME);
}
@Bean()
public AccountingAdapter accountingAdapter(@SuppressWarnings("SpringJavaAutowiringInspection")
@@ -136,6 +141,10 @@
@MockBean
LedgerManager ledgerManager;
+ @Autowired
+ @Qualifier(LOGGER_NAME)
+ Logger logger;
+
@Before
public void prepTest() {
userContext = this.tenantApplicationSecurityEnvironment.createAutoUserContext(TEST_USER);
@@ -235,4 +244,25 @@
final Set<CostComponent> setOfExpectedCostComponents = new HashSet<>(Arrays.asList(expectedCostComponents));
Assert.assertEquals(setOfExpectedCostComponents, setOfCostComponents);
}
+
+ void setFeeToFixedValue(final String productIdentifier,
+ final String feeId,
+ final BigDecimal amount) throws InterruptedException {
+ final ChargeDefinition chargeDefinition
+ = portfolioManager.getChargeDefinition(productIdentifier, feeId);
+ chargeDefinition.setChargeMethod(ChargeDefinition.ChargeMethod.FIXED);
+ chargeDefinition.setAmount(amount);
+ chargeDefinition.setProportionalTo(null);
+ portfolioManager.changeChargeDefinition(productIdentifier, feeId, chargeDefinition);
+ Assert.assertTrue(this.eventRecorder.wait(EventConstants.PUT_CHARGE_DEFINITION,
+ new ChargeDefinitionEvent(productIdentifier, feeId)));
+ }
+
+ AccountAssignment assignEntryToTeller() {
+ final AccountAssignment entryAccountAssignment = new AccountAssignment();
+ entryAccountAssignment.setDesignator(AccountDesignators.ENTRY);
+ entryAccountAssignment.setAccountIdentifier(AccountingFixture.TELLER_ONE_ACCOUNT_IDENTIFIER);
+ return entryAccountAssignment;
+ }
+
}
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 c5d87fb..89f9ccb 100644
--- a/component-test/src/main/java/io/mifos/portfolio/AccountingFixture.java
+++ b/component-test/src/main/java/io/mifos/portfolio/AccountingFixture.java
@@ -29,18 +29,35 @@
import java.util.Collections;
import java.util.Set;
-import static io.mifos.portfolio.Fixture.*;
import static org.mockito.Matchers.argThat;
/**
* @author Myrle Krantz
*/
+@SuppressWarnings("Duplicates")
class AccountingFixture {
+ private static final String INCOME_LEDGER_IDENTIFIER = "1000";
+ private static final String LOAN_INCOME_LEDGER_IDENTIFIER = "1100";
+ private static final String FEES_AND_CHARGES_LEDGER_IDENTIFIER = "1300";
+ private static final String ASSET_LEDGER_IDENTIFIER = "7000";
+ private static final String CASH_LEDGER_IDENTIFIER = "7300";
+ static final String PENDING_DISBURSAL_LEDGER_IDENTIFIER = "7320";
+ static final String CUSTOMER_LOAN_LEDGER_IDENTIFIER = "7353";
+ private static final String ACCRUED_INCOME_LEDGER_IDENTIFIER = "7800";
+
+ static final String LOAN_FUNDS_SOURCE_ACCOUNT_IDENTIFIER = "7310";
+ static final String LOAN_ORIGINATION_FEES_ACCOUNT_IDENTIFIER = "1310";
+ static final String PROCESSING_FEE_INCOME_ACCOUNT_IDENTIFIER = "1312";
+ static final String DISBURSEMENT_FEE_INCOME_ACCOUNT_IDENTIFIER = "1313";
+ static final String TELLER_ONE_ACCOUNT_IDENTIFIER = "7352";
+ static final String LOAN_INTEREST_ACCRUAL_ACCOUNT = "7810";
+ static final String CONSUMER_LOAN_INTEREST_ACCOUNT = "1103";
private static Ledger cashLedger() {
final Ledger ret = new Ledger();
ret.setIdentifier(CASH_LEDGER_IDENTIFIER);
+ ret.setParentLedgerIdentifier(ASSET_LEDGER_IDENTIFIER);
ret.setType(AccountType.ASSET.name());
return ret;
}
@@ -76,6 +93,24 @@
return ret;
}
+ private static Ledger loanIncomeLedger() {
+ final Ledger ret = new Ledger();
+ ret.setIdentifier(LOAN_INCOME_LEDGER_IDENTIFIER);
+ ret.setParentLedgerIdentifier(INCOME_LEDGER_IDENTIFIER);
+ ret.setType(AccountType.REVENUE.name());
+ return ret;
+
+ }
+
+ private static Ledger accruedIncomeLedger() {
+ final Ledger ret = new Ledger();
+ ret.setIdentifier(ACCRUED_INCOME_LEDGER_IDENTIFIER);
+ ret.setParentLedgerIdentifier(ASSET_LEDGER_IDENTIFIER);
+ ret.setType(AccountType.ASSET.name());
+ return ret;
+
+ }
+
private static Account loanFundsSourceAccount() {
final Account ret = new Account();
ret.setIdentifier(LOAN_FUNDS_SOURCE_ACCOUNT_IDENTIFIER);
@@ -100,6 +135,14 @@
return ret;
}
+ private static Account disbursementFeeIncomeAccount() {
+ final Account ret = new Account();
+ ret.setIdentifier(DISBURSEMENT_FEE_INCOME_ACCOUNT_IDENTIFIER);
+ ret.setLedger(FEES_AND_CHARGES_LEDGER_IDENTIFIER);
+ ret.setType(AccountType.REVENUE.name());
+ return ret;
+ }
+
private static Account tellerOneAccount() {
final Account ret = new Account();
ret.setIdentifier(TELLER_ONE_ACCOUNT_IDENTIFIER);
@@ -108,6 +151,22 @@
return ret;
}
+ private static Account loanInterestAccrualAccount() {
+ final Account ret = new Account();
+ ret.setIdentifier(LOAN_INTEREST_ACCRUAL_ACCOUNT);
+ ret.setLedger(ACCRUED_INCOME_LEDGER_IDENTIFIER);
+ ret.setType(AccountType.ASSET.name());
+ return ret;
+ }
+
+ private static Account consumerLoanInterestAccount() {
+ final Account ret = new Account();
+ ret.setIdentifier(CONSUMER_LOAN_INTEREST_ACCOUNT);
+ ret.setLedger(LOAN_INCOME_LEDGER_IDENTIFIER);
+ ret.setType(AccountType.REVENUE.name());
+ return ret;
+ }
+
private static AccountPage customerLoanAccountsPage() {
final Account customerLoanAccount1 = new Account();
customerLoanAccount1.setIdentifier("customerLoanAccount1");
@@ -157,12 +216,12 @@
private static class AccountMatcher extends ArgumentMatcher<Account> {
private final String ledgerIdentifer;
private final AccountType type;
- private Account checkedArgument;
+ private Account matchedArgument;
private AccountMatcher(final String ledgerIdentifier, final AccountType type) {
this.ledgerIdentifer = ledgerIdentifier;
this.type = type;
- this.checkedArgument = null; //Set when matches called.
+ this.matchedArgument = null; //Set when matches called and returns true.
}
@Override
@@ -172,15 +231,20 @@
if (! (argument instanceof Account))
return false;
- checkedArgument = (Account) argument;
+ final Account checkedArgument = (Account) argument;
- return checkedArgument.getLedger().equals(ledgerIdentifer) &&
- checkedArgument.getType().equals(type.name()) &&
- checkedArgument.getBalance() == 0.0;
+ final boolean ret = checkedArgument.getLedger().equals(ledgerIdentifer) &&
+ checkedArgument.getType().equals(type.name()) &&
+ checkedArgument.getBalance() == 0.0;
+
+ if (ret)
+ matchedArgument = checkedArgument;
+
+ return ret;
}
- Account getCheckedArgument() {
- return checkedArgument;
+ Account getMatchedArgument() {
+ return matchedArgument;
}
}
@@ -232,10 +296,15 @@
Mockito.doReturn(cashLedger()).when(ledgerManagerMock).findLedger(CASH_LEDGER_IDENTIFIER);
Mockito.doReturn(pendingDisbursalLedger()).when(ledgerManagerMock).findLedger(PENDING_DISBURSAL_LEDGER_IDENTIFIER);
Mockito.doReturn(customerLoanLedger()).when(ledgerManagerMock).findLedger(CUSTOMER_LOAN_LEDGER_IDENTIFIER);
+ Mockito.doReturn(loanIncomeLedger()).when(ledgerManagerMock).findLedger(LOAN_INCOME_LEDGER_IDENTIFIER);
+ Mockito.doReturn(accruedIncomeLedger()).when(ledgerManagerMock).findLedger(ACCRUED_INCOME_LEDGER_IDENTIFIER);
Mockito.doReturn(loanFundsSourceAccount()).when(ledgerManagerMock).findAccount(LOAN_FUNDS_SOURCE_ACCOUNT_IDENTIFIER);
Mockito.doReturn(loanOriginationFeesIncomeAccount()).when(ledgerManagerMock).findAccount(LOAN_ORIGINATION_FEES_ACCOUNT_IDENTIFIER);
Mockito.doReturn(processingFeeIncomeAccount()).when(ledgerManagerMock).findAccount(PROCESSING_FEE_INCOME_ACCOUNT_IDENTIFIER);
+ Mockito.doReturn(disbursementFeeIncomeAccount()).when(ledgerManagerMock).findAccount(DISBURSEMENT_FEE_INCOME_ACCOUNT_IDENTIFIER);
Mockito.doReturn(tellerOneAccount()).when(ledgerManagerMock).findAccount(TELLER_ONE_ACCOUNT_IDENTIFIER);
+ Mockito.doReturn(loanInterestAccrualAccount()).when(ledgerManagerMock).findAccount(LOAN_INTEREST_ACCRUAL_ACCOUNT);
+ Mockito.doReturn(consumerLoanInterestAccount()).when(ledgerManagerMock).findAccount(CONSUMER_LOAN_INTEREST_ACCOUNT);
Mockito.doReturn(customerLoanAccountsPage()).when(ledgerManagerMock).fetchAccountsOfLedger(Mockito.eq(CUSTOMER_LOAN_LEDGER_IDENTIFIER),
Mockito.any(), Mockito.any(), Mockito.any(), Mockito.any());
Mockito.doReturn(pendingDisbursalAccountsPage()).when(ledgerManagerMock).fetchAccountsOfLedger(Mockito.eq(PENDING_DISBURSAL_LEDGER_IDENTIFIER),
@@ -247,7 +316,7 @@
final AccountType type) {
final AccountMatcher specifiesCorrectAccount = new AccountMatcher(ledgerIdentifier, type);
Mockito.verify(ledgerManager).createAccount(AdditionalMatchers.and(argThat(isValid()), argThat(specifiesCorrectAccount)));
- return specifiesCorrectAccount.getCheckedArgument().getIdentifier();
+ return specifiesCorrectAccount.getMatchedArgument().getIdentifier();
}
static void verifyTransfer(final LedgerManager ledgerManager,
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 3ad05ee..5d60486 100644
--- a/component-test/src/main/java/io/mifos/portfolio/Fixture.java
+++ b/component-test/src/main/java/io/mifos/portfolio/Fixture.java
@@ -28,6 +28,7 @@
import java.util.function.Consumer;
import static io.mifos.individuallending.api.v1.domain.product.AccountDesignators.*;
+import static io.mifos.portfolio.AccountingFixture.*;
import static java.math.BigDecimal.ROUND_HALF_EVEN;
/**
@@ -36,16 +37,8 @@
@SuppressWarnings({"WeakerAccess", "unused"})
public class Fixture {
- static final String INCOME_LEDGER_IDENTIFIER = "1000";
- static final String FEES_AND_CHARGES_LEDGER_IDENTIFIER = "1300";
- static final String CASH_LEDGER_IDENTIFIER = "7300";
- static final String PENDING_DISBURSAL_LEDGER_IDENTIFIER = "7320";
- static final String CUSTOMER_LOAN_LEDGER_IDENTIFIER = "7353";
- static final String LOAN_FUNDS_SOURCE_ACCOUNT_IDENTIFIER = "7310";
- static final String LOAN_ORIGINATION_FEES_ACCOUNT_IDENTIFIER = "1310";
- static final String PROCESSING_FEE_INCOME_ACCOUNT_IDENTIFIER = "1312";
- static final String TELLER_ONE_ACCOUNT_IDENTIFIER = "7352";
+ public static final int MINOR_CURRENCY_UNIT_DIGITS = 2;
private static int uniquenessSuffix = 0;
static public Product getTestProduct() {
@@ -60,7 +53,7 @@
product.setInterestBasis(InterestBasis.CURRENT_BALANCE);
product.setCurrencyCode("XXX");
- product.setMinorCurrencyUnitDigits(2);
+ product.setMinorCurrencyUnitDigits(MINOR_CURRENCY_UNIT_DIGITS);
final Set<AccountAssignment> accountAssignments = new HashSet<>();
final AccountAssignment pendingDisbursalAccountAssignment = new AccountAssignment();
@@ -69,9 +62,9 @@
accountAssignments.add(pendingDisbursalAccountAssignment);
accountAssignments.add(new AccountAssignment(PROCESSING_FEE_INCOME, PROCESSING_FEE_INCOME_ACCOUNT_IDENTIFIER));
accountAssignments.add(new AccountAssignment(ORIGINATION_FEE_INCOME, LOAN_ORIGINATION_FEES_ACCOUNT_IDENTIFIER));
- accountAssignments.add(new AccountAssignment(DISBURSEMENT_FEE_INCOME, "001-004"));
- accountAssignments.add(new AccountAssignment(INTEREST_INCOME, "001-005"));
- accountAssignments.add(new AccountAssignment(INTEREST_ACCRUAL, "001-007"));
+ accountAssignments.add(new AccountAssignment(DISBURSEMENT_FEE_INCOME, DISBURSEMENT_FEE_INCOME_ACCOUNT_IDENTIFIER));
+ accountAssignments.add(new AccountAssignment(INTEREST_INCOME, CONSUMER_LOAN_INTEREST_ACCOUNT));
+ accountAssignments.add(new AccountAssignment(INTEREST_ACCRUAL, LOAN_INTEREST_ACCRUAL_ACCOUNT));
accountAssignments.add(new AccountAssignment(LATE_FEE_INCOME, "001-008"));
accountAssignments.add(new AccountAssignment(LATE_FEE_ACCRUAL, "001-009"));
accountAssignments.add(new AccountAssignment(ARREARS_ALLOWANCE, "001-010"));
@@ -107,7 +100,7 @@
static public BigDecimal fixScale(final BigDecimal bigDecimal)
{
- return bigDecimal.setScale(4, ROUND_HALF_EVEN);
+ return bigDecimal.setScale(MINOR_CURRENCY_UNIT_DIGITS, ROUND_HALF_EVEN);
}
static public Case getTestCase(final String productIdentifier) {
@@ -118,8 +111,6 @@
final Set<AccountAssignment> accountAssignments = new HashSet<>();
- accountAssignments.add(new AccountAssignment(CUSTOMER_LOAN, "001-011"));
- accountAssignments.add(new AccountAssignment(ENTRY, "001-012"));
ret.setAccountAssignments(accountAssignments);
ret.setCurrentState(Case.State.CREATED.name());
diff --git a/component-test/src/main/java/io/mifos/portfolio/TestAccountingInteraction.java b/component-test/src/main/java/io/mifos/portfolio/TestAccountingInteraction.java
deleted file mode 100644
index 91bf999..0000000
--- a/component-test/src/main/java/io/mifos/portfolio/TestAccountingInteraction.java
+++ /dev/null
@@ -1,125 +0,0 @@
-/*
- * Copyright 2017 The Mifos Initiative.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package io.mifos.portfolio;
-
-import com.google.gson.Gson;
-import io.mifos.accounting.api.v1.domain.AccountType;
-import io.mifos.accounting.api.v1.domain.Creditor;
-import io.mifos.accounting.api.v1.domain.Debtor;
-import io.mifos.individuallending.api.v1.domain.caseinstance.CaseParameters;
-import io.mifos.individuallending.api.v1.domain.workflow.Action;
-import io.mifos.portfolio.api.v1.domain.*;
-import io.mifos.portfolio.api.v1.events.ChargeDefinitionEvent;
-import io.mifos.portfolio.api.v1.events.EventConstants;
-import org.junit.Assert;
-import org.junit.Test;
-
-import java.math.BigDecimal;
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.Set;
-
-import static io.mifos.individuallending.api.v1.domain.product.ChargeIdentifiers.*;
-import static io.mifos.individuallending.api.v1.events.IndividualLoanEventConstants.APPROVE_INDIVIDUALLOAN_CASE;
-import static io.mifos.individuallending.api.v1.events.IndividualLoanEventConstants.OPEN_INDIVIDUALLOAN_CASE;
-import static io.mifos.portfolio.Fixture.*;
-
-/**
- * @author Myrle Krantz
- */
-public class TestAccountingInteraction extends AbstractPortfolioTest {
-
- @Test
- public void testLoanApproval() throws InterruptedException {
- //Create product and set charges to fixed fees.
- final Product product = createProduct();
-
- final ChargeDefinition processingFee = portfolioManager.getChargeDefinition(product.getIdentifier(), PROCESSING_FEE_ID);
- processingFee.setChargeMethod(ChargeDefinition.ChargeMethod.FIXED);
- processingFee.setAmount(BigDecimal.valueOf(10_0000, 4));
- processingFee.setProportionalTo(null);
- portfolioManager.changeChargeDefinition(product.getIdentifier(), PROCESSING_FEE_ID, processingFee);
- Assert.assertTrue(this.eventRecorder.wait(EventConstants.PUT_CHARGE_DEFINITION,
- new ChargeDefinitionEvent(product.getIdentifier(), PROCESSING_FEE_ID)));
-
- final ChargeDefinition loanOriginationFee = portfolioManager.getChargeDefinition(product.getIdentifier(), LOAN_ORIGINATION_FEE_ID);
- loanOriginationFee.setChargeMethod(ChargeDefinition.ChargeMethod.FIXED);
- loanOriginationFee.setAmount(BigDecimal.valueOf(100_0000, 4));
- loanOriginationFee.setProportionalTo(null);
- portfolioManager.changeChargeDefinition(product.getIdentifier(), LOAN_ORIGINATION_FEE_ID, loanOriginationFee);
- Assert.assertTrue(this.eventRecorder.wait(EventConstants.PUT_CHARGE_DEFINITION,
- new ChargeDefinitionEvent(product.getIdentifier(), LOAN_ORIGINATION_FEE_ID)));
-
- portfolioManager.enableProduct(product.getIdentifier(), true);
- Assert.assertTrue(this.eventRecorder.wait(EventConstants.PUT_PRODUCT_ENABLE, product.getIdentifier()));
-
- //Create case.
- final CaseParameters caseParameters = Fixture.createAdjustedCaseParameters(x -> {});
- final String caseParametersAsString = new Gson().toJson(caseParameters);
- final Case customerCase = createAdjustedCase(product.getIdentifier(), x -> x.setParameters(caseParametersAsString));
-
- //Open the case and accept a processing fee.
- checkNextActionsCorrect(product.getIdentifier(), customerCase.getIdentifier(), Action.OPEN);
- checkCostComponentForActionCorrect(product.getIdentifier(), customerCase.getIdentifier(), Action.OPEN,
- new CostComponent(processingFee.getIdentifier(), processingFee.getAmount().setScale(product.getMinorCurrencyUnitDigits(), BigDecimal.ROUND_UNNECESSARY)));
-
- final AccountAssignment openCommandProcessingFeeAccountAssignment = new AccountAssignment();
- openCommandProcessingFeeAccountAssignment.setDesignator(processingFee.getFromAccountDesignator());
- openCommandProcessingFeeAccountAssignment.setAccountIdentifier(TELLER_ONE_ACCOUNT_IDENTIFIER);
-
- checkStateTransfer(product.getIdentifier(), customerCase.getIdentifier(), Action.OPEN,
- Collections.singletonList(openCommandProcessingFeeAccountAssignment),
- OPEN_INDIVIDUALLOAN_CASE, Case.State.PENDING);
- checkNextActionsCorrect(product.getIdentifier(), customerCase.getIdentifier(), Action.APPROVE, Action.DENY);
- checkCostComponentForActionCorrect(product.getIdentifier(), customerCase.getIdentifier(), Action.APPROVE,
- new CostComponent(loanOriginationFee.getIdentifier(), loanOriginationFee.getAmount().setScale(product.getMinorCurrencyUnitDigits(), BigDecimal.ROUND_UNNECESSARY)),
- new CostComponent(LOAN_FUNDS_ALLOCATION_ID, caseParameters.getMaximumBalance().setScale(product.getMinorCurrencyUnitDigits(), BigDecimal.ROUND_UNNECESSARY)));
-
- AccountingFixture.verifyTransfer(ledgerManager,
- TELLER_ONE_ACCOUNT_IDENTIFIER, PROCESSING_FEE_INCOME_ACCOUNT_IDENTIFIER,
- processingFee.getAmount().setScale(product.getMinorCurrencyUnitDigits(), BigDecimal.ROUND_UNNECESSARY)
- );
-
-
- //Approve the case, accept a loan origination fee, and prepare to disburse the loan by earmarking the funds.
- final AccountAssignment approveCommandOriginationFeeAccountAssignment = new AccountAssignment();
- approveCommandOriginationFeeAccountAssignment.setDesignator(loanOriginationFee.getFromAccountDesignator());
- approveCommandOriginationFeeAccountAssignment.setAccountIdentifier(TELLER_ONE_ACCOUNT_IDENTIFIER);
-
- checkStateTransfer(product.getIdentifier(), customerCase.getIdentifier(), Action.APPROVE,
- Collections.singletonList(approveCommandOriginationFeeAccountAssignment),
- APPROVE_INDIVIDUALLOAN_CASE, Case.State.APPROVED);
- checkNextActionsCorrect(product.getIdentifier(), customerCase.getIdentifier(), Action.DISBURSE, Action.CLOSE);
-
- final String pendingDisbursalAccountIdentifier =
- AccountingFixture.verifyAccountCreation(ledgerManager, Fixture.PENDING_DISBURSAL_LEDGER_IDENTIFIER, AccountType.ASSET);
- final String customerLoanAccountIdentifier =
- AccountingFixture.verifyAccountCreation(ledgerManager, Fixture.CUSTOMER_LOAN_LEDGER_IDENTIFIER, AccountType.ASSET);
-
- final Set<Debtor> debtors = new HashSet<>();
- debtors.add(new Debtor(LOAN_FUNDS_SOURCE_ACCOUNT_IDENTIFIER, toString(caseParameters.getMaximumBalance(), product.getMinorCurrencyUnitDigits())));
- debtors.add(new Debtor(TELLER_ONE_ACCOUNT_IDENTIFIER, toString(loanOriginationFee.getAmount(), product.getMinorCurrencyUnitDigits())));
-
- final Set<Creditor> creditors = new HashSet<>();
- creditors.add(new Creditor(pendingDisbursalAccountIdentifier, toString(caseParameters.getMaximumBalance(), product.getMinorCurrencyUnitDigits())));
- creditors.add(new Creditor(LOAN_ORIGINATION_FEES_ACCOUNT_IDENTIFIER, toString(loanOriginationFee.getAmount(), product.getMinorCurrencyUnitDigits())));
- AccountingFixture.verifyTransfer(ledgerManager, debtors, creditors);
- }
-
- private String toString(final BigDecimal bigDecimal, final int scale) {
- return bigDecimal.setScale(scale, BigDecimal.ROUND_UNNECESSARY).toPlainString();
- }
-}
\ No newline at end of file
diff --git a/component-test/src/main/java/io/mifos/portfolio/TestAccountingInteractionInLoanWorkflow.java b/component-test/src/main/java/io/mifos/portfolio/TestAccountingInteractionInLoanWorkflow.java
new file mode 100644
index 0000000..7ec81c9
--- /dev/null
+++ b/component-test/src/main/java/io/mifos/portfolio/TestAccountingInteractionInLoanWorkflow.java
@@ -0,0 +1,157 @@
+/*
+ * Copyright 2017 The Mifos Initiative.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.mifos.portfolio;
+
+import com.google.gson.Gson;
+import io.mifos.accounting.api.v1.domain.AccountType;
+import io.mifos.accounting.api.v1.domain.Creditor;
+import io.mifos.accounting.api.v1.domain.Debtor;
+import io.mifos.individuallending.api.v1.domain.caseinstance.CaseParameters;
+import io.mifos.individuallending.api.v1.domain.product.ChargeIdentifiers;
+import io.mifos.individuallending.api.v1.domain.workflow.Action;
+import io.mifos.portfolio.api.v1.domain.Case;
+import io.mifos.portfolio.api.v1.domain.CostComponent;
+import io.mifos.portfolio.api.v1.domain.Product;
+import io.mifos.portfolio.api.v1.events.EventConstants;
+import org.junit.Assert;
+import org.junit.FixMethodOrder;
+import org.junit.Test;
+import org.junit.runners.MethodSorters;
+
+import java.math.BigDecimal;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+
+import static io.mifos.individuallending.api.v1.events.IndividualLoanEventConstants.*;
+import static io.mifos.portfolio.Fixture.MINOR_CURRENCY_UNIT_DIGITS;
+
+/**
+ * @author Myrle Krantz
+ */
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+public class TestAccountingInteractionInLoanWorkflow extends AbstractPortfolioTest {
+ private static final BigDecimal PROCESSING_FEE_AMOUNT = BigDecimal.valueOf(10_0000, MINOR_CURRENCY_UNIT_DIGITS);
+ private static final BigDecimal LOAN_ORIGINATION_FEE_AMOUNT = BigDecimal.valueOf(100_0000, MINOR_CURRENCY_UNIT_DIGITS);
+ private static final BigDecimal DISBURSEMENT_FEE_AMOUNT = BigDecimal.valueOf(1_0000, MINOR_CURRENCY_UNIT_DIGITS);
+
+ private static Product product;
+ private static Case customerCase;
+ private static CaseParameters caseParameters;
+ private static String pendingDisbursalAccountIdentifier;
+ private static String customerLoanAccountIdentifier;
+
+
+ @Test
+ public void step1CreateProduct() throws InterruptedException {
+ //Create product and set charges to fixed fees.
+ product = createProduct();
+
+ setFeeToFixedValue(product.getIdentifier(), ChargeIdentifiers.PROCESSING_FEE_ID, PROCESSING_FEE_AMOUNT);
+ setFeeToFixedValue(product.getIdentifier(), ChargeIdentifiers.LOAN_ORIGINATION_FEE_ID, LOAN_ORIGINATION_FEE_AMOUNT);
+ setFeeToFixedValue(product.getIdentifier(), ChargeIdentifiers.DISBURSEMENT_FEE_ID, DISBURSEMENT_FEE_AMOUNT);
+
+ portfolioManager.enableProduct(product.getIdentifier(), true);
+ Assert.assertTrue(this.eventRecorder.wait(EventConstants.PUT_PRODUCT_ENABLE, product.getIdentifier()));
+ }
+
+ @Test
+ public void step2CreateCase() throws InterruptedException {
+ //Create case.
+ caseParameters = Fixture.createAdjustedCaseParameters(x -> {
+ });
+ final String caseParametersAsString = new Gson().toJson(caseParameters);
+ 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.
+ @Test
+ public void step3OpenCase() throws InterruptedException {
+ 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);
+ 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,
+ PROCESSING_FEE_AMOUNT
+ );
+ }
+
+
+ //Approve the case, accept a loan origination fee, and prepare to disburse the loan by earmarking the funds.
+ @Test
+ public void step4ApproveCase() throws InterruptedException {
+ 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);
+
+ pendingDisbursalAccountIdentifier =
+ AccountingFixture.verifyAccountCreation(ledgerManager, AccountingFixture.PENDING_DISBURSAL_LEDGER_IDENTIFIER, AccountType.ASSET);
+ customerLoanAccountIdentifier =
+ AccountingFixture.verifyAccountCreation(ledgerManager, AccountingFixture.CUSTOMER_LOAN_LEDGER_IDENTIFIER, AccountType.ASSET);
+
+ final Set<Debtor> debtors = new HashSet<>();
+ debtors.add(new Debtor(AccountingFixture.LOAN_FUNDS_SOURCE_ACCOUNT_IDENTIFIER, caseParameters.getMaximumBalance().toPlainString()));
+ debtors.add(new Debtor(AccountingFixture.TELLER_ONE_ACCOUNT_IDENTIFIER, LOAN_ORIGINATION_FEE_AMOUNT.toPlainString()));
+
+ final Set<Creditor> creditors = new HashSet<>();
+ creditors.add(new Creditor(pendingDisbursalAccountIdentifier, caseParameters.getMaximumBalance().toPlainString()));
+ creditors.add(new Creditor(AccountingFixture.LOAN_ORIGINATION_FEES_ACCOUNT_IDENTIFIER, LOAN_ORIGINATION_FEE_AMOUNT.toPlainString()));
+ AccountingFixture.verifyTransfer(ledgerManager, debtors, creditors);
+ }
+
+ //Approve the case, accept a loan origination fee, and prepare to disburse the loan by earmarking the funds.
+ @Test
+ public void step5DisburseFullAmount() throws InterruptedException {
+ checkStateTransfer(
+ product.getIdentifier(),
+ customerCase.getIdentifier(),
+ Action.DISBURSE,
+ Collections.singletonList(assignEntryToTeller()),
+ DISBURSE_INDIVIDUALLOAN_CASE,
+ 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);
+
+
+ final Set<Debtor> debtors = new HashSet<>();
+ debtors.add(new Debtor(pendingDisbursalAccountIdentifier, caseParameters.getMaximumBalance().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.DISBURSEMENT_FEE_INCOME_ACCOUNT_IDENTIFIER, DISBURSEMENT_FEE_AMOUNT.toPlainString()));
+ AccountingFixture.verifyTransfer(ledgerManager, debtors, creditors);
+
+ }
+}
\ No newline at end of file
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 1112a68..6bfc9f1 100644
--- a/component-test/src/main/java/io/mifos/portfolio/TestCases.java
+++ b/component-test/src/main/java/io/mifos/portfolio/TestCases.java
@@ -132,7 +132,7 @@
final List<CreditWorthinessFactor> debts = caseParameters.getCreditWorthinessSnapshots().get(0).getDebts();
final ArrayList<CreditWorthinessFactor> newDebts = new ArrayList<>();
newDebts.addAll(debts);
- newDebts.add(new CreditWorthinessFactor("boop", BigDecimal.valueOf(5, 4)));
+ newDebts.add(new CreditWorthinessFactor("boop", BigDecimal.valueOf(5, Fixture.MINOR_CURRENCY_UNIT_DIGITS)));
caseParameters.getCreditWorthinessSnapshots().get(0).setDebts(newDebts);
final String changedParameters = new Gson().toJson(caseParameters);
caseInstance.setParameters(changedParameters);
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 22b3b33..dae6f62 100644
--- a/component-test/src/main/java/io/mifos/portfolio/TestCommands.java
+++ b/component-test/src/main/java/io/mifos/portfolio/TestCommands.java
@@ -17,6 +17,7 @@
import io.mifos.individuallending.api.v1.domain.workflow.Action;
import io.mifos.individuallending.api.v1.events.IndividualLoanCommandEvent;
+import io.mifos.portfolio.api.v1.domain.AccountAssignment;
import io.mifos.portfolio.api.v1.domain.Case;
import io.mifos.portfolio.api.v1.domain.Command;
import io.mifos.portfolio.api.v1.domain.Product;
@@ -24,6 +25,7 @@
import org.junit.Test;
import java.util.Collections;
+import java.util.List;
import static io.mifos.individuallending.api.v1.events.IndividualLoanEventConstants.*;
@@ -39,29 +41,64 @@
checkNextActionsCorrect(product.getIdentifier(), customerCase.getIdentifier(), Action.OPEN);
- checkStateTransfer(product.getIdentifier(), customerCase.getIdentifier(), Action.OPEN, null, OPEN_INDIVIDUALLOAN_CASE, Case.State.PENDING);
+ 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, null, APPROVE_INDIVIDUALLOAN_CASE, Case.State.APPROVED);
+ 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, null, DISBURSE_INDIVIDUALLOAN_CASE, Case.State.ACTIVE);
+ checkStateTransfer(
+ product.getIdentifier(),
+ customerCase.getIdentifier(),
+ Action.DISBURSE,
+ Collections.singletonList(assignEntryToTeller()),
+ DISBURSE_INDIVIDUALLOAN_CASE,
+ 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.ACCEPT_PAYMENT, null, ACCEPT_PAYMENT_INDIVIDUALLOAN_CASE, Case.State.ACTIVE);
+ checkStateTransfer(
+ product.getIdentifier(),
+ customerCase.getIdentifier(),
+ Action.ACCEPT_PAYMENT,
+ Collections.singletonList(assignEntryToTeller()),
+ ACCEPT_PAYMENT_INDIVIDUALLOAN_CASE,
+ 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.ACCEPT_PAYMENT, null, ACCEPT_PAYMENT_INDIVIDUALLOAN_CASE, Case.State.ACTIVE);
+ checkStateTransfer(
+ product.getIdentifier(),
+ customerCase.getIdentifier(),
+ Action.ACCEPT_PAYMENT,
+ Collections.singletonList(assignEntryToTeller()),
+ ACCEPT_PAYMENT_INDIVIDUALLOAN_CASE,
+ 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.CLOSE, null, CLOSE_INDIVIDUALLOAN_CASE, Case.State.CLOSED);
+ checkStateTransfer(
+ product.getIdentifier(),
+ customerCase.getIdentifier(),
+ Action.CLOSE,
+ Collections.singletonList(assignEntryToTeller()),
+ CLOSE_INDIVIDUALLOAN_CASE,
+ Case.State.CLOSED);
checkNextActionsCorrect(product.getIdentifier(), customerCase.getIdentifier());
}
@@ -73,19 +110,43 @@
checkNextActionsCorrect(product.getIdentifier(), customerCase.getIdentifier(), Action.OPEN);
- checkStateTransfer(product.getIdentifier(), customerCase.getIdentifier(), Action.OPEN, Collections.emptyList(), OPEN_INDIVIDUALLOAN_CASE, Case.State.PENDING);
+ 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.emptyList(), APPROVE_INDIVIDUALLOAN_CASE, Case.State.APPROVED);
+ 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, Collections.emptyList(), DISBURSE_INDIVIDUALLOAN_CASE, Case.State.ACTIVE);
+ checkStateTransfer(
+ product.getIdentifier(),
+ customerCase.getIdentifier(),
+ Action.DISBURSE,
+ Collections.singletonList(assignEntryToTeller()),
+ DISBURSE_INDIVIDUALLOAN_CASE,
+ 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.emptyList(), WRITE_OFF_INDIVIDUALLOAN_CASE, Case.State.CLOSED);
+ checkStateTransfer(
+ product.getIdentifier(),
+ customerCase.getIdentifier(),
+ Action.WRITE_OFF,
+ Collections.singletonList(assignEntryToTeller()),
+ WRITE_OFF_INDIVIDUALLOAN_CASE,
+ Case.State.CLOSED);
checkNextActionsCorrect(product.getIdentifier(), customerCase.getIdentifier());
}
@@ -94,17 +155,31 @@
final Product product = createAndEnableProduct();
final Case customerCase = createCase(product.getIdentifier());
- checkStateTransfer(product.getIdentifier(), customerCase.getIdentifier(), Action.OPEN, Collections.emptyList(), OPEN_INDIVIDUALLOAN_CASE, Case.State.PENDING);
+ checkStateTransfer(
+ product.getIdentifier(),
+ customerCase.getIdentifier(),
+ Action.OPEN,
+ Collections.singletonList(assignEntryToTeller()),
+ OPEN_INDIVIDUALLOAN_CASE,
+ Case.State.PENDING);
- checkStateTransferFails(product.getIdentifier(), customerCase.getIdentifier(), Action.DISBURSE, DISBURSE_INDIVIDUALLOAN_CASE, Case.State.PENDING);
+ checkStateTransferFails(
+ product.getIdentifier(),
+ customerCase.getIdentifier(),
+ Action.DISBURSE,
+ Collections.singletonList(assignEntryToTeller()),
+ DISBURSE_INDIVIDUALLOAN_CASE,
+ Case.State.PENDING);
}
public void checkStateTransferFails(final String productIdentifier,
- final String caseIdentifier,
- final Action action,
- final String event,
- final Case.State initialState) throws InterruptedException {
+ final String caseIdentifier,
+ final Action action,
+ final List<AccountAssignment> oneTimeAccountAssignments,
+ final String event,
+ final Case.State initialState) throws InterruptedException {
final Command command = new Command();
+ command.setOneTimeAccountAssignments(oneTimeAccountAssignments);
try {
portfolioManager.executeCaseCommand(productIdentifier, caseIdentifier, action.name(), command);
Assert.fail();
diff --git a/service/src/main/java/io/mifos/individuallending/IndividualLendingPatternFactory.java b/service/src/main/java/io/mifos/individuallending/IndividualLendingPatternFactory.java
index 6591023..14aa235 100644
--- a/service/src/main/java/io/mifos/individuallending/IndividualLendingPatternFactory.java
+++ b/service/src/main/java/io/mifos/individuallending/IndividualLendingPatternFactory.java
@@ -246,10 +246,10 @@
}
@Override
- public Optional<String> getParameters(final Long caseId) {
+ public Optional<String> getParameters(final Long caseId, final int minorCurrencyUnitDigits) {
return caseParametersRepository
.findByCaseId(caseId)
- .map(CaseParametersMapper::mapEntity)
+ .map(x -> CaseParametersMapper.mapEntity(x, minorCurrencyUnitDigits))
.map(gson::toJson);
}
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 6f65ccb..467c25a 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
@@ -22,6 +22,7 @@
import io.mifos.core.command.annotation.EventEmitter;
import io.mifos.core.lang.ServiceException;
import io.mifos.individuallending.IndividualLendingPatternFactory;
+import io.mifos.individuallending.api.v1.domain.product.AccountDesignators;
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;
@@ -42,6 +43,7 @@
import java.time.ZoneId;
import java.util.List;
import java.util.stream.Collectors;
+import java.util.stream.Stream;
/**
* @author Myrle Krantz
@@ -163,10 +165,36 @@
public IndividualLoanCommandEvent process(final DisburseCommand command) {
final String productIdentifier = command.getProductIdentifier();
final String caseIdentifier = command.getCaseIdentifier();
- final DataContextOfAction dataContextOfAction = costComponentService.checkedGetDataContext(productIdentifier, caseIdentifier, command.getCommand().getOneTimeAccountAssignments());
+ final DataContextOfAction dataContextOfAction = costComponentService.checkedGetDataContext(
+ productIdentifier, caseIdentifier, command.getCommand().getOneTimeAccountAssignments());
checkActionCanBeExecuted(Case.State.valueOf(dataContextOfAction.getCustomerCase().getCurrentState()), Action.DISBURSE);
+
+
+ final CostComponentsForRepaymentPeriod costComponentsForRepaymentPeriod =
+ individualLoanService.getCostComponentsForRepaymentPeriod(productIdentifier, dataContextOfAction.getCaseParameters(), BigDecimal.ZERO, Action.DISBURSE, today(), LocalDate.now());
+
+
+ final DesignatorToAccountIdentifierMapper designatorToAccountIdentifierMapper
+ = new DesignatorToAccountIdentifierMapper(dataContextOfAction);
+
+ final List<ChargeInstance> charges = Stream.concat(costComponentsForRepaymentPeriod.stream().map(x -> new ChargeInstance(
+ designatorToAccountIdentifierMapper.mapOrThrow(x.getKey().getFromAccountDesignator()),
+ designatorToAccountIdentifierMapper.mapOrThrow(x.getKey().getToAccountDesignator()),
+ x.getValue().getAmount())),
+ Stream.of(new ChargeInstance(
+ designatorToAccountIdentifierMapper.mapOrThrow(AccountDesignators.PENDING_DISBURSAL),
+ designatorToAccountIdentifierMapper.mapOrThrow(AccountDesignators.CUSTOMER_LOAN),
+ dataContextOfAction.getCaseParameters().getMaximumBalance())))
+ .collect(Collectors.toList());
+
+ accountingAdapter.bookCharges(charges,
+ command.getCommand().getNote(),
+ productIdentifier + "." + caseIdentifier + "." + Action.DISBURSE.name(),
+ Action.DISBURSE.getTransactionType());
+ //Only move to new state if book charges command was accepted.
updateCaseState(dataContextOfAction.getCustomerCase(), Case.State.ACTIVE);
- return new IndividualLoanCommandEvent(command.getProductIdentifier(), command.getCaseIdentifier(), "x");
+
+ return new IndividualLoanCommandEvent(productIdentifier, caseIdentifier, Action.DISBURSE.name());
}
@Transactional
diff --git a/service/src/main/java/io/mifos/individuallending/internal/mapper/CaseParametersMapper.java b/service/src/main/java/io/mifos/individuallending/internal/mapper/CaseParametersMapper.java
index 6be2ef8..1655266 100644
--- a/service/src/main/java/io/mifos/individuallending/internal/mapper/CaseParametersMapper.java
+++ b/service/src/main/java/io/mifos/individuallending/internal/mapper/CaseParametersMapper.java
@@ -25,10 +25,7 @@
import io.mifos.portfolio.api.v1.domain.TermRange;
import java.math.BigDecimal;
-import java.util.Collections;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
+import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;
@@ -139,37 +136,40 @@
return ret;
}
- public static CaseParameters mapEntity(final CaseParametersEntity caseParametersEntity) {
+ public static CaseParameters mapEntity(final CaseParametersEntity caseParametersEntity,
+ final int minorCurrencyUnitDigits) {
final CaseParameters ret = new CaseParameters();
ret.setCustomerIdentifier(caseParametersEntity.getCustomerIdentifier());
- ret.setCreditWorthinessSnapshots(mapFactorsToSnapshots(caseParametersEntity.getCreditWorthinessFactors()));
+ ret.setCreditWorthinessSnapshots(mapFactorsToSnapshots(caseParametersEntity.getCreditWorthinessFactors(), minorCurrencyUnitDigits));
ret.setTermRange(getTermRange(caseParametersEntity));
- ret.setMaximumBalance(caseParametersEntity.getBalanceRangeMaximum());
+ ret.setMaximumBalance(caseParametersEntity.getBalanceRangeMaximum().setScale(minorCurrencyUnitDigits, BigDecimal.ROUND_HALF_EVEN));
ret.setPaymentCycle(getPaymentCycle(caseParametersEntity));
return ret;
}
private static List<CreditWorthinessSnapshot> mapFactorsToSnapshots(
- final Set<CaseCreditWorthinessFactorEntity> creditWorthinessFactors) {
+ final Set<CaseCreditWorthinessFactorEntity> creditWorthinessFactors,
+ final int minorCurrencyUnitDigits) {
final Map<Integer, Set<CaseCreditWorthinessFactorEntity>> groupedByCustomerId
= creditWorthinessFactors.stream()
.collect(Collectors.groupingBy(CaseCreditWorthinessFactorEntity::getPositionInCustomers, Collectors.toSet()));
return groupedByCustomerId.entrySet().stream()
- .sorted((x, y) -> Integer.compare(x.getKey(), y.getKey()))
- .map(CaseParametersMapper::mapEntryToSnapshot).collect(Collectors.toList());
+ .sorted(Comparator.comparingInt(Map.Entry::getKey))
+ .map(customerEntry -> mapEntryToSnapshot(customerEntry, minorCurrencyUnitDigits)).collect(Collectors.toList());
}
private static CreditWorthinessSnapshot mapEntryToSnapshot(
- final Map.Entry<Integer, Set<CaseCreditWorthinessFactorEntity>> customerEntry) {
+ final Map.Entry<Integer, Set<CaseCreditWorthinessFactorEntity>> customerEntry,
+ final int minorCurrencyUnitDigits) {
final CreditWorthinessSnapshot ret = new CreditWorthinessSnapshot();
final Map<CreditWorthinessFactorType, Set<CaseCreditWorthinessFactorEntity>> groupedByFactorType
= customerEntry.getValue().stream()
.collect(Collectors.groupingBy(CaseCreditWorthinessFactorEntity::getFactorType, Collectors.toSet()));
- ret.setAssets(getFactorsByType(groupedByFactorType, CreditWorthinessFactorType.ASSET));
- ret.setDebts(getFactorsByType(groupedByFactorType, CreditWorthinessFactorType.DEBT));
- ret.setIncomeSources(getFactorsByType(groupedByFactorType, CreditWorthinessFactorType.INCOME_SOURCE));
+ ret.setAssets(getFactorsByType(groupedByFactorType, CreditWorthinessFactorType.ASSET, minorCurrencyUnitDigits));
+ ret.setDebts(getFactorsByType(groupedByFactorType, CreditWorthinessFactorType.DEBT, minorCurrencyUnitDigits));
+ ret.setIncomeSources(getFactorsByType(groupedByFactorType, CreditWorthinessFactorType.INCOME_SOURCE, minorCurrencyUnitDigits));
final String customerId = customerEntry.getValue().stream()
.findFirst()
@@ -181,25 +181,27 @@
}
private static List<CreditWorthinessFactor> getFactorsByType(
- final Map<CreditWorthinessFactorType, Set<CaseCreditWorthinessFactorEntity>> groupedByFactorType,
- final CreditWorthinessFactorType factorType) {
+ final Map<CreditWorthinessFactorType, Set<CaseCreditWorthinessFactorEntity>> groupedByFactorType,
+ final CreditWorthinessFactorType factorType,
+ final int minorCurrencyUnitDigits) {
final Set<CaseCreditWorthinessFactorEntity> byFactorType = groupedByFactorType.get(factorType);
if (byFactorType == null)
return Collections.emptyList();
else {
return byFactorType.stream()
- .sorted((x, y) -> Integer.compare(x.getPositionInFactor(), y.getPositionInFactor()))
- .map(CaseParametersMapper::mapEntryToFactor)
+ .sorted(Comparator.comparingInt(CaseCreditWorthinessFactorEntity::getPositionInFactor))
+ .map(caseCreditWorthinessFactorEntity -> mapEntryToFactor(caseCreditWorthinessFactorEntity, minorCurrencyUnitDigits))
.collect(Collectors.toList());
}
}
private static CreditWorthinessFactor mapEntryToFactor(
- final CaseCreditWorthinessFactorEntity caseCreditWorthinessFactorEntity) {
+ final CaseCreditWorthinessFactorEntity caseCreditWorthinessFactorEntity,
+ final int minorCurrencyUnitDigits) {
final CreditWorthinessFactor ret = new CreditWorthinessFactor();
ret.setDescription(caseCreditWorthinessFactorEntity.getDescription());
- ret.setAmount(caseCreditWorthinessFactorEntity.getAmount());
+ ret.setAmount(caseCreditWorthinessFactorEntity.getAmount().setScale(minorCurrencyUnitDigits, BigDecimal.ROUND_HALF_EVEN));
return ret;
}
}
diff --git a/service/src/main/java/io/mifos/individuallending/internal/service/CaseParametersService.java b/service/src/main/java/io/mifos/individuallending/internal/service/CaseParametersService.java
index 1a48f2b..b5d3710 100644
--- a/service/src/main/java/io/mifos/individuallending/internal/service/CaseParametersService.java
+++ b/service/src/main/java/io/mifos/individuallending/internal/service/CaseParametersService.java
@@ -62,7 +62,7 @@
.findByProductIdentifierAndIdentifier(productIdentifier, caseIdentifier)
.map(CaseEntity::getId)
.flatMap(caseParametersRepository::findByCaseId)
- .map(CaseParametersMapper::mapEntity);
+ .map(x -> CaseParametersMapper.mapEntity(x, 4));
}
public CasePage findByCustomerIdentifier(
@@ -79,7 +79,7 @@
private List<Case> mapList(final List<CaseParametersEntity> in) {
return in.stream()
- .map(x -> CaseMapper.map(caseRepository.findOne(x.getCaseId()), gson.toJson(CaseParametersMapper.mapEntity(x))))
+ .map(x -> CaseMapper.map(caseRepository.findOne(x.getCaseId()), gson.toJson(CaseParametersMapper.mapEntity(x, 4))))
.collect(Collectors.toList());
}
}
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 1376692..f3b4ade 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
@@ -80,7 +80,7 @@
final CaseParameters caseParameters =
caseParametersRepository.findByCaseId(customerCase.getId())
- .map(CaseParametersMapper::mapEntity)
+ .map(x -> CaseParametersMapper.mapEntity(x, product.getMinorCurrencyUnitDigits()))
.orElseThrow(() -> ServiceException.notFound(
"Individual loan not found ''{0}.{1}''.",
productIdentifier, caseIdentifier));
diff --git a/service/src/main/java/io/mifos/individuallending/internal/service/ScheduledActionService.java b/service/src/main/java/io/mifos/individuallending/internal/service/ScheduledActionService.java
index 0e3c3f6..87a126e 100644
--- a/service/src/main/java/io/mifos/individuallending/internal/service/ScheduledActionService.java
+++ b/service/src/main/java/io/mifos/individuallending/internal/service/ScheduledActionService.java
@@ -56,10 +56,11 @@
final Period lastPeriod = repaymentPeriods.last();
return Stream.concat(Stream.of(
- new ScheduledAction(Action.OPEN, initialDisbursalDate, firstPeriod, firstPeriod),
- new ScheduledAction(Action.APPROVE, initialDisbursalDate, firstPeriod, firstPeriod)),
- Stream.concat(repaymentPeriods.stream().flatMap(this::generateScheduledActionsForRepaymentPeriod),
- Stream.of(new ScheduledAction(Action.CLOSE, lastPeriod.getEndDate(), lastPeriod, lastPeriod))));
+ new ScheduledAction(Action.OPEN, initialDisbursalDate, firstPeriod, firstPeriod),
+ new ScheduledAction(Action.APPROVE, initialDisbursalDate, firstPeriod, firstPeriod),
+ new ScheduledAction(Action.DISBURSE, initialDisbursalDate, firstPeriod, firstPeriod)),
+ Stream.concat(repaymentPeriods.stream().flatMap(this::generateScheduledActionsForRepaymentPeriod),
+ Stream.of(new ScheduledAction(Action.CLOSE, lastPeriod.getEndDate(), lastPeriod, lastPeriod))));
}
private LocalDate getEndDate(final @Nonnull CaseParameters caseParameters,
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 a6b96e4..9250ee0 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
@@ -72,13 +72,15 @@
final List<String> currentStates = currentStatesStream.map(Enum::name).collect(Collectors.toList());
final Page<CaseEntity> ret = caseRepository.findByProductIdentifierAndCurrentStateIn(productIdentifier, currentStates, pageRequest);
+ final int minorCurrencyUnitDigits = getMinorCurrencyUnitDigits(productIdentifier);
- return new CasePage(mapList(ret.getContent()), ret.getTotalPages(), ret.getTotalElements());
+ return new CasePage(mapList(ret.getContent(), minorCurrencyUnitDigits), ret.getTotalPages(), ret.getTotalElements());
}
- private List<Case> mapList(final List<CaseEntity> in) {
+ private List<Case> mapList(final List<CaseEntity> in,
+ final int minorCurrencyUnitDigits) {
return in.stream()
- .map(this::map)
+ .map(caseEntity -> map(caseEntity, minorCurrencyUnitDigits))
.filter(Optional::isPresent)
.map(Optional::get)
.collect(Collectors.toList());
@@ -86,8 +88,9 @@
public Optional<Case> findByIdentifier(final String productIdentifier, final String caseIdentifier)
{
+ final int minorCurrencyUnitDigits = getMinorCurrencyUnitDigits(productIdentifier);
return caseRepository.findByProductIdentifierAndIdentifier(productIdentifier, caseIdentifier)
- .flatMap(this::map);
+ .flatMap(caseEntity -> map(caseEntity, minorCurrencyUnitDigits));
}
public Set<String> getNextActionsForCase(final String productIdentifier, final String caseIdentifier) {
@@ -95,22 +98,22 @@
return caseRepository.findByProductIdentifierAndIdentifier(productIdentifier, caseIdentifier)
.map(x -> pattern.getNextActionsForState(Case.State.valueOf(x.getCurrentState())))
- .orElseThrow(() -> ServiceException.notFound("Case with identifier " + productIdentifier + "." + caseIdentifier + " doesn't exist."));
+ .orElseThrow(() -> ServiceException.notFound("Case with identifier ''" + productIdentifier + "." + caseIdentifier + "'' doesn''t exist."));
}
public ProductCommandDispatcher getProductCommandDispatcher(final String productIdentifier) {
return getPatternFactoryOrThrow(productIdentifier).getIndividualLendingCommandDispatcher();
}
- private Optional<Case> map(final CaseEntity caseEntity) {
+ private Optional<Case> map(final CaseEntity caseEntity, final int minorCurrencyUnitDigits) {
return getPatternFactory(caseEntity.getProductIdentifier())
- .flatMap(x -> x.getParameters(caseEntity.getId()))
+ .flatMap(x -> x.getParameters(caseEntity.getId(), minorCurrencyUnitDigits))
.map(x -> CaseMapper.map(caseEntity, x));
}
private PatternFactory getPatternFactoryOrThrow(final String productIdentifier) {
return getPatternFactory(productIdentifier)
- .orElseThrow(() -> ServiceException.notFound("Product with identifier " + productIdentifier + " doesn't exist."));
+ .orElseThrow(() -> ServiceException.notFound("Product with identifier ''" + productIdentifier + "'' doesn''t exist."));
}
private Optional<PatternFactory> getPatternFactory(final String productIdentifier) {
@@ -129,4 +132,10 @@
return getPatternFactoryOrThrow(productIdentifier)
.getCostComponentsForAction(productIdentifier, caseIdentifier, actionIdentifier);
}
+
+ private int getMinorCurrencyUnitDigits(final String productIdentifier) {
+ return productRepository.findByIdentifier(productIdentifier)
+ .map(ProductEntity::getMinorCurrencyUnitDigits)
+ .orElse(4);
+ }
}
\ No newline at end of file
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 74a0f16..a035aa1 100644
--- a/service/src/main/java/io/mifos/products/spi/PatternFactory.java
+++ b/service/src/main/java/io/mifos/products/spi/PatternFactory.java
@@ -33,7 +33,7 @@
List<ChargeDefinition> charges();
void persistParameters(Long caseId, String parameters);
void changeParameters(Long caseId, String parameters);
- Optional<String> getParameters(Long caseId);
+ Optional<String> getParameters(Long caseId, int minorCurrencyUnitDigits);
Set<String> getNextActionsForState(Case.State state);
List<CostComponent> getCostComponentsForAction(String productIdentifier, String caseIdentifier, String actionIdentifier);
ProductCommandDispatcher getIndividualLendingCommandDispatcher();