Setting start and end of term and payment size correctly for imported loans.
diff --git a/api/src/main/java/io/mifos/portfolio/api/v1/PermittableGroupIds.java b/api/src/main/java/io/mifos/portfolio/api/v1/PermittableGroupIds.java
index 05d282b..fbe7949 100644
--- a/api/src/main/java/io/mifos/portfolio/api/v1/PermittableGroupIds.java
+++ b/api/src/main/java/io/mifos/portfolio/api/v1/PermittableGroupIds.java
@@ -25,5 +25,5 @@
String PRODUCT_MANAGEMENT = "portfolio__v1__products";
String CASE_MANAGEMENT = "portfolio__v1__case";
String CASE_DOCUMENT_MANAGEMENT = "portfolio__v1__case_documents";
- String CASE_IMPORT = "portfolio__v1__case_import";
+ String CASE_STATUS = "portfolio__v1__case_status";
}
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 a5da9a3..ab593a7 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
@@ -308,6 +308,15 @@
@PathVariable("caseidentifier") final String caseIdentifier);
@RequestMapping(
+ value = "/products/{productidentifier}/cases/{caseidentifier}/status",
+ method = RequestMethod.GET,
+ produces = MediaType.ALL_VALUE,
+ consumes = MediaType.APPLICATION_JSON_VALUE)
+ CaseStatus getCaseStatus(
+ @PathVariable("productidentifier") final String productIdentifier,
+ @PathVariable("caseidentifier") final String caseIdentifier);
+
+ @RequestMapping(
value = "/products/{productidentifier}/cases/{caseidentifier}",
method = RequestMethod.PUT,
produces = MediaType.APPLICATION_JSON_VALUE,
diff --git a/api/src/main/java/io/mifos/portfolio/api/v1/domain/CaseStatus.java b/api/src/main/java/io/mifos/portfolio/api/v1/domain/CaseStatus.java
new file mode 100644
index 0000000..3aa0e48
--- /dev/null
+++ b/api/src/main/java/io/mifos/portfolio/api/v1/domain/CaseStatus.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright 2017 Kuelap, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.mifos.portfolio.api.v1.domain;
+
+import java.util.Objects;
+
+/**
+ * @author Myrle Krantz
+ */
+public class CaseStatus {
+ private String startOfTerm;
+ private String endOfTerm;
+ private Case.State currentState; //The same as Case.currentState.
+
+ public CaseStatus() {
+ }
+
+ public String getStartOfTerm() {
+ return startOfTerm;
+ }
+
+ public void setStartOfTerm(String startOfTerm) {
+ this.startOfTerm = startOfTerm;
+ }
+
+ public String getEndOfTerm() {
+ return endOfTerm;
+ }
+
+ public void setEndOfTerm(String endOfTerm) {
+ this.endOfTerm = endOfTerm;
+ }
+
+ public String getCurrentState() {
+ return currentState == null ? null : currentState.name();
+ }
+
+ public void setCurrentState(String currentState) {
+ this.currentState = Case.State.valueOf(currentState);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ CaseStatus that = (CaseStatus) o;
+ return Objects.equals(startOfTerm, that.startOfTerm) &&
+ Objects.equals(endOfTerm, that.endOfTerm) &&
+ currentState == that.currentState;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(startOfTerm, endOfTerm, currentState);
+ }
+
+ @Override
+ public String toString() {
+ return "CaseStatus{" +
+ "startOfTerm='" + startOfTerm + '\'' +
+ ", endOfTerm='" + endOfTerm + '\'' +
+ ", currentState=" + currentState +
+ '}';
+ }
+}
diff --git a/api/src/main/java/io/mifos/portfolio/api/v1/domain/ImportParameters.java b/api/src/main/java/io/mifos/portfolio/api/v1/domain/ImportParameters.java
index a301d8e..1a80bdf 100644
--- a/api/src/main/java/io/mifos/portfolio/api/v1/domain/ImportParameters.java
+++ b/api/src/main/java/io/mifos/portfolio/api/v1/domain/ImportParameters.java
@@ -22,6 +22,7 @@
import javax.validation.constraints.NotNull;
import java.math.BigDecimal;
import java.util.List;
+import java.util.Map;
import java.util.Objects;
/**
@@ -37,8 +38,7 @@
private BigDecimal paymentSize;
@Valid
- @NotNull
- private BigDecimal currentBalance;
+ private Map<String, BigDecimal> currentBalances;
@ValidLocalDateTimeString
@NotNull
@@ -70,13 +70,12 @@
this.paymentSize = paymentSize;
}
- @Nullable
- public BigDecimal getCurrentBalance() {
- return currentBalance;
+ public Map<String, BigDecimal> getCurrentBalances() {
+ return currentBalances;
}
- public void setCurrentBalance(@Nullable BigDecimal currentBalance) {
- this.currentBalance = currentBalance;
+ public void setCurrentBalances(Map<String, BigDecimal> currentBalances) {
+ this.currentBalances = currentBalances;
}
public String getStartOfTerm() {
@@ -110,13 +109,13 @@
ImportParameters that = (ImportParameters) o;
return Objects.equals(caseAccountAssignments, that.caseAccountAssignments) &&
Objects.equals(paymentSize, that.paymentSize) &&
- Objects.equals(currentBalance, that.currentBalance) &&
+ Objects.equals(currentBalances, that.currentBalances) &&
Objects.equals(startOfTerm, that.startOfTerm);
}
@Override
public int hashCode() {
- return Objects.hash(caseAccountAssignments, paymentSize, currentBalance, startOfTerm);
+ return Objects.hash(caseAccountAssignments, paymentSize, currentBalances, startOfTerm);
}
@Override
@@ -124,7 +123,7 @@
return "ImportParameters{" +
"caseAccountAssignments=" + caseAccountAssignments +
", paymentSize=" + paymentSize +
- ", currentBalance=" + currentBalance +
+ ", currentBalances=" + currentBalances +
", startOfTerm='" + startOfTerm + '\'' +
", createdOn='" + createdOn + '\'' +
", createdBy='" + createdBy + '\'' +
diff --git a/api/src/test/java/io/mifos/portfolio/api/v1/domain/ImportParametersTest.java b/api/src/test/java/io/mifos/portfolio/api/v1/domain/ImportParametersTest.java
index 81ebf11..408ba16 100644
--- a/api/src/test/java/io/mifos/portfolio/api/v1/domain/ImportParametersTest.java
+++ b/api/src/test/java/io/mifos/portfolio/api/v1/domain/ImportParametersTest.java
@@ -18,6 +18,7 @@
import io.mifos.core.lang.DateConverter;
import io.mifos.core.test.domain.ValidationTest;
import io.mifos.core.test.domain.ValidationTestCase;
+import io.mifos.individuallending.api.v1.domain.product.AccountDesignators;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
@@ -44,7 +45,7 @@
final ImportParameters ret = new ImportParameters();
ret.setCaseAccountAssignments(Collections.emptyList());
ret.setPaymentSize(BigDecimal.TEN);
- ret.setCurrentBalance(BigDecimal.TEN.multiply(BigDecimal.TEN));
+ ret.setCurrentBalances(Collections.singletonMap(AccountDesignators.CUSTOMER_LOAN_PRINCIPAL, BigDecimal.TEN.multiply(BigDecimal.TEN)));
ret.setStartOfTerm(DateConverter.toIsoString(LocalDateTime.now(Clock.systemUTC())));
ret.setCreatedOn(DateConverter.toIsoString(LocalDateTime.now(Clock.systemUTC())));
return ret;
@@ -61,8 +62,8 @@
.adjustment(x -> x.setPaymentSize(null))
.valid(false));
ret.add(new ValidationTestCase<ImportParameters>("nullCurrentBalance")
- .adjustment(x -> x.setCurrentBalance(null))
- .valid(false));
+ .adjustment(x -> x.setCurrentBalances(null))
+ .valid(true));
return ret;
}
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 4665c8b..16f932a 100644
--- a/component-test/src/main/java/io/mifos/portfolio/AccountingFixture.java
+++ b/component-test/src/main/java/io/mifos/portfolio/AccountingFixture.java
@@ -307,15 +307,18 @@
private final String ledgerIdentifer;
private final String accountDesignator;
private final AccountType type;
+ private final BigDecimal balance;
private Account matchedArgument;
private AccountMatcher(
final String ledgerIdentifier,
final String accountDesignator,
- final AccountType type) {
+ final AccountType type,
+ final BigDecimal balance) {
this.ledgerIdentifer = ledgerIdentifier;
this.accountDesignator = accountDesignator;
this.type = type;
+ this.balance = balance;
this.matchedArgument = null; //Set when matches called and returns true.
}
@@ -331,7 +334,7 @@
final boolean ret = checkedArgument.getLedger().equals(ledgerIdentifer) &&
checkedArgument.getIdentifier().contains(accountDesignator) &&
checkedArgument.getType().equals(type.name()) &&
- checkedArgument.getBalance() == 0.0;
+ checkedArgument.getBalance().compareTo(balance.doubleValue()) == 0;
if (ret)
matchedArgument = checkedArgument;
@@ -354,6 +357,7 @@
"ledgerIdentifer='" + ledgerIdentifer + '\'' +
", accountDesignator='" + accountDesignator + '\'' +
", type=" + type +
+ ", balance=" + balance +
'}';
}
}
@@ -590,8 +594,9 @@
final LedgerManager ledgerManager,
final String ledgerIdentifier,
final String accountDesignator,
- final AccountType type) {
- final AccountMatcher specifiesCorrectAccount = new AccountMatcher(ledgerIdentifier, accountDesignator, type);
+ final AccountType type,
+ final BigDecimal balance) {
+ final AccountMatcher specifiesCorrectAccount = new AccountMatcher(ledgerIdentifier, accountDesignator, type, balance);
Mockito.verify(ledgerManager).createAccount(AdditionalMatchers.and(argThat(isValid()), argThat(specifiesCorrectAccount)));
return specifiesCorrectAccount.getMatchedArgument().getIdentifier();
}
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 15ab9f4..e4489ff 100644
--- a/component-test/src/main/java/io/mifos/portfolio/TestAccountingInteractionInLoanWorkflow.java
+++ b/component-test/src/main/java/io/mifos/portfolio/TestAccountingInteractionInLoanWorkflow.java
@@ -96,10 +96,9 @@
@Test
public void workflowImportingLoanFromExternalSystem() throws InterruptedException {
- final LocalDateTime today = midnightToday();
step1CreateProduct();
step2CreateCase();
- step3IImportCase(today);
+ step3IImportCase(LocalDateTime.of(2017,11,8,0,0));
}
@Test
@@ -474,22 +473,46 @@
private void step3IImportCase(final LocalDateTime forDateTime) throws InterruptedException {
logger.info("step3IImportCase");
+ final BigDecimal expectedNextRepaymentAmount = BigDecimal.valueOf(20_00, MINOR_CURRENCY_UNIT_DIGITS);
+ final BigDecimal currentPrincipal = BigDecimal.valueOf(2_000_00, MINOR_CURRENCY_UNIT_DIGITS);
+
final ImportParameters importParameters = new ImportParameters();
importParameters.setCaseAccountAssignments(Collections.singletonList(assignEntryToTeller()));
- importParameters.setPaymentSize(BigDecimal.valueOf(20_00, MINOR_CURRENCY_UNIT_DIGITS));
+ importParameters.setPaymentSize(expectedNextRepaymentAmount);
importParameters.setCreatedOn(DateConverter.toIsoString(forDateTime));
- importParameters.setCurrentBalance(BigDecimal.valueOf(2_000_00, MINOR_CURRENCY_UNIT_DIGITS));
+ importParameters.setCurrentBalances(Collections.singletonMap(AccountDesignators.CUSTOMER_LOAN_PRINCIPAL, currentPrincipal));
importParameters.setStartOfTerm(DateConverter.toIsoString(forDateTime));
portfolioManager.executeImportCommand(product.getIdentifier(), customerCase.getIdentifier(), importParameters);
Assert.assertTrue(eventRecorder.wait(IndividualLoanEventConstants.IMPORT_INDIVIDUALLOAN_CASE, new IndividualLoanCommandEvent(product.getIdentifier(), customerCase.getIdentifier(), DateConverter.toIsoString(forDateTime))));
- //TODO: check payment size, start of term, and end of term and account assignments.
+ final String customerLoanLedgerIdentifier = AccountingFixture.verifyLedgerCreation(
+ ledgerManager,
+ AccountingFixture.CUSTOMER_LOAN_LEDGER_IDENTIFIER,
+ AccountType.ASSET);
- final Case changedCase = portfolioManager.getCase(product.getIdentifier(), customerCase.getIdentifier());
- Assert.assertEquals(Case.State.ACTIVE.name(), changedCase.getCurrentState());
+ customerLoanPrincipalIdentifier =
+ AccountingFixture.verifyAccountCreationMatchingDesignator(ledgerManager, customerLoanLedgerIdentifier, AccountDesignators.CUSTOMER_LOAN_PRINCIPAL, AccountType.ASSET, currentPrincipal);
+ customerLoanInterestIdentifier =
+ AccountingFixture.verifyAccountCreationMatchingDesignator(ledgerManager, customerLoanLedgerIdentifier, AccountDesignators.CUSTOMER_LOAN_INTEREST, AccountType.ASSET, BigDecimal.ZERO);
+ customerLoanFeeIdentifier =
+ AccountingFixture.verifyAccountCreationMatchingDesignator(ledgerManager, customerLoanLedgerIdentifier, AccountDesignators.CUSTOMER_LOAN_FEES, AccountType.ASSET, BigDecimal.ZERO);
+
+ final CaseStatus changedCaseStatus = portfolioManager.getCaseStatus(product.getIdentifier(), customerCase.getIdentifier());
+ Assert.assertEquals(Case.State.ACTIVE.name(), changedCaseStatus.getCurrentState());
+ Assert.assertEquals(DateConverter.toIsoString(forDateTime), changedCaseStatus.getStartOfTerm());
+ Assert.assertEquals("2018-02-08T00:00:00Z", changedCaseStatus.getEndOfTerm());
checkNextActionsCorrect(product.getIdentifier(), customerCase.getIdentifier(),
Action.APPLY_INTEREST, Action.MARK_LATE, Action.ACCEPT_PAYMENT, Action.DISBURSE, Action.MARK_IN_ARREARS, Action.WRITE_OFF, Action.CLOSE);
+
+ expectedCurrentPrincipal = currentPrincipal;
+ updateBalanceMock();
+
+ final BigDecimal nextRepaymentAmount = findNextRepaymentAmount(forDateTime);
+ Assert.assertEquals(expectedNextRepaymentAmount, nextRepaymentAmount);
+
+
+ //TODO: check account assignments.
}
@@ -547,11 +570,11 @@
AccountType.ASSET);
customerLoanPrincipalIdentifier =
- AccountingFixture.verifyAccountCreationMatchingDesignator(ledgerManager, customerLoanLedgerIdentifier, AccountDesignators.CUSTOMER_LOAN_PRINCIPAL, AccountType.ASSET);
+ AccountingFixture.verifyAccountCreationMatchingDesignator(ledgerManager, customerLoanLedgerIdentifier, AccountDesignators.CUSTOMER_LOAN_PRINCIPAL, AccountType.ASSET, BigDecimal.ZERO);
customerLoanInterestIdentifier =
- AccountingFixture.verifyAccountCreationMatchingDesignator(ledgerManager, customerLoanLedgerIdentifier, AccountDesignators.CUSTOMER_LOAN_INTEREST, AccountType.ASSET);
+ AccountingFixture.verifyAccountCreationMatchingDesignator(ledgerManager, customerLoanLedgerIdentifier, AccountDesignators.CUSTOMER_LOAN_INTEREST, AccountType.ASSET, BigDecimal.ZERO);
customerLoanFeeIdentifier =
- AccountingFixture.verifyAccountCreationMatchingDesignator(ledgerManager, customerLoanLedgerIdentifier, AccountDesignators.CUSTOMER_LOAN_FEES, AccountType.ASSET);
+ AccountingFixture.verifyAccountCreationMatchingDesignator(ledgerManager, customerLoanLedgerIdentifier, AccountDesignators.CUSTOMER_LOAN_FEES, AccountType.ASSET, BigDecimal.ZERO);
expectedCurrentPrincipal = BigDecimal.ZERO;
interestAccrued = BigDecimal.ZERO;
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 3e1216f..e9db1cf 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
@@ -49,6 +49,7 @@
import javax.annotation.Nullable;
import java.math.BigDecimal;
+import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.util.Collections;
@@ -174,15 +175,19 @@
@EventEmitter(
selectorName = IndividualLoanEventConstants.SELECTOR_NAME,
selectorValue = IndividualLoanEventConstants.IMPORT_INDIVIDUALLOAN_CASE)
- public IndividualLoanCommandEvent process(final ImportCommand command) {
+ public IndividualLoanCommandEvent process(final ImportCommand command) throws InterruptedException {
final String productIdentifier = command.getProductIdentifier();
final String caseIdentifier = command.getCaseIdentifier();
final DataContextOfAction dataContextOfAction = dataContextService.checkedGetDataContext(
productIdentifier, caseIdentifier, command.getImportParameters().getCaseAccountAssignments());
- IndividualLendingPatternFactory.checkActionCanBeExecuted(Case.State.valueOf(dataContextOfAction.getCustomerCaseEntity().getCurrentState()), Action.OPEN);
+ IndividualLendingPatternFactory.checkActionCanBeExecuted(Case.State.valueOf(dataContextOfAction.getCustomerCaseEntity().getCurrentState()), Action.IMPORT);
checkIfTasksAreOutstanding(dataContextOfAction, Action.IMPORT);
+ final DesignatorToAccountIdentifierMapper designatorToAccountIdentifierMapper
+ = new DesignatorToAccountIdentifierMapper(dataContextOfAction);
+ createAccounts(dataContextOfAction, designatorToAccountIdentifierMapper, command.getImportParameters().getCurrentBalances());
+
final CaseEntity customerCase = dataContextOfAction.getCustomerCaseEntity();
recordCommand(
@@ -191,14 +196,19 @@
Action.IMPORT,
Optional.empty());
+
+ final LocalDate startOfTerm = DateConverter.fromIsoString(command.getImportParameters().getStartOfTerm()).toLocalDate();
+ final LocalDateTime endOfTerm = ScheduledActionHelpers.getRoughEndDate(startOfTerm, dataContextOfAction.getCaseParameters())
+ .atTime(LocalTime.MIDNIGHT);
+ customerCase.setStartOfTerm(startOfTerm.atTime(LocalTime.MIDNIGHT));
+ customerCase.setEndOfTerm(endOfTerm);
customerCase.setCurrentState(Case.State.ACTIVE.name());
caseRepository.save(customerCase);
final CaseParametersEntity caseParameters = dataContextOfAction.getCaseParametersEntity();
caseParameters.setPaymentSize(command.getImportParameters().getPaymentSize());
caseParametersRepository.save(caseParameters);
- //TODO: find end of term.
- //TODO: persist start of term.
+
//TODO: create/connect to accounts in account assignments.
return new IndividualLoanCommandEvent(productIdentifier, caseIdentifier, command.getImportParameters().getCreatedOn());
@@ -279,40 +289,8 @@
final DesignatorToAccountIdentifierMapper designatorToAccountIdentifierMapper
= new DesignatorToAccountIdentifierMapper(dataContextOfAction);
+ createAccounts(dataContextOfAction, designatorToAccountIdentifierMapper, Collections.emptyMap());
- //Create the needed account assignments for groups and persist them for the case.
- try {
- designatorToAccountIdentifierMapper.getGroupsNeedingLedgers()
- .map(groupNeedingLedger -> {
- try {
- final String createdLedgerIdentifier = accountingAdapter.createLedger(
- dataContextOfAction.getCaseParametersEntity().getCustomerIdentifier(),
- groupNeedingLedger.getGroupName(),
- groupNeedingLedger.getParentLedger());
- return new AccountAssignment(groupNeedingLedger.getGroupName(), createdLedgerIdentifier);
- } catch (InterruptedException e) {
- throw new InterruptedInALambdaException(e);
- }
- })
- .map(accountAssignment -> CaseMapper.map(accountAssignment, dataContextOfAction.getCustomerCaseEntity()))
- .forEach(caseAccountAssignmentEntity -> dataContextOfAction.getCustomerCaseEntity().getAccountAssignments().add(caseAccountAssignmentEntity));
- }
- catch (final InterruptedInALambdaException e) {
- e.throwWrappedException();
- }
-
- //Create the needed account assignments and persist them for the case.
- designatorToAccountIdentifierMapper.getLedgersNeedingAccounts()
- .map(ledger ->
- new AccountAssignment(ledger.getDesignator(),
- accountingAdapter.createCaseAccountForLedgerAssignment(
- dataContextOfAction.getCaseParametersEntity().getCustomerIdentifier(),
- ledger)))
- .map(accountAssignment -> CaseMapper.map(accountAssignment, dataContextOfAction.getCustomerCaseEntity()))
- .forEach(caseAccountAssignmentEntity ->
- dataContextOfAction.getCustomerCaseEntity().getAccountAssignments().add(caseAccountAssignmentEntity)
- );
- caseRepository.save(dataContextOfAction.getCustomerCaseEntity());
final RealRunningBalances runningBalances = new RealRunningBalances(
accountingAdapter,
@@ -381,11 +359,12 @@
Action.DISBURSE,
transactionUniqueifier);
- //Only move to new state if book charges command was accepted.
+ //TODO: Only move to new state if book charges command was accepted.
if (Case.State.valueOf(dataContextOfAction.getCustomerCaseEntity().getCurrentState()) != Case.State.ACTIVE) {
- final LocalDateTime endOfTerm
- = ScheduledActionHelpers.getRoughEndDate(DateConverter.fromIsoString(command.getCommand().getCreatedOn()).toLocalDate(), dataContextOfAction.getCaseParameters())
+ final LocalDate startOfTerm = DateConverter.fromIsoString(command.getCommand().getCreatedOn()).toLocalDate();
+ final LocalDateTime endOfTerm = ScheduledActionHelpers.getRoughEndDate(startOfTerm, dataContextOfAction.getCaseParameters())
.atTime(LocalTime.MIDNIGHT);
+ customerCase.setStartOfTerm(startOfTerm.atTime(LocalTime.MIDNIGHT));
customerCase.setEndOfTerm(endOfTerm);
customerCase.setCurrentState(Case.State.ACTIVE.name());
caseRepository.save(customerCase);
@@ -715,6 +694,48 @@
return new IndividualLoanCommandEvent(productIdentifier, caseIdentifier, command.getCommand().getCreatedOn());
}
+ private void createAccounts(
+ final DataContextOfAction dataContextOfAction,
+ final DesignatorToAccountIdentifierMapper designatorToAccountIdentifierMapper,
+ final Map<String, BigDecimal> currentBalances) throws InterruptedException
+ {
+ //Create the needed account assignments for groups and persist them for the case.
+ try {
+ designatorToAccountIdentifierMapper.getGroupsNeedingLedgers()
+ .map(groupNeedingLedger -> {
+ try {
+ final String createdLedgerIdentifier = accountingAdapter.createLedger(
+ dataContextOfAction.getCaseParametersEntity().getCustomerIdentifier(),
+ groupNeedingLedger.getGroupName(),
+ groupNeedingLedger.getParentLedger());
+ return new AccountAssignment(groupNeedingLedger.getGroupName(), createdLedgerIdentifier);
+ } catch (InterruptedException e) {
+ throw new InterruptedInALambdaException(e);
+ }
+ })
+ .map(accountAssignment -> CaseMapper.map(accountAssignment, dataContextOfAction.getCustomerCaseEntity()))
+ .forEach(caseAccountAssignmentEntity -> dataContextOfAction.getCustomerCaseEntity().getAccountAssignments().add(caseAccountAssignmentEntity));
+ }
+ catch (final InterruptedInALambdaException e) {
+ e.throwWrappedException();
+ }
+
+ //Create the needed account assignments and persist them for the case.
+ designatorToAccountIdentifierMapper.getLedgersNeedingAccounts()
+ .map(ledger -> {
+ final BigDecimal currentBalance = currentBalances.getOrDefault(ledger.getDesignator(), BigDecimal.ZERO);
+ return new AccountAssignment(ledger.getDesignator(),
+ accountingAdapter.createCaseAccountForLedgerAssignment(
+ dataContextOfAction.getCaseParametersEntity().getCustomerIdentifier(),
+ ledger,
+ currentBalance));})
+ .map(accountAssignment -> CaseMapper.map(accountAssignment, dataContextOfAction.getCustomerCaseEntity()))
+ .forEach(caseAccountAssignmentEntity ->
+ dataContextOfAction.getCustomerCaseEntity().getAccountAssignments().add(caseAccountAssignmentEntity)
+ );
+ caseRepository.save(dataContextOfAction.getCustomerCaseEntity());
+ }
+
private Map<String, BigDecimal> getRequestedChargeAmounts(final @Nullable List<CostComponent> costComponents) {
if (costComponents == null)
return Collections.emptyMap();
diff --git a/service/src/main/java/io/mifos/individuallending/internal/service/costcomponent/AvailableRunningBalancesWithLimits.java b/service/src/main/java/io/mifos/individuallending/internal/service/costcomponent/AvailableRunningBalancesWithLimits.java
index 3f8a072..046106a 100644
--- a/service/src/main/java/io/mifos/individuallending/internal/service/costcomponent/AvailableRunningBalancesWithLimits.java
+++ b/service/src/main/java/io/mifos/individuallending/internal/service/costcomponent/AvailableRunningBalancesWithLimits.java
@@ -15,7 +15,6 @@
*/
package io.mifos.individuallending.internal.service.costcomponent;
-import io.mifos.individuallending.internal.service.DataContextOfAction;
import io.mifos.portfolio.api.v1.domain.ChargeDefinition;
import java.math.BigDecimal;
@@ -61,7 +60,7 @@
}
@Override
- public Optional<LocalDateTime> getStartOfTerm(final DataContextOfAction dataContextOfAction) {
- return decoratedRunningBalances.getStartOfTerm(dataContextOfAction);
+ public Optional<LocalDateTime> getStartOfTerm() {
+ return decoratedRunningBalances.getStartOfTerm();
}
}
diff --git a/service/src/main/java/io/mifos/individuallending/internal/service/costcomponent/RealRunningBalances.java b/service/src/main/java/io/mifos/individuallending/internal/service/costcomponent/RealRunningBalances.java
index 30b05be..a394f2c 100644
--- a/service/src/main/java/io/mifos/individuallending/internal/service/costcomponent/RealRunningBalances.java
+++ b/service/src/main/java/io/mifos/individuallending/internal/service/costcomponent/RealRunningBalances.java
@@ -89,13 +89,22 @@
}
@Override
- public Optional<LocalDateTime> getStartOfTerm(final DataContextOfAction dataContextOfAction) {
+ public Optional<LocalDateTime> getStartOfTerm() {
if (!startOfTerm.isPresent()) {
+ final LocalDateTime persistedStartOfTerm = dataContextOfAction.getCustomerCaseEntity().getStartOfTerm();
+ if (persistedStartOfTerm != null) {
+ this.startOfTerm = Optional.of(persistedStartOfTerm);
+ return this.startOfTerm;
+ }
final String customerLoanPrincipalAccountIdentifier = designatorToAccountIdentifierMapper.mapOrThrow(AccountDesignators.CUSTOMER_LOAN_PRINCIPAL);
this.startOfTerm = accountingAdapter.getDateOfOldestEntryContainingMessage(
customerLoanPrincipalAccountIdentifier,
dataContextOfAction.getMessageForCharge(Action.DISBURSE));
+
+ //Moving start of term persistence from accounting to the portfolio db. Opportunistic migration only right now.
+ startOfTerm.ifPresent(startOfTermPersistedInAccounting ->
+ dataContextOfAction.getCustomerCaseEntity().setStartOfTerm(startOfTermPersistedInAccounting));
}
return this.startOfTerm;
diff --git a/service/src/main/java/io/mifos/individuallending/internal/service/costcomponent/RunningBalances.java b/service/src/main/java/io/mifos/individuallending/internal/service/costcomponent/RunningBalances.java
index affd152..b18edaf 100644
--- a/service/src/main/java/io/mifos/individuallending/internal/service/costcomponent/RunningBalances.java
+++ b/service/src/main/java/io/mifos/individuallending/internal/service/costcomponent/RunningBalances.java
@@ -60,10 +60,10 @@
BigDecimal getAccruedBalanceForCharge(
final ChargeDefinition chargeDefinition);
- Optional<LocalDateTime> getStartOfTerm(final DataContextOfAction dataContextOfAction);
+ Optional<LocalDateTime> getStartOfTerm();
default LocalDateTime getStartOfTermOrThrow(final DataContextOfAction dataContextOfAction) {
- return this.getStartOfTerm(dataContextOfAction)
+ return this.getStartOfTerm()
.orElseThrow(() -> ServiceException.internalError(
"Start of term for loan ''{0}'' could not be acquired from accounting.",
dataContextOfAction.getCompoundIdentifer()));
diff --git a/service/src/main/java/io/mifos/individuallending/internal/service/costcomponent/SimulatedRunningBalances.java b/service/src/main/java/io/mifos/individuallending/internal/service/costcomponent/SimulatedRunningBalances.java
index cf7d47c..05505fa 100644
--- a/service/src/main/java/io/mifos/individuallending/internal/service/costcomponent/SimulatedRunningBalances.java
+++ b/service/src/main/java/io/mifos/individuallending/internal/service/costcomponent/SimulatedRunningBalances.java
@@ -16,7 +16,6 @@
package io.mifos.individuallending.internal.service.costcomponent;
-import io.mifos.individuallending.internal.service.DataContextOfAction;
import io.mifos.portfolio.api.v1.domain.ChargeDefinition;
import java.math.BigDecimal;
@@ -54,7 +53,7 @@
}
@Override
- public Optional<LocalDateTime> getStartOfTerm(final DataContextOfAction dataContextOfAction) {
+ public Optional<LocalDateTime> getStartOfTerm() {
return Optional.ofNullable(startOfTerm);
}
diff --git a/service/src/main/java/io/mifos/portfolio/service/internal/mapper/CaseMapper.java b/service/src/main/java/io/mifos/portfolio/service/internal/mapper/CaseMapper.java
index 2ffcbe8..e742ce3 100644
--- a/service/src/main/java/io/mifos/portfolio/service/internal/mapper/CaseMapper.java
+++ b/service/src/main/java/io/mifos/portfolio/service/internal/mapper/CaseMapper.java
@@ -19,6 +19,7 @@
import io.mifos.core.lang.DateConverter;
import io.mifos.portfolio.api.v1.domain.AccountAssignment;
import io.mifos.portfolio.api.v1.domain.Case;
+import io.mifos.portfolio.api.v1.domain.CaseStatus;
import io.mifos.portfolio.service.internal.repository.CaseAccountAssignmentEntity;
import io.mifos.portfolio.service.internal.repository.CaseEntity;
@@ -49,6 +50,18 @@
return ret;
}
+ public static CaseStatus mapToStatus(final CaseEntity caseEntity) {
+ final CaseStatus ret = new CaseStatus();
+
+ ret.setCurrentState(caseEntity.getCurrentState());
+ if (caseEntity.getStartOfTerm() != null)
+ ret.setStartOfTerm(DateConverter.toIsoString(caseEntity.getStartOfTerm()));
+ if (caseEntity.getEndOfTerm() != null)
+ ret.setEndOfTerm(DateConverter.toIsoString(caseEntity.getEndOfTerm()));
+
+ return ret;
+ }
+
public static AccountAssignment mapAccountAssignmentEntity(final CaseAccountAssignmentEntity instance) {
final AccountAssignment ret = new AccountAssignment();
diff --git a/service/src/main/java/io/mifos/portfolio/service/internal/repository/CaseEntity.java b/service/src/main/java/io/mifos/portfolio/service/internal/repository/CaseEntity.java
index 39a4ee7..56d62bc 100644
--- a/service/src/main/java/io/mifos/portfolio/service/internal/repository/CaseEntity.java
+++ b/service/src/main/java/io/mifos/portfolio/service/internal/repository/CaseEntity.java
@@ -54,6 +54,10 @@
@Column(name = "current_state", nullable = false)
private String currentState;
+ @Column(name = "start_of_term")
+ @Convert(converter = LocalDateTimeConverter.class)
+ @Nullable private LocalDateTime startOfTerm;
+
@Column(name = "end_of_term")
@Convert(converter = LocalDateTimeConverter.class)
@Nullable private LocalDateTime endOfTerm;
@@ -131,6 +135,15 @@
this.currentState = currentState;
}
+ @Nullable
+ public LocalDateTime getStartOfTerm() {
+ return startOfTerm;
+ }
+
+ public void setStartOfTerm(@Nullable LocalDateTime startOfTerm) {
+ this.startOfTerm = startOfTerm;
+ }
+
public LocalDateTime getEndOfTerm() {
return endOfTerm;
}
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 414d38e..48db85c 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
@@ -18,6 +18,7 @@
import io.mifos.core.lang.ServiceException;
import io.mifos.portfolio.api.v1.domain.Case;
import io.mifos.portfolio.api.v1.domain.CasePage;
+import io.mifos.portfolio.api.v1.domain.CaseStatus;
import io.mifos.portfolio.api.v1.domain.Payment;
import io.mifos.portfolio.service.internal.mapper.CaseMapper;
import io.mifos.portfolio.service.internal.pattern.PatternFactoryRegistry;
@@ -95,6 +96,12 @@
.flatMap(caseEntity -> map(caseEntity, minorCurrencyUnitDigits));
}
+ public Optional<CaseStatus> findStatusByIdentifier(final String productIdentifier, final String caseIdentifier)
+ {
+ return caseRepository.findByProductIdentifierAndIdentifier(productIdentifier, caseIdentifier)
+ .map(CaseMapper::mapToStatus);
+ }
+
public Set<String> getNextActionsForCase(final String productIdentifier, final String caseIdentifier) {
final PatternFactory pattern = getPatternFactoryOrThrow(productIdentifier);
@@ -109,8 +116,8 @@
private Optional<Case> map(final CaseEntity caseEntity, final int minorCurrencyUnitDigits) {
return getPatternFactory(caseEntity.getProductIdentifier())
- .flatMap(x -> x.getParameters(caseEntity.getId(), minorCurrencyUnitDigits))
- .map(x -> CaseMapper.map(caseEntity, x));
+ .flatMap(x -> x.getParameters(caseEntity.getId(), minorCurrencyUnitDigits))
+ .map(x -> CaseMapper.map(caseEntity, x));
}
private PatternFactory getPatternFactoryOrThrow(final String productIdentifier) {
diff --git a/service/src/main/java/io/mifos/portfolio/service/internal/util/AccountingAdapter.java b/service/src/main/java/io/mifos/portfolio/service/internal/util/AccountingAdapter.java
index 158ec90..651497b 100644
--- a/service/src/main/java/io/mifos/portfolio/service/internal/util/AccountingAdapter.java
+++ b/service/src/main/java/io/mifos/portfolio/service/internal/util/AccountingAdapter.java
@@ -272,12 +272,15 @@
productIdentifier, accountDesignator, ledgerIdentifier));
}
- public String createCaseAccountForLedgerAssignment(final String customerIdentifier, final AccountAssignment ledgerAssignment) {
+ public String createCaseAccountForLedgerAssignment(
+ final String customerIdentifier,
+ final AccountAssignment ledgerAssignment,
+ final BigDecimal currentBalance) {
final Ledger ledger = ledgerManager.findLedger(ledgerAssignment.getLedgerIdentifier());
final AccountPage accountsOfLedger = ledgerManager.fetchAccountsOfLedger(ledger.getIdentifier(), null, null, null, null);
final Account generatedAccount = new Account();
- generatedAccount.setBalance(0.0);
+ generatedAccount.setBalance(currentBalance.doubleValue());
generatedAccount.setType(ledger.getType());
generatedAccount.setState(Account.State.OPEN.name());
long guestimatedAccountIndex = accountsOfLedger.getTotalElements() + 1;
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 52da934..7a6b72b 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
@@ -132,12 +132,27 @@
consumes = MediaType.ALL_VALUE,
produces = MediaType.APPLICATION_JSON_VALUE)
public @ResponseBody Case getCase(
- @PathVariable("productidentifier") final String productIdentifier,
- @PathVariable("caseidentifier") final String caseIdentifier)
+ @PathVariable("productidentifier") final String productIdentifier,
+ @PathVariable("caseidentifier") final String caseIdentifier)
{
return caseService.findByIdentifier(productIdentifier, caseIdentifier)
- .orElseThrow(() -> ServiceException.notFound(
- "Instance with identifier " + productIdentifier + "." + caseIdentifier + " doesn't exist."));
+ .orElseThrow(() -> ServiceException.notFound(
+ "Instance with identifier ''{0}.{1}'' doesn''t exist.", productIdentifier, caseIdentifier));
+ }
+
+ @Permittable(value = AcceptedTokenType.TENANT, groupId = PermittableGroupIds.CASE_STATUS)
+ @RequestMapping(
+ value = "{caseidentifier}/status",
+ method = RequestMethod.GET,
+ consumes = MediaType.ALL_VALUE,
+ produces = MediaType.APPLICATION_JSON_VALUE)
+ public @ResponseBody CaseStatus getCaseStatus(
+ @PathVariable("productidentifier") final String productIdentifier,
+ @PathVariable("caseidentifier") final String caseIdentifier)
+ {
+ return caseService.findStatusByIdentifier(productIdentifier, caseIdentifier)
+ .orElseThrow(() -> ServiceException.notFound(
+ "Instance with identifier ''{0}.{1}'' doesn''t exist.", productIdentifier, caseIdentifier));
}
@Permittable(value = AcceptedTokenType.TENANT, groupId = PermittableGroupIds.CASE_MANAGEMENT)
@@ -235,7 +250,7 @@
return new ResponseEntity<>(HttpStatus.ACCEPTED);
}
- @Permittable(value = AcceptedTokenType.TENANT, groupId = PermittableGroupIds.CASE_IMPORT)
+ @Permittable(value = AcceptedTokenType.TENANT, groupId = PermittableGroupIds.CASE_STATUS)
@RequestMapping(
value = "{caseidentifier}/commands/IMPORT",
method = RequestMethod.POST,
diff --git a/service/src/main/resources/db/migrations/mariadb/V12__persist_start_of_term_locally.sql b/service/src/main/resources/db/migrations/mariadb/V12__persist_start_of_term_locally.sql
new file mode 100644
index 0000000..e254487
--- /dev/null
+++ b/service/src/main/resources/db/migrations/mariadb/V12__persist_start_of_term_locally.sql
@@ -0,0 +1,17 @@
+--
+-- Copyright 2017 Kuelap, Inc.
+--
+-- Licensed under the Apache License, Version 2.0 (the "License");
+-- you may not use this file except in compliance with the License.
+-- You may obtain a copy of the License at
+--
+-- http://www.apache.org/licenses/LICENSE-2.0
+--
+-- Unless required by applicable law or agreed to in writing, software
+-- distributed under the License is distributed on an "AS IS" BASIS,
+-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+-- See the License for the specific language governing permissions and
+-- limitations under the License.
+--
+
+ALTER TABLE bastet_cases ADD COLUMN start_of_term TIMESTAMP(3) NULL DEFAULT NULL;
\ No newline at end of file