Preparing interface changes for late fee checking:
* added datetime to command event.
* added payment size column to case parameters.
* added/adjusted properties to set time to check for lateness (next to interest)
* added command for lateness checking.
diff --git a/api/src/main/java/io/mifos/individuallending/api/v1/domain/caseinstance/CaseParameters.java b/api/src/main/java/io/mifos/individuallending/api/v1/domain/caseinstance/CaseParameters.java
index 5fc2e9c..7be3e81 100644
--- a/api/src/main/java/io/mifos/individuallending/api/v1/domain/caseinstance/CaseParameters.java
+++ b/api/src/main/java/io/mifos/individuallending/api/v1/domain/caseinstance/CaseParameters.java
@@ -50,6 +50,9 @@
@Valid
private PaymentCycle paymentCycle;
+ @Range(min = 0)
+ private BigDecimal paymentSize;
+
public CaseParameters() {
}
@@ -97,31 +100,41 @@
this.paymentCycle = paymentCycle;
}
+ public BigDecimal getPaymentSize() {
+ return paymentSize;
+ }
+
+ public void setPaymentSize(BigDecimal paymentSize) {
+ this.paymentSize = paymentSize;
+ }
+
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
CaseParameters that = (CaseParameters) o;
return Objects.equals(customerIdentifier, that.customerIdentifier) &&
- Objects.equals(creditWorthinessSnapshots, that.creditWorthinessSnapshots) &&
- Objects.equals(maximumBalance, that.maximumBalance) &&
- Objects.equals(termRange, that.termRange) &&
- Objects.equals(paymentCycle, that.paymentCycle);
+ Objects.equals(creditWorthinessSnapshots, that.creditWorthinessSnapshots) &&
+ Objects.equals(maximumBalance, that.maximumBalance) &&
+ Objects.equals(termRange, that.termRange) &&
+ Objects.equals(paymentCycle, that.paymentCycle) &&
+ Objects.equals(paymentSize, that.paymentSize);
}
@Override
public int hashCode() {
- return Objects.hash(customerIdentifier, creditWorthinessSnapshots, maximumBalance, termRange, paymentCycle);
+ return Objects.hash(customerIdentifier, creditWorthinessSnapshots, maximumBalance, termRange, paymentCycle, paymentSize);
}
@Override
public String toString() {
return "CaseParameters{" +
- "customerIdentifier='" + customerIdentifier + '\'' +
- ", creditWorthinessSnapshots=" + creditWorthinessSnapshots +
- ", maximumBalance=" + maximumBalance +
- ", termRange=" + termRange +
- ", paymentCycle=" + paymentCycle +
- '}';
+ "customerIdentifier='" + customerIdentifier + '\'' +
+ ", creditWorthinessSnapshots=" + creditWorthinessSnapshots +
+ ", maximumBalance=" + maximumBalance +
+ ", termRange=" + termRange +
+ ", paymentCycle=" + paymentCycle +
+ ", paymentSize=" + paymentSize +
+ '}';
}
}
\ No newline at end of file
diff --git a/api/src/main/java/io/mifos/individuallending/api/v1/events/IndividualLoanCommandEvent.java b/api/src/main/java/io/mifos/individuallending/api/v1/events/IndividualLoanCommandEvent.java
index 2ae1249..4576d73 100644
--- a/api/src/main/java/io/mifos/individuallending/api/v1/events/IndividualLoanCommandEvent.java
+++ b/api/src/main/java/io/mifos/individuallending/api/v1/events/IndividualLoanCommandEvent.java
@@ -24,13 +24,15 @@
public class IndividualLoanCommandEvent {
private String productIdentifier;
private String caseIdentifier;
+ private String forDate;
public IndividualLoanCommandEvent() {
}
- public IndividualLoanCommandEvent(String productIdentifier, String caseIdentifier) {
+ public IndividualLoanCommandEvent(String productIdentifier, String caseIdentifier, String forDate) {
this.productIdentifier = productIdentifier;
this.caseIdentifier = caseIdentifier;
+ this.forDate = forDate;
}
public String getProductIdentifier() {
@@ -49,25 +51,35 @@
this.caseIdentifier = caseIdentifier;
}
+ public String getForDate() {
+ return forDate;
+ }
+
+ public void setForDate(String forDate) {
+ this.forDate = forDate;
+ }
+
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
IndividualLoanCommandEvent that = (IndividualLoanCommandEvent) o;
return Objects.equals(productIdentifier, that.productIdentifier) &&
- Objects.equals(caseIdentifier, that.caseIdentifier);
+ Objects.equals(caseIdentifier, that.caseIdentifier) &&
+ Objects.equals(forDate, that.forDate);
}
@Override
public int hashCode() {
- return Objects.hash(productIdentifier, caseIdentifier);
+ return Objects.hash(productIdentifier, caseIdentifier, forDate);
}
@Override
public String toString() {
return "IndividualLoanCommandEvent{" +
- "productIdentifier='" + productIdentifier + '\'' +
- ", caseIdentifier='" + caseIdentifier + '\'' +
- '}';
+ "productIdentifier='" + productIdentifier + '\'' +
+ ", caseIdentifier='" + caseIdentifier + '\'' +
+ ", forDate='" + forDate + '\'' +
+ '}';
}
}
diff --git a/api/src/main/java/io/mifos/individuallending/api/v1/events/IndividualLoanEventConstants.java b/api/src/main/java/io/mifos/individuallending/api/v1/events/IndividualLoanEventConstants.java
index c5fa4f2..89e1e4f 100644
--- a/api/src/main/java/io/mifos/individuallending/api/v1/events/IndividualLoanEventConstants.java
+++ b/api/src/main/java/io/mifos/individuallending/api/v1/events/IndividualLoanEventConstants.java
@@ -28,6 +28,7 @@
String DISBURSE_INDIVIDUALLOAN_CASE = "disburse-individualloan-case";
String APPLY_INTEREST_INDIVIDUALLOAN_CASE = "apply-interest-individualloan-case";
String ACCEPT_PAYMENT_INDIVIDUALLOAN_CASE = "accept-payment-individualloan-case";
+ String CHECK_LATE_INDIVIDUALLOAN_CASE = "check-late-individualloan-case";
String MARK_LATE_INDIVIDUALLOAN_CASE = "mark-late-individualloan-case";
String WRITE_OFF_INDIVIDUALLOAN_CASE = "write-off-individualloan-case";
String CLOSE_INDIVIDUALLOAN_CASE = "close-individualloan-case";
@@ -39,6 +40,7 @@
String SELECTOR_DISBURSE_INDIVIDUALLOAN_CASE = SELECTOR_NAME + " = '" + DISBURSE_INDIVIDUALLOAN_CASE + "'";
String SELECTOR_APPLY_INTEREST_INDIVIDUALLOAN_CASE = SELECTOR_NAME + " = '" + APPLY_INTEREST_INDIVIDUALLOAN_CASE + "'";
String SELECTOR_ACCEPT_PAYMENT_INDIVIDUALLOAN_CASE = SELECTOR_NAME + " = '" + ACCEPT_PAYMENT_INDIVIDUALLOAN_CASE + "'";
+ String SELECTOR_CHECK_LATE_INDIVIDUALLOAN_CASE = SELECTOR_NAME + " = '" + CHECK_LATE_INDIVIDUALLOAN_CASE + "'";
String SELECTOR_MARK_LATE_INDIVIDUALLOAN_CASE = SELECTOR_NAME + " = '" + MARK_LATE_INDIVIDUALLOAN_CASE + "'";
String SELECTOR_WRITE_OFF_INDIVIDUALLOAN_CASE = SELECTOR_NAME + " = '" + WRITE_OFF_INDIVIDUALLOAN_CASE + "'";
String SELECTOR_CLOSE_INDIVIDUALLOAN_CASE = SELECTOR_NAME + " = '" + CLOSE_INDIVIDUALLOAN_CASE + "'";
diff --git a/component-test/src/main/java/io/mifos/portfolio/AbstractPortfolioTest.java b/component-test/src/main/java/io/mifos/portfolio/AbstractPortfolioTest.java
index 4c7dfcd..62de9e5 100644
--- a/component-test/src/main/java/io/mifos/portfolio/AbstractPortfolioTest.java
+++ b/component-test/src/main/java/io/mifos/portfolio/AbstractPortfolioTest.java
@@ -18,6 +18,7 @@
import io.mifos.accounting.api.v1.client.LedgerManager;
import io.mifos.anubis.test.v1.TenantApplicationSecurityEnvironmentTestRule;
import io.mifos.core.api.context.AutoUserContext;
+import io.mifos.core.lang.DateConverter;
import io.mifos.core.test.fixture.TenantDataStoreContextTestRule;
import io.mifos.core.test.listener.EnableEventRecording;
import io.mifos.core.test.listener.EventRecorder;
@@ -53,6 +54,8 @@
import javax.validation.Validator;
import javax.validation.ValidatorFactory;
import java.math.BigDecimal;
+import java.time.LocalDateTime;
+import java.time.temporal.ChronoUnit;
import java.util.*;
import java.util.function.Consumer;
import java.util.stream.Collectors;
@@ -63,7 +66,7 @@
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT,
classes = {AbstractPortfolioTest.TestConfiguration.class},
- properties = {"portfolio.bookInterestAsUser=interest_user", "portfolio.bookInterestInTimeSlot=0"}
+ properties = {"portfolio.bookLateFeesAndInterestAsUser=interest_user", "portfolio.bookInterestInTimeSlot=0", "portfolio.checkForLatenessInTimeSlot=0"}
)
public class AbstractPortfolioTest extends SuiteTestEnvironment {
private static final String LOGGER_NAME = "test-logger";
@@ -188,7 +191,15 @@
final List<AccountAssignment> oneTimeAccountAssignments,
final String event,
final Case.State nextState) throws InterruptedException {
- checkStateTransfer(productIdentifier, caseIdentifier, action, oneTimeAccountAssignments, BigDecimal.ZERO, event, nextState);
+ checkStateTransfer(
+ productIdentifier,
+ caseIdentifier,
+ action,
+ oneTimeAccountAssignments,
+ BigDecimal.ZERO,
+ event,
+ midnightToday(),
+ nextState);
}
void checkStateTransfer(final String productIdentifier,
@@ -197,13 +208,14 @@
final List<AccountAssignment> oneTimeAccountAssignments,
final BigDecimal paymentSize,
final String event,
+ final LocalDateTime eventDateTime,
final Case.State nextState) throws InterruptedException {
final Command command = new Command();
command.setOneTimeAccountAssignments(oneTimeAccountAssignments);
command.setPaymentSize(paymentSize);
portfolioManager.executeCaseCommand(productIdentifier, caseIdentifier, action.name(), command);
- Assert.assertTrue(eventRecorder.wait(event, new IndividualLoanCommandEvent(productIdentifier, caseIdentifier)));
+ Assert.assertTrue(eventRecorder.wait(event, new IndividualLoanCommandEvent(productIdentifier, caseIdentifier, DateConverter.toIsoString(eventDateTime))));
final Case customerCase = portfolioManager.getCase(productIdentifier, caseIdentifier);
Assert.assertEquals(nextState.name(), customerCase.getCurrentState());
@@ -313,4 +325,7 @@
Assert.assertTrue(eventRecorder.wait(EventConstants.PUT_TASK_INSTANCE_EXECUTION, new TaskInstanceEvent(product.getIdentifier(), customerCase.getIdentifier(), taskDefinition.getIdentifier())));
}
+ LocalDateTime midnightToday() {
+ return LocalDateTime.now().truncatedTo(ChronoUnit.DAYS);
+ }
}
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 dc3cdfe..69e1c0b 100644
--- a/component-test/src/main/java/io/mifos/portfolio/Fixture.java
+++ b/component-test/src/main/java/io/mifos/portfolio/Fixture.java
@@ -141,6 +141,7 @@
ret.setMaximumBalance(fixScale(BigDecimal.valueOf(2000L)));
ret.setTermRange(new TermRange(ChronoUnit.MONTHS, 18));
ret.setPaymentCycle(new PaymentCycle(ChronoUnit.MONTHS, 1, 1, null, null));
+ ret.setPaymentSize(BigDecimal.ONE.negate().setScale(4, RoundingMode.HALF_EVEN));
final CreditWorthinessSnapshot customerCreditWorthinessSnapshot = new CreditWorthinessSnapshot();
customerCreditWorthinessSnapshot.setForCustomer("alice");
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 c4a9e5f..b34a0ed 100644
--- a/component-test/src/main/java/io/mifos/portfolio/TestAccountingInteractionInLoanWorkflow.java
+++ b/component-test/src/main/java/io/mifos/portfolio/TestAccountingInteractionInLoanWorkflow.java
@@ -41,8 +41,6 @@
import java.math.BigDecimal;
import java.math.RoundingMode;
-import java.time.LocalDateTime;
-import java.time.temporal.ChronoUnit;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
@@ -319,6 +317,7 @@
Collections.singletonList(assignEntryToTeller()),
amount,
IndividualLoanEventConstants.DISBURSE_INDIVIDUALLOAN_CASE,
+ midnightToday(),
Case.State.ACTIVE);
checkNextActionsCorrect(product.getIdentifier(), customerCase.getIdentifier(), Action.APPLY_INTEREST,
Action.APPLY_INTEREST, Action.MARK_LATE, Action.ACCEPT_PAYMENT, Action.DISBURSE, Action.WRITE_OFF, Action.CLOSE);
@@ -342,7 +341,7 @@
private void step6CalculateInterestAccrual() throws InterruptedException {
logger.info("step6CalculateInterestAccrual");
final String beatIdentifier = "alignment0";
- final String midnightTimeStamp = DateConverter.toIsoString(LocalDateTime.now().truncatedTo(ChronoUnit.DAYS));
+ final String midnightTimeStamp = DateConverter.toIsoString(midnightToday());
AccountingFixture.mockBalance(customerLoanAccountIdentifier, expectedCurrentBalance.negate());
@@ -362,7 +361,7 @@
new BeatPublishEvent(EventConstants.DESTINATION, beatIdentifier, midnightTimeStamp)));
Assert.assertTrue(eventRecorder.wait(IndividualLoanEventConstants.APPLY_INTEREST_INDIVIDUALLOAN_CASE,
- new IndividualLoanCommandEvent(product.getIdentifier(), customerCase.getIdentifier())));
+ new IndividualLoanCommandEvent(product.getIdentifier(), customerCase.getIdentifier(), midnightTimeStamp)));
final Case customerCaseAfterStateChange = portfolioManager.getCase(product.getIdentifier(), customerCase.getIdentifier());
@@ -408,6 +407,7 @@
Collections.singletonList(assignEntryToTeller()),
amount,
IndividualLoanEventConstants.ACCEPT_PAYMENT_INDIVIDUALLOAN_CASE,
+ midnightToday(),
Case.State.ACTIVE); //Close has to be done explicitly.
checkNextActionsCorrect(product.getIdentifier(), customerCase.getIdentifier(), Action.APPLY_INTEREST,
Action.APPLY_INTEREST, Action.MARK_LATE, Action.ACCEPT_PAYMENT, Action.DISBURSE, Action.WRITE_OFF, Action.CLOSE);
diff --git a/service/src/main/java/io/mifos/individuallending/IndividualLendingPatternFactory.java b/service/src/main/java/io/mifos/individuallending/IndividualLendingPatternFactory.java
index 1ea1bdf..cb83144 100644
--- a/service/src/main/java/io/mifos/individuallending/IndividualLendingPatternFactory.java
+++ b/service/src/main/java/io/mifos/individuallending/IndividualLendingPatternFactory.java
@@ -351,7 +351,7 @@
final BigDecimal forPaymentSize) {
final Action action = Action.valueOf(actionIdentifier);
final DataContextOfAction dataContextOfAction = dataContextService.checkedGetDataContext(productIdentifier, caseIdentifier, Collections.emptyList());
- final Case.State caseState = Case.State.valueOf(dataContextOfAction.getCustomerCase().getCurrentState());
+ final Case.State caseState = Case.State.valueOf(dataContextOfAction.getCustomerCaseEntity().getCurrentState());
checkActionCanBeExecuted(caseState, action);
return costComponentService.getCostComponentsForAction(action, dataContextOfAction, forPaymentSize)
diff --git a/service/src/main/java/io/mifos/individuallending/internal/command/ApplyInterestCommand.java b/service/src/main/java/io/mifos/individuallending/internal/command/ApplyInterestCommand.java
index 1e658f3..5c7e3ad 100644
--- a/service/src/main/java/io/mifos/individuallending/internal/command/ApplyInterestCommand.java
+++ b/service/src/main/java/io/mifos/individuallending/internal/command/ApplyInterestCommand.java
@@ -21,10 +21,12 @@
public class ApplyInterestCommand {
private final String productIdentifier;
private final String caseIdentifier;
+ private final String forTime;
- public ApplyInterestCommand(String productIdentifier, String caseIdentifier) {
+ public ApplyInterestCommand(String productIdentifier, String caseIdentifier, String forTime) {
this.productIdentifier = productIdentifier;
this.caseIdentifier = caseIdentifier;
+ this.forTime = forTime;
}
public String getProductIdentifier() {
@@ -35,11 +37,16 @@
return caseIdentifier;
}
+ public String getForTime() {
+ return forTime;
+ }
+
@Override
public String toString() {
return "ApplyInterestCommand{" +
"productIdentifier='" + productIdentifier + '\'' +
", caseIdentifier='" + caseIdentifier + '\'' +
+ ", forTime='" + forTime + '\'' +
'}';
}
}
diff --git a/service/src/main/java/io/mifos/individuallending/internal/command/CheckLateCommand.java b/service/src/main/java/io/mifos/individuallending/internal/command/CheckLateCommand.java
new file mode 100644
index 0000000..bb31db2
--- /dev/null
+++ b/service/src/main/java/io/mifos/individuallending/internal/command/CheckLateCommand.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2017 Kuelap, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.mifos.individuallending.internal.command;
+
+public class CheckLateCommand {
+ private final String productIdentifier;
+ private final String caseIdentifier;
+ private final String forTime;
+
+ public CheckLateCommand(String productIdentifier, String caseIdentifier, String forTime) {
+ this.productIdentifier = productIdentifier;
+ this.caseIdentifier = caseIdentifier;
+ this.forTime = forTime;
+ }
+
+ public String getProductIdentifier() {
+ return productIdentifier;
+ }
+
+ public String getCaseIdentifier() {
+ return caseIdentifier;
+ }
+
+ public String getForTime() {
+ return forTime;
+ }
+
+ @Override
+ public String toString() {
+ return "CheckLateCommand{" +
+ "productIdentifier='" + productIdentifier + '\'' +
+ ", caseIdentifier='" + caseIdentifier + '\'' +
+ ", forTime='" + forTime + '\'' +
+ '}';
+ }
+}
diff --git a/service/src/main/java/io/mifos/individuallending/internal/command/handler/BeatPublishCommandHandler.java b/service/src/main/java/io/mifos/individuallending/internal/command/handler/BeatPublishCommandHandler.java
index af4abc5..8f1c6e9 100644
--- a/service/src/main/java/io/mifos/individuallending/internal/command/handler/BeatPublishCommandHandler.java
+++ b/service/src/main/java/io/mifos/individuallending/internal/command/handler/BeatPublishCommandHandler.java
@@ -22,7 +22,12 @@
import io.mifos.core.command.internal.CommandBus;
import io.mifos.core.lang.ApplicationName;
import io.mifos.core.lang.DateConverter;
+import io.mifos.individuallending.api.v1.events.IndividualLoanCommandEvent;
+import io.mifos.individuallending.api.v1.events.IndividualLoanEventConstants;
import io.mifos.individuallending.internal.command.ApplyInterestCommand;
+import io.mifos.individuallending.internal.command.CheckLateCommand;
+import io.mifos.individuallending.internal.service.DataContextOfAction;
+import io.mifos.individuallending.internal.service.DataContextService;
import io.mifos.portfolio.api.v1.domain.Case;
import io.mifos.portfolio.service.config.PortfolioProperties;
import io.mifos.portfolio.service.internal.command.CreateBeatPublishCommand;
@@ -46,6 +51,7 @@
public class BeatPublishCommandHandler {
private final CaseRepository caseRepository;
private final PortfolioProperties portfolioProperties;
+ private final DataContextService dataContextService;
private final ApplicationName applicationName;
private final CommandBus commandBus;
@@ -53,10 +59,12 @@
public BeatPublishCommandHandler(
final CaseRepository caseRepository,
final PortfolioProperties portfolioProperties,
+ final DataContextService dataContextService,
final ApplicationName applicationName,
final CommandBus commandBus) {
this.caseRepository = caseRepository;
this.portfolioProperties = portfolioProperties;
+ this.dataContextService = dataContextService;
this.applicationName = applicationName;
this.commandBus = commandBus;
}
@@ -71,11 +79,42 @@
{
final Stream<CaseEntity> activeCases = caseRepository.findByCurrentStateIn(Collections.singleton(Case.State.ACTIVE.name()));
activeCases.forEach(activeCase -> {
- final ApplyInterestCommand applyInterestCommand = new ApplyInterestCommand(activeCase.getProductIdentifier(), activeCase.getIdentifier());
+ final ApplyInterestCommand applyInterestCommand = new ApplyInterestCommand(
+ activeCase.getProductIdentifier(),
+ activeCase.getIdentifier(),
+ instance.getForTime());
commandBus.dispatch(applyInterestCommand);
});
}
+ if (portfolioProperties.getCheckForLatenessInTimeSlot() == forTime.getHour())
+ {
+ final Stream<CaseEntity> activeCases = caseRepository.findByCurrentStateIn(Collections.singleton(Case.State.ACTIVE.name()));
+ activeCases.forEach(activeCase -> {
+ final CheckLateCommand checkLateCommand = new CheckLateCommand(
+ activeCase.getProductIdentifier(),
+ activeCase.getIdentifier(),
+ instance.getForTime());
+ commandBus.dispatch(checkLateCommand);
+ });
+ }
+
return new BeatPublishEvent(applicationName.toString(), instance.getIdentifier(), instance.getForTime());
}
+
+ @Transactional
+ @CommandHandler(logStart = CommandLogLevel.INFO, logFinish = CommandLogLevel.INFO)
+ @EventEmitter(
+ selectorName = io.mifos.portfolio.api.v1.events.EventConstants.SELECTOR_NAME,
+ selectorValue = IndividualLoanEventConstants.SELECTOR_CHECK_LATE_INDIVIDUALLOAN_CASE)
+ public IndividualLoanCommandEvent process(final CheckLateCommand command) {
+ final String productIdentifier = command.getProductIdentifier();
+ final String caseIdentifier = command.getCaseIdentifier();
+ final DataContextOfAction dataContextOfAction = dataContextService.checkedGetDataContext(
+ productIdentifier, caseIdentifier, Collections.emptyList());
+
+//TODO:
+
+ return new IndividualLoanCommandEvent(productIdentifier, caseIdentifier, command.getForTime());
+ }
}
\ No newline at end of file
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 00740d5..5d105b4 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
@@ -20,13 +20,16 @@
import io.mifos.core.command.annotation.CommandHandler;
import io.mifos.core.command.annotation.CommandLogLevel;
import io.mifos.core.command.annotation.EventEmitter;
+import io.mifos.core.lang.DateConverter;
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.product.ChargeIdentifiers;
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;
import io.mifos.individuallending.internal.command.*;
+import io.mifos.individuallending.internal.repository.CaseParametersRepository;
import io.mifos.individuallending.internal.service.*;
import io.mifos.portfolio.api.v1.domain.AccountAssignment;
import io.mifos.portfolio.api.v1.domain.Case;
@@ -44,10 +47,10 @@
import javax.annotation.Nullable;
import java.math.BigDecimal;
+import java.time.Clock;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
-import java.time.ZoneId;
import java.util.Collections;
import java.util.List;
import java.util.Map;
@@ -65,6 +68,7 @@
private final CostComponentService costComponentService;
private final AccountingAdapter accountingAdapter;
private final TaskInstanceRepository taskInstanceRepository;
+ private final CaseParametersRepository caseParametersRepository;
@Autowired
public IndividualLoanCommandHandler(
@@ -72,12 +76,14 @@
final DataContextService dataContextService,
final CostComponentService costComponentService,
final AccountingAdapter accountingAdapter,
- final TaskInstanceRepository taskInstanceRepository) {
+ final TaskInstanceRepository taskInstanceRepository,
+ final CaseParametersRepository caseParametersRepository) {
this.caseRepository = caseRepository;
this.dataContextService = dataContextService;
this.costComponentService = costComponentService;
this.accountingAdapter = accountingAdapter;
this.taskInstanceRepository = taskInstanceRepository;
+ this.caseParametersRepository = caseParametersRepository;
}
@Transactional
@@ -90,7 +96,7 @@
final String caseIdentifier = command.getCaseIdentifier();
final DataContextOfAction dataContextOfAction = dataContextService.checkedGetDataContext(
productIdentifier, caseIdentifier, command.getCommand().getOneTimeAccountAssignments());
- IndividualLendingPatternFactory.checkActionCanBeExecuted(Case.State.valueOf(dataContextOfAction.getCustomerCase().getCurrentState()), Action.OPEN);
+ IndividualLendingPatternFactory.checkActionCanBeExecuted(Case.State.valueOf(dataContextOfAction.getCustomerCaseEntity().getCurrentState()), Action.OPEN);
checkIfTasksAreOutstanding(dataContextOfAction, Action.OPEN);
@@ -109,16 +115,18 @@
.map(Optional::get)
.collect(Collectors.toList());
+ final LocalDateTime today = today();
+
accountingAdapter.bookCharges(charges,
command.getCommand().getNote(),
dataContextOfAction.getMessageForCharge(Action.OPEN),
Action.OPEN.getTransactionType());
//Only move to new state if book charges command was accepted.
- final CaseEntity customerCase = dataContextOfAction.getCustomerCase();
+ final CaseEntity customerCase = dataContextOfAction.getCustomerCaseEntity();
customerCase.setCurrentState(Case.State.PENDING.name());
caseRepository.save(customerCase);
- return new IndividualLoanCommandEvent(productIdentifier, caseIdentifier);
+ return new IndividualLoanCommandEvent(productIdentifier, caseIdentifier, DateConverter.toIsoString(today));
}
@Transactional
@@ -131,7 +139,7 @@
final String caseIdentifier = command.getCaseIdentifier();
final DataContextOfAction dataContextOfAction = dataContextService.checkedGetDataContext(
productIdentifier, caseIdentifier, command.getCommand().getOneTimeAccountAssignments());
- IndividualLendingPatternFactory.checkActionCanBeExecuted(Case.State.valueOf(dataContextOfAction.getCustomerCase().getCurrentState()), Action.DENY);
+ IndividualLendingPatternFactory.checkActionCanBeExecuted(Case.State.valueOf(dataContextOfAction.getCustomerCaseEntity().getCurrentState()), Action.DENY);
checkIfTasksAreOutstanding(dataContextOfAction, Action.DENY);
@@ -150,11 +158,13 @@
.map(Optional::get)
.collect(Collectors.toList());
- final CaseEntity customerCase = dataContextOfAction.getCustomerCase();
+ final LocalDateTime today = today();
+
+ final CaseEntity customerCase = dataContextOfAction.getCustomerCaseEntity();
customerCase.setCurrentState(Case.State.CLOSED.name());
caseRepository.save(customerCase);
- return new IndividualLoanCommandEvent(productIdentifier, caseIdentifier);
+ return new IndividualLoanCommandEvent(productIdentifier, caseIdentifier, DateConverter.toIsoString(today));
}
@Transactional
@@ -167,7 +177,7 @@
final String caseIdentifier = command.getCaseIdentifier();
final DataContextOfAction dataContextOfAction = dataContextService.checkedGetDataContext(
productIdentifier, caseIdentifier, command.getCommand().getOneTimeAccountAssignments());
- IndividualLendingPatternFactory.checkActionCanBeExecuted(Case.State.valueOf(dataContextOfAction.getCustomerCase().getCurrentState()), Action.APPROVE);
+ IndividualLendingPatternFactory.checkActionCanBeExecuted(Case.State.valueOf(dataContextOfAction.getCustomerCaseEntity().getCurrentState()), Action.APPROVE);
checkIfTasksAreOutstanding(dataContextOfAction, Action.APPROVE);
@@ -178,12 +188,12 @@
designatorToAccountIdentifierMapper.getLedgersNeedingAccounts()
.map(ledger ->
new AccountAssignment(ledger.getDesignator(),
- accountingAdapter.createAccountForLedgerAssignment(dataContextOfAction.getCaseParameters().getCustomerIdentifier(), ledger)))
- .map(accountAssignment -> CaseMapper.map(accountAssignment, dataContextOfAction.getCustomerCase()))
+ accountingAdapter.createAccountForLedgerAssignment(dataContextOfAction.getCaseParametersEntity().getCustomerIdentifier(), ledger)))
+ .map(accountAssignment -> CaseMapper.map(accountAssignment, dataContextOfAction.getCustomerCaseEntity()))
.forEach(caseAccountAssignmentEntity ->
- dataContextOfAction.getCustomerCase().getAccountAssignments().add(caseAccountAssignmentEntity)
+ dataContextOfAction.getCustomerCaseEntity().getAccountAssignments().add(caseAccountAssignmentEntity)
);
- caseRepository.save(dataContextOfAction.getCustomerCase());
+ caseRepository.save(dataContextOfAction.getCustomerCaseEntity());
final CostComponentsForRepaymentPeriod costComponentsForRepaymentPeriod =
costComponentService.getCostComponentsForApprove(dataContextOfAction);
@@ -197,17 +207,19 @@
.map(Optional::get)
.collect(Collectors.toList());
+ final LocalDateTime today = today();
+
accountingAdapter.bookCharges(charges,
command.getCommand().getNote(),
dataContextOfAction.getMessageForCharge(Action.APPROVE),
Action.APPROVE.getTransactionType());
//Only move to new state if book charges command was accepted.
- final CaseEntity customerCase = dataContextOfAction.getCustomerCase();
+ final CaseEntity customerCase = dataContextOfAction.getCustomerCaseEntity();
customerCase.setCurrentState(Case.State.APPROVED.name());
caseRepository.save(customerCase);
- return new IndividualLoanCommandEvent(productIdentifier, caseIdentifier);
+ return new IndividualLoanCommandEvent(productIdentifier, caseIdentifier, DateConverter.toIsoString(today));
}
@Transactional
@@ -218,7 +230,7 @@
final String caseIdentifier = command.getCaseIdentifier();
final DataContextOfAction dataContextOfAction = dataContextService.checkedGetDataContext(
productIdentifier, caseIdentifier, command.getCommand().getOneTimeAccountAssignments());
- IndividualLendingPatternFactory.checkActionCanBeExecuted(Case.State.valueOf(dataContextOfAction.getCustomerCase().getCurrentState()), Action.DISBURSE);
+ IndividualLendingPatternFactory.checkActionCanBeExecuted(Case.State.valueOf(dataContextOfAction.getCustomerCaseEntity().getCurrentState()), Action.DISBURSE);
checkIfTasksAreOutstanding(dataContextOfAction, Action.DISBURSE);
@@ -239,22 +251,33 @@
.map(Optional::get)
.collect(Collectors.toList());
+ final LocalDateTime today = today();
+
accountingAdapter.bookCharges(charges,
command.getCommand().getNote(),
dataContextOfAction.getMessageForCharge(Action.DISBURSE),
Action.DISBURSE.getTransactionType());
//Only move to new state if book charges command was accepted.
- if (Case.State.valueOf(dataContextOfAction.getCustomerCase().getCurrentState()) != Case.State.ACTIVE) {
- final CaseEntity customerCase = dataContextOfAction.getCustomerCase();
+ if (Case.State.valueOf(dataContextOfAction.getCustomerCaseEntity().getCurrentState()) != Case.State.ACTIVE) {
+ final CaseEntity customerCase = dataContextOfAction.getCustomerCaseEntity();
final LocalDateTime endOfTerm
- = ScheduledActionHelpers.getRoughEndDate(LocalDate.now(ZoneId.of("UTC")), dataContextOfAction.getCaseParameters())
+ = ScheduledActionHelpers.getRoughEndDate(today.toLocalDate(), dataContextOfAction.getCaseParameters())
.atTime(LocalTime.MIDNIGHT);
customerCase.setEndOfTerm(endOfTerm);
customerCase.setCurrentState(Case.State.ACTIVE.name());
caseRepository.save(customerCase);
}
+ final String customerLoanAccountIdentifier = designatorToAccountIdentifierMapper.mapOrThrow(AccountDesignators.CUSTOMER_LOAN);
+ final BigDecimal currentBalance = accountingAdapter.getCurrentBalance(customerLoanAccountIdentifier).negate();
- return new IndividualLoanCommandEvent(productIdentifier, caseIdentifier);
+ final BigDecimal newLoanPaymentSize = costComponentService.getLoanPaymentSize(
+ currentBalance.add(disbursalAmount),
+ dataContextOfAction);
+
+ dataContextOfAction.getCaseParametersEntity().setPaymentSize(newLoanPaymentSize);
+ caseParametersRepository.save(dataContextOfAction.getCaseParametersEntity());
+
+ return new IndividualLoanCommandEvent(productIdentifier, caseIdentifier, DateConverter.toIsoString(today));
}
@Transactional
@@ -267,9 +290,9 @@
final String caseIdentifier = command.getCaseIdentifier();
final DataContextOfAction dataContextOfAction = dataContextService.checkedGetDataContext(
productIdentifier, caseIdentifier, null);
- IndividualLendingPatternFactory.checkActionCanBeExecuted(Case.State.valueOf(dataContextOfAction.getCustomerCase().getCurrentState()), Action.APPLY_INTEREST);
+ IndividualLendingPatternFactory.checkActionCanBeExecuted(Case.State.valueOf(dataContextOfAction.getCustomerCaseEntity().getCurrentState()), Action.APPLY_INTEREST);
- if (dataContextOfAction.getCustomerCase().getEndOfTerm() == null)
+ if (dataContextOfAction.getCustomerCaseEntity().getEndOfTerm() == null)
throw ServiceException.internalError(
"End of term not set for active case ''{0}.{1}.''", productIdentifier, caseIdentifier);
@@ -293,7 +316,7 @@
dataContextOfAction.getMessageForCharge(Action.APPLY_INTEREST),
Action.APPLY_INTEREST.getTransactionType());
- return new IndividualLoanCommandEvent(productIdentifier, caseIdentifier);
+ return new IndividualLoanCommandEvent(productIdentifier, caseIdentifier, command.getForTime());
}
@Transactional
@@ -306,11 +329,11 @@
final String caseIdentifier = command.getCaseIdentifier();
final DataContextOfAction dataContextOfAction = dataContextService.checkedGetDataContext(
productIdentifier, caseIdentifier, command.getCommand().getOneTimeAccountAssignments());
- IndividualLendingPatternFactory.checkActionCanBeExecuted(Case.State.valueOf(dataContextOfAction.getCustomerCase().getCurrentState()), Action.ACCEPT_PAYMENT);
+ IndividualLendingPatternFactory.checkActionCanBeExecuted(Case.State.valueOf(dataContextOfAction.getCustomerCaseEntity().getCurrentState()), Action.ACCEPT_PAYMENT);
checkIfTasksAreOutstanding(dataContextOfAction, Action.ACCEPT_PAYMENT);
- if (dataContextOfAction.getCustomerCase().getEndOfTerm() == null)
+ if (dataContextOfAction.getCustomerCaseEntity().getEndOfTerm() == null)
throw ServiceException.internalError(
"End of term not set for active case ''{0}.{1}.''", productIdentifier, caseIdentifier);
@@ -334,13 +357,14 @@
.map(Optional::get)
.collect(Collectors.toList());
+ final LocalDateTime today = today();
accountingAdapter.bookCharges(charges,
command.getCommand().getNote(),
dataContextOfAction.getMessageForCharge(Action.ACCEPT_PAYMENT),
Action.ACCEPT_PAYMENT.getTransactionType());
- return new IndividualLoanCommandEvent(command.getProductIdentifier(), command.getCaseIdentifier());
+ return new IndividualLoanCommandEvent(productIdentifier, caseIdentifier, DateConverter.toIsoString(today));
}
@Transactional
@@ -351,14 +375,17 @@
final String caseIdentifier = command.getCaseIdentifier();
final DataContextOfAction dataContextOfAction = dataContextService.checkedGetDataContext(
productIdentifier, caseIdentifier, command.getCommand().getOneTimeAccountAssignments());
- IndividualLendingPatternFactory.checkActionCanBeExecuted(Case.State.valueOf(dataContextOfAction.getCustomerCase().getCurrentState()), Action.WRITE_OFF);
+ IndividualLendingPatternFactory.checkActionCanBeExecuted(Case.State.valueOf(dataContextOfAction.getCustomerCaseEntity().getCurrentState()), Action.WRITE_OFF);
checkIfTasksAreOutstanding(dataContextOfAction, Action.WRITE_OFF);
- final CaseEntity customerCase = dataContextOfAction.getCustomerCase();
+ final LocalDateTime today = today();
+
+ final CaseEntity customerCase = dataContextOfAction.getCustomerCaseEntity();
customerCase.setCurrentState(Case.State.CLOSED.name());
caseRepository.save(customerCase);
- return new IndividualLoanCommandEvent(command.getProductIdentifier(), command.getCaseIdentifier());
+
+ return new IndividualLoanCommandEvent(productIdentifier, caseIdentifier, DateConverter.toIsoString(today));
}
@Transactional
@@ -369,7 +396,7 @@
final String caseIdentifier = command.getCaseIdentifier();
final DataContextOfAction dataContextOfAction = dataContextService.checkedGetDataContext(
productIdentifier, caseIdentifier, command.getCommand().getOneTimeAccountAssignments());
- IndividualLendingPatternFactory.checkActionCanBeExecuted(Case.State.valueOf(dataContextOfAction.getCustomerCase().getCurrentState()), Action.CLOSE);
+ IndividualLendingPatternFactory.checkActionCanBeExecuted(Case.State.valueOf(dataContextOfAction.getCustomerCaseEntity().getCurrentState()), Action.CLOSE);
checkIfTasksAreOutstanding(dataContextOfAction, Action.CLOSE);
@@ -389,15 +416,18 @@
.map(Optional::get)
.collect(Collectors.toList());
+ final LocalDateTime today = today();
+
accountingAdapter.bookCharges(charges,
command.getCommand().getNote(),
dataContextOfAction.getMessageForCharge(Action.DISBURSE),
Action.DISBURSE.getTransactionType());
- final CaseEntity customerCase = dataContextOfAction.getCustomerCase();
+ final CaseEntity customerCase = dataContextOfAction.getCustomerCaseEntity();
customerCase.setCurrentState(Case.State.CLOSED.name());
caseRepository.save(customerCase);
- return new IndividualLoanCommandEvent(command.getProductIdentifier(), command.getCaseIdentifier());
+
+ return new IndividualLoanCommandEvent(productIdentifier, caseIdentifier, DateConverter.toIsoString(today));
}
@Transactional
@@ -408,14 +438,17 @@
final String caseIdentifier = command.getCaseIdentifier();
final DataContextOfAction dataContextOfAction = dataContextService.checkedGetDataContext(
productIdentifier, caseIdentifier, command.getCommand().getOneTimeAccountAssignments());
- IndividualLendingPatternFactory.checkActionCanBeExecuted(Case.State.valueOf(dataContextOfAction.getCustomerCase().getCurrentState()), Action.RECOVER);
+ IndividualLendingPatternFactory.checkActionCanBeExecuted(Case.State.valueOf(dataContextOfAction.getCustomerCaseEntity().getCurrentState()), Action.RECOVER);
checkIfTasksAreOutstanding(dataContextOfAction, Action.RECOVER);
- final CaseEntity customerCase = dataContextOfAction.getCustomerCase();
+ final LocalDateTime today = today();
+
+ final CaseEntity customerCase = dataContextOfAction.getCustomerCaseEntity();
customerCase.setCurrentState(Case.State.CLOSED.name());
caseRepository.save(customerCase);
- return new IndividualLoanCommandEvent(command.getProductIdentifier(), command.getCaseIdentifier());
+
+ return new IndividualLoanCommandEvent(productIdentifier, caseIdentifier, DateConverter.toIsoString(today));
}
private static Optional<ChargeInstance> mapCostComponentEntryToChargeInstance(
@@ -461,12 +494,16 @@
}
private void checkIfTasksAreOutstanding(final DataContextOfAction dataContextOfAction, final Action action) {
- final String productIdentifier = dataContextOfAction.getProduct().getIdentifier();
- final String caseIdentifier = dataContextOfAction.getCustomerCase().getIdentifier();
+ final String productIdentifier = dataContextOfAction.getProductEntity().getIdentifier();
+ final String caseIdentifier = dataContextOfAction.getCustomerCaseEntity().getIdentifier();
final boolean tasksOutstanding = taskInstanceRepository.areTasksOutstanding(
productIdentifier, caseIdentifier, action.name());
if (tasksOutstanding)
throw ServiceException.conflict("Cannot execute action ''{0}'' for case ''{1}.{2}'' because tasks are incomplete.",
action.name(), productIdentifier, caseIdentifier);
}
+
+ private LocalDateTime today() {
+ return LocalDate.now(Clock.systemUTC()).atStartOfDay();
+ }
}
\ No newline at end of file
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 1655266..c1231f1 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
@@ -34,7 +34,9 @@
*/
public class CaseParametersMapper {
- public static CaseParametersEntity map(final Long caseId, final CaseParameters instance) {
+ public static CaseParametersEntity map(
+ final Long caseId,
+ final CaseParameters instance) {
final CaseParametersEntity ret = new CaseParametersEntity();
ret.setCaseId(caseId);
@@ -49,6 +51,7 @@
ret.setPaymentCycleAlignmentWeek(instance.getPaymentCycle().getAlignmentWeek());
ret.setPaymentCycleAlignmentMonth(instance.getPaymentCycle().getAlignmentMonth());
ret.setCreditWorthinessFactors(mapSnapshotsToFactors(instance.getCreditWorthinessSnapshots(), ret));
+ ret.setPaymentSize(BigDecimal.ONE.negate()); //semaphore for not yet set.
return ret;
}
@@ -56,6 +59,8 @@
public static Set<CaseCreditWorthinessFactorEntity> mapSnapshotsToFactors(
final List<CreditWorthinessSnapshot> creditWorthinessSnapshots,
final CaseParametersEntity caseParametersEntity) {
+ if (creditWorthinessSnapshots == null)
+ return Collections.emptySet();
return Stream.iterate(0, i -> i+1).limit(creditWorthinessSnapshots.size())
.flatMap(i -> mapSnapshotToFactors(
creditWorthinessSnapshots.get(i), i, caseParametersEntity)).collect(Collectors.toSet());
@@ -144,6 +149,7 @@
ret.setTermRange(getTermRange(caseParametersEntity));
ret.setMaximumBalance(caseParametersEntity.getBalanceRangeMaximum().setScale(minorCurrencyUnitDigits, BigDecimal.ROUND_HALF_EVEN));
ret.setPaymentCycle(getPaymentCycle(caseParametersEntity));
+ ret.setPaymentSize(caseParametersEntity.getPaymentSize());
return ret;
}
diff --git a/service/src/main/java/io/mifos/individuallending/internal/repository/CaseParametersEntity.java b/service/src/main/java/io/mifos/individuallending/internal/repository/CaseParametersEntity.java
index b9ecf6b..00d6176 100644
--- a/service/src/main/java/io/mifos/individuallending/internal/repository/CaseParametersEntity.java
+++ b/service/src/main/java/io/mifos/individuallending/internal/repository/CaseParametersEntity.java
@@ -70,6 +70,9 @@
@OneToMany(targetEntity = CaseCreditWorthinessFactorEntity.class, cascade = CascadeType.ALL, orphanRemoval = true, mappedBy = "caseId")
private Set<CaseCreditWorthinessFactorEntity> creditWorthinessFactors;
+ @Column(name = "payment_size")
+ private BigDecimal paymentSize;
+
public CaseParametersEntity() {
}
@@ -177,6 +180,14 @@
this.creditWorthinessFactors = creditWorthinessFactors;
}
+ public BigDecimal getPaymentSize() {
+ return paymentSize;
+ }
+
+ public void setPaymentSize(BigDecimal paymentSize) {
+ this.paymentSize = paymentSize;
+ }
+
@Override
public boolean equals(Object o) {
if (this == o) return true;
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 c3ba820..5f73add 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
@@ -16,10 +16,10 @@
package io.mifos.individuallending.internal.service;
import io.mifos.core.lang.ServiceException;
-import io.mifos.individuallending.api.v1.domain.caseinstance.CaseParameters;
import io.mifos.individuallending.api.v1.domain.product.AccountDesignators;
import io.mifos.individuallending.api.v1.domain.product.ChargeProportionalDesignator;
import io.mifos.individuallending.api.v1.domain.workflow.Action;
+import io.mifos.individuallending.internal.repository.CaseParametersEntity;
import io.mifos.portfolio.api.v1.domain.ChargeDefinition;
import io.mifos.portfolio.api.v1.domain.CostComponent;
import io.mifos.portfolio.service.internal.util.AccountingAdapter;
@@ -89,9 +89,9 @@
}
public CostComponentsForRepaymentPeriod getCostComponentsForOpen(final DataContextOfAction dataContextOfAction) {
- final CaseParameters caseParameters = dataContextOfAction.getCaseParameters();
- final String productIdentifier = dataContextOfAction.getProduct().getIdentifier();
- final int minorCurrencyUnitDigits = dataContextOfAction.getProduct().getMinorCurrencyUnitDigits();
+ final CaseParametersEntity caseParameters = dataContextOfAction.getCaseParametersEntity();
+ final String productIdentifier = dataContextOfAction.getProductEntity().getIdentifier();
+ final int minorCurrencyUnitDigits = dataContextOfAction.getProductEntity().getMinorCurrencyUnitDigits();
final List<ScheduledAction> scheduledActions = Collections.singletonList(new ScheduledAction(Action.OPEN, today()));
final List<ScheduledCharge> scheduledCharges = scheduledChargesService.getScheduledCharges(
productIdentifier, scheduledActions);
@@ -99,7 +99,7 @@
return getCostComponentsForScheduledCharges(
Collections.emptyMap(),
scheduledCharges,
- caseParameters.getMaximumBalance(),
+ caseParameters.getBalanceRangeMaximum(),
BigDecimal.ZERO,
BigDecimal.ZERO,
dataContextOfAction.getInterest(),
@@ -108,9 +108,9 @@
}
public CostComponentsForRepaymentPeriod getCostComponentsForDeny(final DataContextOfAction dataContextOfAction) {
- final CaseParameters caseParameters = dataContextOfAction.getCaseParameters();
- final String productIdentifier = dataContextOfAction.getProduct().getIdentifier();
- final int minorCurrencyUnitDigits = dataContextOfAction.getProduct().getMinorCurrencyUnitDigits();
+ final CaseParametersEntity caseParameters = dataContextOfAction.getCaseParametersEntity();
+ final String productIdentifier = dataContextOfAction.getProductEntity().getIdentifier();
+ final int minorCurrencyUnitDigits = dataContextOfAction.getProductEntity().getMinorCurrencyUnitDigits();
final List<ScheduledAction> scheduledActions = Collections.singletonList(new ScheduledAction(Action.DENY, today()));
final List<ScheduledCharge> scheduledCharges = scheduledChargesService.getScheduledCharges(
productIdentifier, scheduledActions);
@@ -118,7 +118,7 @@
return getCostComponentsForScheduledCharges(
Collections.emptyMap(),
scheduledCharges,
- caseParameters.getMaximumBalance(),
+ caseParameters.getBalanceRangeMaximum(),
BigDecimal.ZERO,
BigDecimal.ZERO,
dataContextOfAction.getInterest(),
@@ -128,9 +128,9 @@
public CostComponentsForRepaymentPeriod getCostComponentsForApprove(final DataContextOfAction dataContextOfAction) {
//Charge the approval fee if applicable.
- final CaseParameters caseParameters = dataContextOfAction.getCaseParameters();
- final String productIdentifier = dataContextOfAction.getProduct().getIdentifier();
- final int minorCurrencyUnitDigits = dataContextOfAction.getProduct().getMinorCurrencyUnitDigits();
+ final CaseParametersEntity caseParameters = dataContextOfAction.getCaseParametersEntity();
+ final String productIdentifier = dataContextOfAction.getProductEntity().getIdentifier();
+ final int minorCurrencyUnitDigits = dataContextOfAction.getProductEntity().getMinorCurrencyUnitDigits();
final List<ScheduledAction> scheduledActions = Collections.singletonList(new ScheduledAction(Action.APPROVE, today()));
final List<ScheduledCharge> scheduledCharges = scheduledChargesService.getScheduledCharges(
productIdentifier, scheduledActions);
@@ -138,7 +138,7 @@
return getCostComponentsForScheduledCharges(
Collections.emptyMap(),
scheduledCharges,
- caseParameters.getMaximumBalance(),
+ caseParameters.getBalanceRangeMaximum(),
BigDecimal.ZERO,
BigDecimal.ZERO,
dataContextOfAction.getInterest(),
@@ -155,21 +155,21 @@
final BigDecimal currentBalance = accountingAdapter.getCurrentBalance(customerLoanAccountIdentifier).negate();
if (requestedDisbursalSize != null &&
- dataContextOfAction.getCaseParameters().getMaximumBalance().compareTo(
+ dataContextOfAction.getCaseParametersEntity().getBalanceRangeMaximum().compareTo(
currentBalance.add(requestedDisbursalSize)) < 0)
throw ServiceException.conflict("Cannot disburse over the maximum balance.");
final Optional<LocalDateTime> optionalStartOfTerm = accountingAdapter.getDateOfOldestEntryContainingMessage(
customerLoanAccountIdentifier,
dataContextOfAction.getMessageForCharge(Action.DISBURSE));
- final CaseParameters caseParameters = dataContextOfAction.getCaseParameters();
- final String productIdentifier = dataContextOfAction.getProduct().getIdentifier();
- final int minorCurrencyUnitDigits = dataContextOfAction.getProduct().getMinorCurrencyUnitDigits();
+ final CaseParametersEntity caseParameters = dataContextOfAction.getCaseParametersEntity();
+ final String productIdentifier = dataContextOfAction.getProductEntity().getIdentifier();
+ final int minorCurrencyUnitDigits = dataContextOfAction.getProductEntity().getMinorCurrencyUnitDigits();
final List<ScheduledAction> scheduledActions = Collections.singletonList(new ScheduledAction(Action.DISBURSE, today()));
final BigDecimal disbursalSize;
if (requestedDisbursalSize == null)
- disbursalSize = dataContextOfAction.getCaseParameters().getMaximumBalance().negate();
+ disbursalSize = dataContextOfAction.getCaseParametersEntity().getBalanceRangeMaximum().negate();
else
disbursalSize = requestedDisbursalSize.negate();
@@ -195,7 +195,7 @@
return getCostComponentsForScheduledCharges(
accruedCostComponents,
chargesSplitIntoScheduledAndAccrued.get(false),
- caseParameters.getMaximumBalance(),
+ caseParameters.getBalanceRangeMaximum(),
currentBalance,
disbursalSize,
dataContextOfAction.getInterest(),
@@ -213,9 +213,9 @@
final LocalDate startOfTerm = getStartOfTermOrThrow(dataContextOfAction, customerLoanAccountIdentifier);
- final CaseParameters caseParameters = dataContextOfAction.getCaseParameters();
- final String productIdentifier = dataContextOfAction.getProduct().getIdentifier();
- final int minorCurrencyUnitDigits = dataContextOfAction.getProduct().getMinorCurrencyUnitDigits();
+ final CaseParametersEntity caseParameters = dataContextOfAction.getCaseParametersEntity();
+ final String productIdentifier = dataContextOfAction.getProductEntity().getIdentifier();
+ final int minorCurrencyUnitDigits = dataContextOfAction.getProductEntity().getMinorCurrencyUnitDigits();
final LocalDate today = today();
final ScheduledAction interestAction = new ScheduledAction(Action.APPLY_INTEREST, today, new Period(1, today));
@@ -235,7 +235,7 @@
return getCostComponentsForScheduledCharges(
accruedCostComponents,
chargesSplitIntoScheduledAndAccrued.get(false),
- caseParameters.getMaximumBalance(),
+ caseParameters.getBalanceRangeMaximum(),
currentBalance,
BigDecimal.ZERO,
dataContextOfAction.getInterest(),
@@ -254,32 +254,20 @@
final LocalDate startOfTerm = getStartOfTermOrThrow(dataContextOfAction, customerLoanAccountIdentifier);
- final CaseParameters caseParameters = dataContextOfAction.getCaseParameters();
- final String productIdentifier = dataContextOfAction.getProduct().getIdentifier();
- final int minorCurrencyUnitDigits = dataContextOfAction.getProduct().getMinorCurrencyUnitDigits();
+ final CaseParametersEntity caseParameters = dataContextOfAction.getCaseParametersEntity();
+ final String productIdentifier = dataContextOfAction.getProductEntity().getIdentifier();
+ final int minorCurrencyUnitDigits = dataContextOfAction.getProductEntity().getMinorCurrencyUnitDigits();
final ScheduledAction scheduledAction
= ScheduledActionHelpers.getNextScheduledPayment(
startOfTerm,
- dataContextOfAction.getCustomerCase().getEndOfTerm().toLocalDate(),
- caseParameters
+ dataContextOfAction.getCustomerCaseEntity().getEndOfTerm().toLocalDate(),
+ dataContextOfAction.getCaseParameters()
);
- final BigDecimal loanPaymentSize;
- if (requestedLoanPaymentSize != null)
- loanPaymentSize = requestedLoanPaymentSize;
- else {
- final List<ScheduledAction> hypotheticalScheduledActions = ScheduledActionHelpers.getHypotheticalScheduledActions(
- today(),
- caseParameters);
- final List<ScheduledCharge> hypotheticalScheduledCharges = scheduledChargesService.getScheduledCharges(
- productIdentifier,
- hypotheticalScheduledActions);
- loanPaymentSize = getLoanPaymentSize(
- currentBalance,
- dataContextOfAction.getInterest(),
- minorCurrencyUnitDigits,
- hypotheticalScheduledCharges);
- }
+ final BigDecimal loanPaymentSize =
+ (requestedLoanPaymentSize != null) ?
+ requestedLoanPaymentSize :
+ dataContextOfAction.getCaseParametersEntity().getPaymentSize();
final List<ScheduledCharge> scheduledChargesForThisAction = scheduledChargesService.getScheduledCharges(
productIdentifier,
@@ -299,7 +287,7 @@
return getCostComponentsForScheduledCharges(
accruedCostComponents,
chargesSplitIntoScheduledAndAccrued.get(false),
- caseParameters.getMaximumBalance(),
+ caseParameters.getBalanceRangeMaximum(),
currentBalance,
loanPaymentSize,
dataContextOfAction.getInterest(),
@@ -361,9 +349,9 @@
final LocalDate startOfTerm = getStartOfTermOrThrow(dataContextOfAction, customerLoanAccountIdentifier);
- final CaseParameters caseParameters = dataContextOfAction.getCaseParameters();
- final String productIdentifier = dataContextOfAction.getProduct().getIdentifier();
- final int minorCurrencyUnitDigits = dataContextOfAction.getProduct().getMinorCurrencyUnitDigits();
+ final CaseParametersEntity caseParameters = dataContextOfAction.getCaseParametersEntity();
+ final String productIdentifier = dataContextOfAction.getProductEntity().getIdentifier();
+ final int minorCurrencyUnitDigits = dataContextOfAction.getProductEntity().getMinorCurrencyUnitDigits();
final LocalDate today = today();
final ScheduledAction closeAction = new ScheduledAction(Action.CLOSE, today, new Period(1, today));
@@ -383,7 +371,7 @@
return getCostComponentsForScheduledCharges(
accruedCostComponents,
chargesSplitIntoScheduledAndAccrued.get(false),
- caseParameters.getMaximumBalance(),
+ caseParameters.getBalanceRangeMaximum(),
currentBalance,
BigDecimal.ZERO,
dataContextOfAction.getInterest(),
@@ -534,6 +522,22 @@
}
}
+ public BigDecimal getLoanPaymentSize(
+ final BigDecimal assumedBalance,
+ final DataContextOfAction dataContextOfAction) {
+ final List<ScheduledAction> hypotheticalScheduledActions = ScheduledActionHelpers.getHypotheticalScheduledActions(
+ today(),
+ dataContextOfAction.getCaseParameters());
+ final List<ScheduledCharge> hypotheticalScheduledCharges = scheduledChargesService.getScheduledCharges(
+ dataContextOfAction.getProductEntity().getIdentifier(),
+ hypotheticalScheduledActions);
+ return getLoanPaymentSize(
+ assumedBalance,
+ dataContextOfAction.getInterest(),
+ dataContextOfAction.getProductEntity().getMinorCurrencyUnitDigits(),
+ hypotheticalScheduledCharges);
+ }
+
static BigDecimal getLoanPaymentSize(final BigDecimal startingBalance,
final BigDecimal interest,
final int minorCurrencyUnitDigits,
diff --git a/service/src/main/java/io/mifos/individuallending/internal/service/DataContextOfAction.java b/service/src/main/java/io/mifos/individuallending/internal/service/DataContextOfAction.java
index 4cb7b5d..3d0e968 100644
--- a/service/src/main/java/io/mifos/individuallending/internal/service/DataContextOfAction.java
+++ b/service/src/main/java/io/mifos/individuallending/internal/service/DataContextOfAction.java
@@ -17,6 +17,8 @@
import io.mifos.individuallending.api.v1.domain.caseinstance.CaseParameters;
import io.mifos.individuallending.api.v1.domain.workflow.Action;
+import io.mifos.individuallending.internal.mapper.CaseParametersMapper;
+import io.mifos.individuallending.internal.repository.CaseParametersEntity;
import io.mifos.portfolio.api.v1.domain.AccountAssignment;
import io.mifos.portfolio.service.internal.repository.CaseEntity;
import io.mifos.portfolio.service.internal.repository.ProductEntity;
@@ -33,12 +35,12 @@
public class DataContextOfAction {
private final ProductEntity product;
private final CaseEntity customerCase;
- private final CaseParameters caseParameters;
+ private final CaseParametersEntity caseParameters;
private final List<AccountAssignment> oneTimeAccountAssignments;
DataContextOfAction(final @Nonnull ProductEntity product,
final @Nonnull CaseEntity customerCase,
- final @Nonnull CaseParameters caseParameters,
+ final @Nonnull CaseParametersEntity caseParameters,
final @Nullable List<AccountAssignment> oneTimeAccountAssignments) {
this.product = product;
this.customerCase = customerCase;
@@ -46,18 +48,22 @@
this.oneTimeAccountAssignments = oneTimeAccountAssignments == null ? Collections.emptyList() : oneTimeAccountAssignments;
}
- public @Nonnull ProductEntity getProduct() {
+ public @Nonnull ProductEntity getProductEntity() {
return product;
}
- public @Nonnull CaseEntity getCustomerCase() {
+ public @Nonnull CaseEntity getCustomerCaseEntity() {
return customerCase;
}
- public @Nonnull CaseParameters getCaseParameters() {
+ public @Nonnull CaseParametersEntity getCaseParametersEntity() {
return caseParameters;
}
+ public @Nonnull CaseParameters getCaseParameters() {
+ return CaseParametersMapper.mapEntity(caseParameters, product.getMinorCurrencyUnitDigits());
+ }
+
@Nonnull List<AccountAssignment> getOneTimeAccountAssignments() {
return oneTimeAccountAssignments;
}
diff --git a/service/src/main/java/io/mifos/individuallending/internal/service/DataContextService.java b/service/src/main/java/io/mifos/individuallending/internal/service/DataContextService.java
index 225d0a6..388ae0c 100644
--- a/service/src/main/java/io/mifos/individuallending/internal/service/DataContextService.java
+++ b/service/src/main/java/io/mifos/individuallending/internal/service/DataContextService.java
@@ -16,8 +16,7 @@
package io.mifos.individuallending.internal.service;
import io.mifos.core.lang.ServiceException;
-import io.mifos.individuallending.api.v1.domain.caseinstance.CaseParameters;
-import io.mifos.individuallending.internal.mapper.CaseParametersMapper;
+import io.mifos.individuallending.internal.repository.CaseParametersEntity;
import io.mifos.individuallending.internal.repository.CaseParametersRepository;
import io.mifos.portfolio.api.v1.domain.AccountAssignment;
import io.mifos.portfolio.service.internal.repository.CaseEntity;
@@ -61,9 +60,8 @@
caseRepository.findByProductIdentifierAndIdentifier(productIdentifier, caseIdentifier)
.orElseThrow(() -> ServiceException.notFound("Case not found ''{0}.{1}''.", productIdentifier, caseIdentifier));
- final CaseParameters caseParameters =
+ final CaseParametersEntity caseParameters =
caseParametersRepository.findByCaseId(customerCase.getId())
- .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/DesignatorToAccountIdentifierMapper.java b/service/src/main/java/io/mifos/individuallending/internal/service/DesignatorToAccountIdentifierMapper.java
index 5b2fb94..2bd9e57 100644
--- a/service/src/main/java/io/mifos/individuallending/internal/service/DesignatorToAccountIdentifierMapper.java
+++ b/service/src/main/java/io/mifos/individuallending/internal/service/DesignatorToAccountIdentifierMapper.java
@@ -37,8 +37,8 @@
private final @Nonnull List<AccountAssignment> oneTimeAccountAssignments;
public DesignatorToAccountIdentifierMapper(final @Nonnull DataContextOfAction dataContextOfAction) {
- this.productAccountAssignments = dataContextOfAction.getProduct().getAccountAssignments();
- this.caseAccountAssignments = dataContextOfAction.getCustomerCase().getAccountAssignments();
+ this.productAccountAssignments = dataContextOfAction.getProductEntity().getAccountAssignments();
+ this.caseAccountAssignments = dataContextOfAction.getCustomerCaseEntity().getAccountAssignments();
this.oneTimeAccountAssignments = dataContextOfAction.getOneTimeAccountAssignments();
}
diff --git a/service/src/main/java/io/mifos/individuallending/internal/service/IndividualLoanService.java b/service/src/main/java/io/mifos/individuallending/internal/service/IndividualLoanService.java
index e836004..6108651 100644
--- a/service/src/main/java/io/mifos/individuallending/internal/service/IndividualLoanService.java
+++ b/service/src/main/java/io/mifos/individuallending/internal/service/IndividualLoanService.java
@@ -45,20 +45,20 @@
final int pageIndex,
final int size,
final @Nonnull LocalDate initialDisbursalDate) {
- final int minorCurrencyUnitDigits = dataContextOfAction.getProduct().getMinorCurrencyUnitDigits();
+ final int minorCurrencyUnitDigits = dataContextOfAction.getProductEntity().getMinorCurrencyUnitDigits();
final List<ScheduledAction> scheduledActions = ScheduledActionHelpers.getHypotheticalScheduledActions(initialDisbursalDate, dataContextOfAction.getCaseParameters());
- final List<ScheduledCharge> scheduledCharges = scheduledChargesService.getScheduledCharges(dataContextOfAction.getProduct().getIdentifier(), scheduledActions);
+ final List<ScheduledCharge> scheduledCharges = scheduledChargesService.getScheduledCharges(dataContextOfAction.getProductEntity().getIdentifier(), scheduledActions);
final BigDecimal loanPaymentSize = CostComponentService.getLoanPaymentSize(
- dataContextOfAction.getCaseParameters().getMaximumBalance(),
+ dataContextOfAction.getCaseParametersEntity().getBalanceRangeMaximum(),
dataContextOfAction.getInterest(),
minorCurrencyUnitDigits,
scheduledCharges);
final List<PlannedPayment> plannedPaymentsElements = getPlannedPaymentsElements(
- dataContextOfAction.getCaseParameters().getMaximumBalance(),
+ dataContextOfAction.getCaseParametersEntity().getBalanceRangeMaximum(),
minorCurrencyUnitDigits,
scheduledCharges,
loanPaymentSize,
diff --git a/service/src/main/java/io/mifos/portfolio/service/config/PortfolioProperties.java b/service/src/main/java/io/mifos/portfolio/service/config/PortfolioProperties.java
index 25cf294..08beb6b 100644
--- a/service/src/main/java/io/mifos/portfolio/service/config/PortfolioProperties.java
+++ b/service/src/main/java/io/mifos/portfolio/service/config/PortfolioProperties.java
@@ -30,20 +30,23 @@
@Validated
public class PortfolioProperties {
@ValidIdentifier
- private String bookInterestAsUser;
+ private String bookLateFeesAndInterestAsUser;
@Range(min=0, max=23)
private int bookInterestInTimeSlot = 0;
+ @Range(min=0, max=23)
+ private int checkForLatenessInTimeSlot = 0;
+
public PortfolioProperties() {
}
- public String getBookInterestAsUser() {
- return bookInterestAsUser;
+ public String getBookLateFeesAndInterestAsUser() {
+ return bookLateFeesAndInterestAsUser;
}
- public void setBookInterestAsUser(String bookInterestAsUser) {
- this.bookInterestAsUser = bookInterestAsUser;
+ public void setBookLateFeesAndInterestAsUser(String bookLateFeesAndInterestAsUser) {
+ this.bookLateFeesAndInterestAsUser = bookLateFeesAndInterestAsUser;
}
public int getBookInterestInTimeSlot() {
@@ -53,4 +56,12 @@
public void setBookInterestInTimeSlot(int bookInterestInTimeSlot) {
this.bookInterestInTimeSlot = bookInterestInTimeSlot;
}
+
+ public int getCheckForLatenessInTimeSlot() {
+ return checkForLatenessInTimeSlot;
+ }
+
+ public void setCheckForLatenessInTimeSlot(int checkForLatenessInTimeSlot) {
+ this.checkForLatenessInTimeSlot = checkForLatenessInTimeSlot;
+ }
}
diff --git a/service/src/main/resources/db/migrations/mariadb/V8__late_payment_determination.sql b/service/src/main/resources/db/migrations/mariadb/V8__late_payment_determination.sql
new file mode 100644
index 0000000..cb3bc59
--- /dev/null
+++ b/service/src/main/resources/db/migrations/mariadb/V8__late_payment_determination.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_il_cases ADD COLUMN payment_size DECIMAL(19,4) NULL DEFAULT NULL;
\ No newline at end of file
diff --git a/service/src/test/java/io/mifos/individuallending/internal/service/IndividualLoanServiceTest.java b/service/src/test/java/io/mifos/individuallending/internal/service/IndividualLoanServiceTest.java
index fc31ae7..beeae57 100644
--- a/service/src/test/java/io/mifos/individuallending/internal/service/IndividualLoanServiceTest.java
+++ b/service/src/test/java/io/mifos/individuallending/internal/service/IndividualLoanServiceTest.java
@@ -23,6 +23,7 @@
import io.mifos.individuallending.api.v1.domain.product.AccountDesignators;
import io.mifos.individuallending.api.v1.domain.product.ChargeIdentifiers;
import io.mifos.individuallending.api.v1.domain.workflow.Action;
+import io.mifos.individuallending.internal.mapper.CaseParametersMapper;
import io.mifos.portfolio.api.v1.domain.*;
import io.mifos.portfolio.service.internal.repository.BalanceSegmentRepository;
import io.mifos.portfolio.service.internal.repository.CaseEntity;
@@ -152,7 +153,7 @@
final CaseEntity customerCase = new CaseEntity();
customerCase.setInterest(interest);
- return new DataContextOfAction(product, customerCase, caseParameters, Collections.emptyList());
+ return new DataContextOfAction(product, customerCase, CaseParametersMapper.map(1L, caseParameters), Collections.emptyList());
}
@Override