* Fixed regression in loan payment size calculation
* Added comparison to planned payments to loan workflow test.
diff --git a/api/src/main/java/io/mifos/individuallending/api/v1/client/IndividualLending.java b/api/src/main/java/io/mifos/individuallending/api/v1/client/IndividualLending.java
index cfafd66..c2264be 100644
--- a/api/src/main/java/io/mifos/individuallending/api/v1/client/IndividualLending.java
+++ b/api/src/main/java/io/mifos/individuallending/api/v1/client/IndividualLending.java
@@ -15,9 +15,10 @@
*/
package io.mifos.individuallending.api.v1.client;
-import io.mifos.portfolio.api.v1.domain.CasePage;
-import io.mifos.individuallending.api.v1.domain.caseinstance.PlannedPaymentPage;
import io.mifos.core.api.util.CustomFeignClientsConfiguration;
+import io.mifos.individuallending.api.v1.domain.caseinstance.PlannedPayment;
+import io.mifos.individuallending.api.v1.domain.caseinstance.PlannedPaymentPage;
+import io.mifos.portfolio.api.v1.domain.CasePage;
import org.springframework.cloud.netflix.feign.FeignClient;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.PathVariable;
@@ -25,6 +26,8 @@
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
+import java.util.stream.Stream;
+
/**
* @author Myrle Krantz
*/
@@ -37,11 +40,32 @@
produces = MediaType.ALL_VALUE,
consumes = MediaType.APPLICATION_JSON_VALUE
)
- PlannedPaymentPage getPaymentScheduleForCase(@PathVariable("productidentifier") final String productIdentifier,
- @PathVariable("caseidentifier") final String caseIdentifier,
- @RequestParam(value = "pageIndex", required = false) final Integer pageIndex,
- @RequestParam(value = "size", required = false) final Integer size,
- @RequestParam(value = "initialDisbursalDate", required = false) final String initialDisbursalDate);
+ PlannedPaymentPage getPaymentScheduleForCase(
+ @PathVariable("productidentifier") final String productIdentifier,
+ @PathVariable("caseidentifier") final String caseIdentifier,
+ @RequestParam(value = "pageIndex", required = false) final Integer pageIndex,
+ @RequestParam(value = "size", required = false) final Integer size,
+ @RequestParam(value = "initialDisbursalDate", required = false) final String initialDisbursalDate);
+
+ default Stream<PlannedPayment> getPaymentScheduleForCaseStream(
+ final String productIdentifier,
+ final String caseIdentifier,
+ final String initialDisbursalDate) {
+ final PlannedPaymentPage firstPage = this.getPaymentScheduleForCase(
+ productIdentifier,
+ caseIdentifier,
+ 0,
+ 10,
+ initialDisbursalDate);
+
+ final Integer pageCount = firstPage.getTotalPages();
+ // Sort column is always date and order always ascending so that the order and adjacency of account
+ // entries is always stable. This has the advantage that the set of account entries included in the
+ // stream is set the moment the first call to fetchAccountEntries (above) is made.
+ return Stream.iterate(0, (i) -> i + 1).limit(pageCount)
+ .map(i -> this.getPaymentScheduleForCase(productIdentifier, caseIdentifier, i, 10, initialDisbursalDate))
+ .flatMap(pageI -> pageI.getElements().stream());
+ }
@RequestMapping(
value = "/individuallending/customers/{customeridentifier}/cases",
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 2aedaa3..968e9da 100644
--- a/component-test/src/main/java/io/mifos/portfolio/AbstractPortfolioTest.java
+++ b/component-test/src/main/java/io/mifos/portfolio/AbstractPortfolioTest.java
@@ -267,24 +267,29 @@
Assert.assertEquals(actionList, portfolioManager.getActionsForCase(productIdentifier, customerCaseIdentifier));
}
- void checkCostComponentForActionCorrect(final String productIdentifier,
- final String customerCaseIdentifier,
- final Action action,
- final Set<String> accountDesignators,
- final BigDecimal amount,
- final CostComponent... expectedCostComponents) {
+ Payment checkCostComponentForActionCorrect(
+ final String productIdentifier,
+ final String customerCaseIdentifier,
+ final Action action,
+ final Set<String> accountDesignators,
+ final BigDecimal amount,
+ final LocalDateTime forDateTime,
+ final CostComponent... expectedCostComponents) {
final Payment payment = portfolioManager.getCostComponentsForAction(
productIdentifier,
customerCaseIdentifier,
action.name(),
accountDesignators,
- amount
+ amount,
+ DateConverter.toIsoString(forDateTime)
);
final Set<CostComponent> setOfCostComponents = new HashSet<>(payment.getCostComponents());
final Set<CostComponent> setOfExpectedCostComponents = Stream.of(expectedCostComponents)
.filter(x -> x.getAmount().compareTo(BigDecimal.ZERO) != 0)
.collect(Collectors.toSet());
Assert.assertEquals(setOfExpectedCostComponents, setOfCostComponents);
+
+ return payment;
}
void setFeeToFixedValue(final String productIdentifier,
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 bf356e0..0ff31b7 100644
--- a/component-test/src/main/java/io/mifos/portfolio/TestAccountingInteractionInLoanWorkflow.java
+++ b/component-test/src/main/java/io/mifos/portfolio/TestAccountingInteractionInLoanWorkflow.java
@@ -22,6 +22,7 @@
import io.mifos.core.api.util.ApiFactory;
import io.mifos.core.lang.DateConverter;
import io.mifos.individuallending.api.v1.domain.caseinstance.CaseParameters;
+import io.mifos.individuallending.api.v1.domain.caseinstance.PlannedPayment;
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.product.ChargeProportionalDesignator;
@@ -46,6 +47,7 @@
import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;
import java.util.*;
+import java.util.stream.Collectors;
import java.util.stream.IntStream;
import static io.mifos.portfolio.Fixture.MINOR_CURRENCY_UNIT_DIGITS;
@@ -54,8 +56,8 @@
* @author Myrle Krantz
*/
public class TestAccountingInteractionInLoanWorkflow extends AbstractPortfolioTest {
- private static final BigDecimal PROCESSING_FEE_AMOUNT = BigDecimal.valueOf(100_00, MINOR_CURRENCY_UNIT_DIGITS);
- private static final BigDecimal LOAN_ORIGINATION_FEE_AMOUNT = BigDecimal.valueOf(100_00, MINOR_CURRENCY_UNIT_DIGITS);
+ private static final BigDecimal PROCESSING_FEE_AMOUNT = BigDecimal.valueOf(50_00, MINOR_CURRENCY_UNIT_DIGITS);
+ private static final BigDecimal LOAN_ORIGINATION_FEE_AMOUNT = BigDecimal.valueOf(50_00, MINOR_CURRENCY_UNIT_DIGITS);
private static final BigDecimal DISBURSEMENT_FEE_LOWER_RANGE_AMOUNT = BigDecimal.valueOf(10_00, MINOR_CURRENCY_UNIT_DIGITS);
private static final BigDecimal DISBURSEMENT_FEE_UPPER_RANGE_AMOUNT = BigDecimal.valueOf(1_00, MINOR_CURRENCY_UNIT_DIGITS);
private static final String DISBURSEMENT_RANGES = "disbursement_ranges";
@@ -85,10 +87,11 @@
@Test
public void workflowTerminatingInApplicationDenial() throws InterruptedException {
+ final LocalDateTime today = midnightToday();
step1CreateProduct();
step2CreateCase();
- step3OpenCase();
- step4DenyCase();
+ step3OpenCase(today);
+ step4DenyCase(today);
}
@Test
@@ -97,18 +100,18 @@
step1CreateProduct();
step2CreateCase();
- step3OpenCase();
- step4ApproveCase();
+ step3OpenCase(today);
+ step4ApproveCase(today);
step5Disburse(
BigDecimal.valueOf(2_000_00, MINOR_CURRENCY_UNIT_DIGITS),
+ today,
UPPER_RANGE_DISBURSEMENT_FEE_ID, BigDecimal.valueOf(20_00, MINOR_CURRENCY_UNIT_DIGITS));
step6CalculateInterestAccrualAndCheckForLateness(midnightToday(), BigDecimal.ZERO);
step7PaybackPartialAmount(
expectedCurrentPrincipal.add(nonLateFees).add(interestAccrued),
today,
- 0,
BigDecimal.ZERO);
- step8Close();
+ step8Close(today);
}
@Test
@@ -117,21 +120,22 @@
step1CreateProduct();
step2CreateCase();
- step3OpenCase();
- step4ApproveCase();
+ step3OpenCase(today);
+ step4ApproveCase(today);
step5Disburse(
BigDecimal.valueOf(500_00, MINOR_CURRENCY_UNIT_DIGITS),
+ today,
ChargeIdentifiers.DISBURSEMENT_FEE_ID, BigDecimal.valueOf(10_00, MINOR_CURRENCY_UNIT_DIGITS));
step5Disburse(
BigDecimal.valueOf(1_500_00, MINOR_CURRENCY_UNIT_DIGITS),
+ today,
UPPER_RANGE_DISBURSEMENT_FEE_ID, BigDecimal.valueOf(15_00, MINOR_CURRENCY_UNIT_DIGITS));
step6CalculateInterestAccrualAndCheckForLateness(midnightToday(), BigDecimal.ZERO);
step7PaybackPartialAmount(
expectedCurrentPrincipal.add(nonLateFees).add(interestAccrued),
today,
- 0,
BigDecimal.ZERO);
- step8Close();
+ step8Close(today);
}
@Test
@@ -140,29 +144,33 @@
step1CreateProduct();
step2CreateCase();
- step3OpenCase();
- step4ApproveCase();
+ step3OpenCase(today);
+ step4ApproveCase(today);
step5Disburse(
BigDecimal.valueOf(2_000_00, MINOR_CURRENCY_UNIT_DIGITS),
+ today,
UPPER_RANGE_DISBURSEMENT_FEE_ID, BigDecimal.valueOf(20_00, MINOR_CURRENCY_UNIT_DIGITS));
step6CalculateInterestAccrualAndCheckForLateness(midnightToday(), BigDecimal.ZERO);
final BigDecimal repayment1 = expectedCurrentPrincipal.divide(BigDecimal.valueOf(2), BigDecimal.ROUND_HALF_EVEN);
step7PaybackPartialAmount(
repayment1.setScale(MINOR_CURRENCY_UNIT_DIGITS, BigDecimal.ROUND_HALF_EVEN),
today,
- 0, BigDecimal.ZERO);
- step7PaybackPartialAmount(expectedCurrentPrincipal, today, 0, BigDecimal.ZERO);
- step8Close();
+ BigDecimal.ZERO);
+ step7PaybackPartialAmount(expectedCurrentPrincipal, today, BigDecimal.ZERO);
+ step8Close(today);
}
@Test
public void workflowWithNegativePaymentSize() throws InterruptedException {
+ final LocalDateTime today = midnightToday();
+
step1CreateProduct();
step2CreateCase();
- step3OpenCase();
- step4ApproveCase();
+ step3OpenCase(today);
+ step4ApproveCase(today);
try {
step5Disburse(BigDecimal.valueOf(-2).setScale(MINOR_CURRENCY_UNIT_DIGITS, BigDecimal.ROUND_HALF_EVEN),
+ today,
UPPER_RANGE_DISBURSEMENT_FEE_ID, BigDecimal.ZERO.setScale(MINOR_CURRENCY_UNIT_DIGITS, BigDecimal.ROUND_HALF_EVEN));
Assert.fail("Expected an IllegalArgumentException.");
}
@@ -175,38 +183,33 @@
step1CreateProduct();
step2CreateCase();
- step3OpenCase();
- step4ApproveCase();
+ step3OpenCase(today);
+ step4ApproveCase(today);
+
+ final List<PlannedPayment> plannedPayments = individualLending.getPaymentScheduleForCaseStream(
+ product.getIdentifier(),
+ customerCase.getIdentifier(),
+ null)
+ .collect(Collectors.toList());
+
step5Disburse(
BigDecimal.valueOf(2_000_00, MINOR_CURRENCY_UNIT_DIGITS),
+ today,
UPPER_RANGE_DISBURSEMENT_FEE_ID, BigDecimal.valueOf(20_00, MINOR_CURRENCY_UNIT_DIGITS));
int week = 0;
- final List<BigDecimal> repayments = new ArrayList<>();
while (expectedCurrentPrincipal.compareTo(BigDecimal.ZERO) > 0) {
- logger.info("Simulating week {}. Expected current balance {}.", week, expectedCurrentPrincipal);
+ logger.info("Simulating week {}. Expected current principal {}.", week, expectedCurrentPrincipal);
step6CalculateInterestAndCheckForLatenessForWeek(today, week);
- final BigDecimal nextRepaymentAmount = findNextRepaymentAmount(today, (week+1)*7);
- repayments.add(nextRepaymentAmount);
- step7PaybackPartialAmount(nextRepaymentAmount, today, (week+1)*7, BigDecimal.ZERO);
+ final BigDecimal nextRepaymentAmount = findNextRepaymentAmount(today.plusDays((week+1)*7));
+ final Payment payment = step7PaybackPartialAmount(nextRepaymentAmount, today.plusDays((week + 1) * 7), BigDecimal.ZERO);
+ payment.getBalanceAdjustments().remove(AccountDesignators.INTEREST_ACCRUAL);
+ payment.getBalanceAdjustments().remove(AccountDesignators.LATE_FEE_ACCRUAL); //Remove accrual accounts for comparison.
+ Assert.assertEquals(plannedPayments.get(week+1).getPayment(), payment);
week++;
}
- checkRepaymentVariance(repayments);
-
-
- step8Close();
- }
-
- private void checkRepaymentVariance(List<BigDecimal> repayments) {
- final Set<BigDecimal> setOfRepayments = new HashSet<>(repayments);
- Assert.assertTrue("Too many payment sizes. Payments are " + repayments,
- + setOfRepayments.size() <= 2); //There should be a standard payment size with only one slightly off.
- final BigDecimal minPayment = setOfRepayments.stream().min(BigDecimal::compareTo).orElseThrow(IllegalStateException::new);
- final BigDecimal maxPayment = setOfRepayments.stream().max(BigDecimal::compareTo).orElseThrow(IllegalStateException::new);
- final BigDecimal delta = maxPayment.subtract(minPayment).abs();
- Assert.assertTrue("Payments vary too much. Payments are " + repayments,
- delta.divide(maxPayment, BigDecimal.ROUND_HALF_EVEN).compareTo(BigDecimal.valueOf(0.01)) <= 0);
+ step8Close(DateConverter.fromIsoString(plannedPayments.get(plannedPayments.size()-1).getPayment().getDate()));
}
@Test
@@ -215,55 +218,58 @@
step1CreateProduct();
step2CreateCase();
- step3OpenCase();
- step4ApproveCase();
+ step3OpenCase(today);
+ step4ApproveCase(today);
+
+ final List<PlannedPayment> plannedPayments = individualLending.getPaymentScheduleForCaseStream(
+ product.getIdentifier(),
+ customerCase.getIdentifier(),
+ null)
+ .collect(Collectors.toList());
+
step5Disburse(
BigDecimal.valueOf(2_000_00, MINOR_CURRENCY_UNIT_DIGITS),
+ today,
UPPER_RANGE_DISBURSEMENT_FEE_ID, BigDecimal.valueOf(20_00, MINOR_CURRENCY_UNIT_DIGITS));
int week = 0;
final int weekOfLateRepayment = 3;
- final List<BigDecimal> repayments = new ArrayList<>();
while (expectedCurrentPrincipal.compareTo(BigDecimal.ZERO) > 0) {
logger.info("Simulating week {}. Expected current balance {}.", week, expectedCurrentPrincipal);
if (week == weekOfLateRepayment) {
- final BigDecimal lateFee = BigDecimal.valueOf(17_31, MINOR_CURRENCY_UNIT_DIGITS); //??? TODO: check the late fee value.
+ final BigDecimal lateFee = BigDecimal.valueOf(16_53, MINOR_CURRENCY_UNIT_DIGITS); //??? TODO: check the late fee value.
step6CalculateInterestAndCheckForLatenessForRangeOfDays(
today,
(week * 7) + 1,
(week + 1) * 7 + 2,
8,
lateFee);
- final BigDecimal nextRepaymentAmount = findNextRepaymentAmount(today, (week + 1) * 7 + 2);
- repayments.add(nextRepaymentAmount);
- step7PaybackPartialAmount(nextRepaymentAmount, today, (week + 1) * 7 + 2, lateFee);
+ final BigDecimal nextRepaymentAmount = findNextRepaymentAmount(today.plusDays((week + 1) * 7 + 2));
+ step7PaybackPartialAmount(nextRepaymentAmount, today.plusDays((week + 1) * 7 + 2), lateFee);
}
else {
step6CalculateInterestAndCheckForLatenessForWeek(today, week);
- final BigDecimal nextRepaymentAmount = findNextRepaymentAmount(today, (week + 1) * 7);
- repayments.add(nextRepaymentAmount);
- step7PaybackPartialAmount(nextRepaymentAmount, today, (week + 1) * 7, BigDecimal.ZERO);
+ final BigDecimal nextRepaymentAmount = findNextRepaymentAmount(today.plusDays((week + 1) * 7));
+ final Payment payment = step7PaybackPartialAmount(nextRepaymentAmount, today.plusDays((week + 1) * 7), BigDecimal.ZERO);
+ payment.getBalanceAdjustments().remove(AccountDesignators.INTEREST_ACCRUAL);
+ payment.getBalanceAdjustments().remove(AccountDesignators.CUSTOMER_LOAN_INTEREST); //Remove accrual accounting components for comparison, since planned payments are done without accrual accounting.
+ Assert.assertEquals(plannedPayments.get(week+1).getPayment(), payment);
}
week++;
}
- repayments.remove(3);
-
- checkRepaymentVariance(repayments);
-
- step8Close();
+ step8Close(DateConverter.fromIsoString(plannedPayments.get(plannedPayments.size()-1).getPayment().getDate()));
}
private BigDecimal findNextRepaymentAmount(
- final LocalDateTime referenceDate,
- final int dayNumber) {
+ final LocalDateTime forDateTime) {
final Payment nextPayment = portfolioManager.getCostComponentsForAction(
product.getIdentifier(),
customerCase.getIdentifier(),
Action.ACCEPT_PAYMENT.name(),
null,
null,
- DateConverter.toIsoString(referenceDate.plusDays(dayNumber)));
+ DateConverter.toIsoString(forDateTime));
final BigDecimal nextRepaymentAmount = nextPayment.getBalanceAdjustments()
.getOrDefault(AccountDesignators.ENTRY, BigDecimal.ZERO).negate();
Assert.assertTrue(nextRepaymentAmount.signum() != -1);
@@ -336,14 +342,15 @@
}
//Open the case and accept a processing fee.
- private void step3OpenCase() throws InterruptedException {
+ private void step3OpenCase(final LocalDateTime forDateTime) throws InterruptedException {
logger.info("step3OpenCase");
checkCostComponentForActionCorrect(
product.getIdentifier(),
customerCase.getIdentifier(),
Action.OPEN,
null,
- null);
+ null,
+ forDateTime);
checkStateTransfer(
product.getIdentifier(),
customerCase.getIdentifier(),
@@ -356,14 +363,14 @@
//Deny the case. Once this is done, no more actions are possible for the case.
- private void step4DenyCase() throws InterruptedException {
+ private void step4DenyCase(final LocalDateTime forDateTime) throws InterruptedException {
logger.info("step4DenyCase");
checkCostComponentForActionCorrect(
product.getIdentifier(),
customerCase.getIdentifier(),
Action.DENY,
null,
- null);
+ null, forDateTime);
checkStateTransfer(
product.getIdentifier(),
customerCase.getIdentifier(),
@@ -376,7 +383,8 @@
//Approve the case, accept a loan origination fee, and prepare to disburse the loan by earmarking the funds.
- private void step4ApproveCase() throws InterruptedException {
+ private void step4ApproveCase(final LocalDateTime forDateTime) throws InterruptedException
+ {
logger.info("step4ApproveCase");
markTaskExecuted(product, customerCase, taskDefinition);
@@ -386,7 +394,7 @@
customerCase.getIdentifier(),
Action.APPROVE,
null,
- null);
+ null, forDateTime);
checkStateTransfer(
product.getIdentifier(),
customerCase.getIdentifier(),
@@ -418,6 +426,7 @@
//Approve the case, accept a loan origination fee, and prepare to disburse the loan by earmarking the funds.
private void step5Disburse(
final BigDecimal amount,
+ final LocalDateTime forDateTime,
final String whichDisbursementFee,
final BigDecimal disbursementFeeAmount) throws InterruptedException {
logger.info("step5Disburse '{}'", amount);
@@ -427,6 +436,7 @@
Action.DISBURSE,
Sets.newLinkedHashSet(AccountDesignators.ENTRY, AccountDesignators.CUSTOMER_LOAN_GROUP),
amount,
+ forDateTime,
new CostComponent(whichDisbursementFee, disbursementFeeAmount),
new CostComponent(ChargeIdentifiers.LOAN_ORIGINATION_FEE_ID, LOAN_ORIGINATION_FEE_AMOUNT),
new CostComponent(ChargeIdentifiers.PROCESSING_FEE_ID, PROCESSING_FEE_AMOUNT),
@@ -459,7 +469,7 @@
expectedCurrentPrincipal = expectedCurrentPrincipal.add(amount);
interestAccrued = BigDecimal.ZERO;
- nonLateFees = nonLateFees.add(disbursementFeeAmount);
+ nonLateFees = nonLateFees.add(disbursementFeeAmount).add(PROCESSING_FEE_AMOUNT).add(LOAN_ORIGINATION_FEE_AMOUNT);
lateFees = BigDecimal.ZERO;
updateBalanceMock();
@@ -510,11 +520,11 @@
//Perform daily interest calculation.
private void step6CalculateInterestAccrualAndCheckForLateness(
- final LocalDateTime forTime,
+ final LocalDateTime forDateTime,
final BigDecimal calculatedLateFee) throws InterruptedException {
- logger.info("step6CalculateInterestAccrualAndCheckForLateness '{}'", forTime);
+ logger.info("step6CalculateInterestAccrualAndCheckForLateness '{}'", forDateTime);
final String beatIdentifier = "alignment0";
- final String midnightTimeStamp = DateConverter.toIsoString(forTime);
+ final String midnightTimeStamp = DateConverter.toIsoString(forDateTime);
final BigDecimal dailyInterestRate = Fixture.INTEREST_RATE
.divide(BigDecimal.valueOf(100), 8, BigDecimal.ROUND_HALF_EVEN)
@@ -535,6 +545,7 @@
Action.APPLY_INTEREST,
null,
null,
+ forDateTime,
new CostComponent(ChargeIdentifiers.INTEREST_ID, calculatedInterest));
if (calculatedLateFee.compareTo(BigDecimal.ZERO) != 0) {
@@ -544,6 +555,7 @@
Action.MARK_LATE,
null,
null,
+ forDateTime,
new CostComponent(ChargeIdentifiers.LATE_FEE_ID, calculatedLateFee));
}
final BeatPublish interestBeat = new BeatPublish(beatIdentifier, midnightTimeStamp);
@@ -603,20 +615,20 @@
logger.info("Completed step6CalculateInterestAccrualAndCheckForLateness");
}
- private void step7PaybackPartialAmount(
+ private Payment step7PaybackPartialAmount(
final BigDecimal amount,
- final LocalDateTime referenceDate,
- final int dayNumber,
+ final LocalDateTime forDateTime,
final BigDecimal lateFee) throws InterruptedException {
- logger.info("step7PaybackPartialAmount '{}'", amount);
+ logger.info("step7PaybackPartialAmount '{}' '{}'", amount, forDateTime);
final BigDecimal principal = amount.subtract(interestAccrued).subtract(lateFee.add(nonLateFees));
- checkCostComponentForActionCorrect(
+ final Payment payment = checkCostComponentForActionCorrect(
product.getIdentifier(),
customerCase.getIdentifier(),
Action.ACCEPT_PAYMENT,
new HashSet<>(Arrays.asList(AccountDesignators.ENTRY, AccountDesignators.CUSTOMER_LOAN_GROUP, AccountDesignators.LOAN_FUNDS_SOURCE)),
amount,
+ forDateTime,
new CostComponent(ChargeIdentifiers.REPAY_PRINCIPAL_ID, principal),
new CostComponent(ChargeIdentifiers.REPAY_INTEREST_ID, interestAccrued),
new CostComponent(ChargeIdentifiers.REPAY_FEES_ID, lateFee.add(nonLateFees)),
@@ -626,7 +638,7 @@
product.getIdentifier(),
customerCase.getIdentifier(),
Action.ACCEPT_PAYMENT,
- referenceDate.plusDays(dayNumber),
+ forDateTime,
Collections.singletonList(assignEntryToTeller()),
amount,
IndividualLoanEventConstants.ACCEPT_PAYMENT_INDIVIDUALLOAN_CASE,
@@ -670,9 +682,12 @@
updateBalanceMock();
logger.info("Completed step7PaybackPartialAmount");
+ return payment;
}
- private void step8Close() throws InterruptedException {
+ private void step8Close(
+ final LocalDateTime forDateTime) throws InterruptedException
+ {
logger.info("step8Close");
checkCostComponentForActionCorrect(
@@ -680,7 +695,8 @@
customerCase.getIdentifier(),
Action.CLOSE,
null,
- null);
+ null,
+ forDateTime);
checkStateTransfer(
product.getIdentifier(),
customerCase.getIdentifier(),
diff --git a/service/src/main/java/io/mifos/individuallending/IndividualLendingPatternFactory.java b/service/src/main/java/io/mifos/individuallending/IndividualLendingPatternFactory.java
index 028fec6..d4f9246 100644
--- a/service/src/main/java/io/mifos/individuallending/IndividualLendingPatternFactory.java
+++ b/service/src/main/java/io/mifos/individuallending/IndividualLendingPatternFactory.java
@@ -482,7 +482,7 @@
forDate,
runningBalances);
- return paymentBuilder.buildPayment(action, forAccountDesignators);
+ return paymentBuilder.buildPayment(action, forAccountDesignators, forDate);
}
public static void checkActionCanBeExecuted(final Case.State state, final Action action) {
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 f5110cb..eab188d 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
@@ -320,13 +320,10 @@
customerCase.setCurrentState(Case.State.ACTIVE.name());
caseRepository.save(customerCase);
}
- final String customerLoanPrinicipalAccountIdentifier = designatorToAccountIdentifierMapper.mapOrThrow(AccountDesignators.CUSTOMER_LOAN_PRINCIPAL);
- final String customerLoanInterestAccountIdentifier = designatorToAccountIdentifierMapper.mapOrThrow(AccountDesignators.CUSTOMER_LOAN_INTEREST);
- final String customerLoanFeesAccountIdentifier = designatorToAccountIdentifierMapper.mapOrThrow(AccountDesignators.CUSTOMER_LOAN_FEES);
- final BigDecimal currentBalance = accountingAdapter.getTotalOfCurrentAccountBalances(customerLoanPrinicipalAccountIdentifier, customerLoanInterestAccountIdentifier, customerLoanFeesAccountIdentifier);
+ final BigDecimal currentBalance = runningBalances.getBalance(AccountDesignators.CUSTOMER_LOAN_GROUP);
final BigDecimal newLoanPaymentSize = disbursePaymentBuilderService.getLoanPaymentSizeForSingleDisbursement(
- currentBalance.add(disbursalAmount),
+ currentBalance.add(paymentBuilder.getBalanceAdjustment(AccountDesignators.ENTRY)),
dataContextOfAction);
dataContextOfAction.getCaseParametersEntity().setPaymentSize(newLoanPaymentSize);
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 744e82e..4d6521b 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
@@ -165,7 +165,7 @@
minorCurrencyUnitDigits,
false);
- plannedPayments.add(paymentBuilder.accumulatePlannedPayment(balances));
+ plannedPayments.add(paymentBuilder.accumulatePlannedPayment(balances, repaymentPeriod.getEndDate()));
}
return plannedPayments;
}
diff --git a/service/src/main/java/io/mifos/individuallending/internal/service/costcomponent/AcceptPaymentBuilderService.java b/service/src/main/java/io/mifos/individuallending/internal/service/costcomponent/AcceptPaymentBuilderService.java
index 2b989e9..d10f130 100644
--- a/service/src/main/java/io/mifos/individuallending/internal/service/costcomponent/AcceptPaymentBuilderService.java
+++ b/service/src/main/java/io/mifos/individuallending/internal/service/costcomponent/AcceptPaymentBuilderService.java
@@ -88,24 +88,15 @@
final BigDecimal loanPaymentSize;
if (requestedLoanPaymentSize != null) {
- loanPaymentSize = requestedLoanPaymentSize;
+ loanPaymentSize = requestedLoanPaymentSize
+ .min(runningBalances.getBalance(AccountDesignators.CUSTOMER_LOAN_GROUP));
+ }
+ else if (scheduledAction.getActionPeriod() != null && scheduledAction.getActionPeriod().isLastPeriod()) {
+ loanPaymentSize = runningBalances.getBalance(AccountDesignators.CUSTOMER_LOAN_GROUP);
}
else {
- if (scheduledAction.getActionPeriod() != null && scheduledAction.getActionPeriod().isLastPeriod()) {
- loanPaymentSize = runningBalances.getBalance(AccountDesignators.CUSTOMER_LOAN_GROUP);
- }
- else {
- final BigDecimal paymentSizeBeforeOnTopCharges = runningBalances.getBalance(AccountDesignators.CUSTOMER_LOAN_GROUP)
- .min(dataContextOfAction.getCaseParametersEntity().getPaymentSize());
-
- @SuppressWarnings("UnnecessaryLocalVariable")
- final BigDecimal paymentSizeIncludingOnTopCharges = accruedCostComponents.entrySet().stream()
- .filter(entry -> entry.getKey().getChargeOnTop() != null && entry.getKey().getChargeOnTop())
- .map(entry -> entry.getValue().getAmount())
- .reduce(paymentSizeBeforeOnTopCharges, BigDecimal::add);
-
- loanPaymentSize = paymentSizeIncludingOnTopCharges;
- }
+ loanPaymentSize = dataContextOfAction.getCaseParametersEntity().getPaymentSize()
+ .min(runningBalances.getBalance(AccountDesignators.CUSTOMER_LOAN_GROUP));
}
diff --git a/service/src/main/java/io/mifos/individuallending/internal/service/costcomponent/PaymentBuilder.java b/service/src/main/java/io/mifos/individuallending/internal/service/costcomponent/PaymentBuilder.java
index 255c6cf..d85056a 100644
--- a/service/src/main/java/io/mifos/individuallending/internal/service/costcomponent/PaymentBuilder.java
+++ b/service/src/main/java/io/mifos/individuallending/internal/service/costcomponent/PaymentBuilder.java
@@ -16,6 +16,7 @@
package io.mifos.individuallending.internal.service.costcomponent;
import com.google.common.collect.Sets;
+import io.mifos.core.lang.DateConverter;
import io.mifos.individuallending.IndividualLendingPatternFactory;
import io.mifos.individuallending.api.v1.domain.caseinstance.PlannedPayment;
import io.mifos.individuallending.api.v1.domain.workflow.Action;
@@ -26,7 +27,9 @@
import io.mifos.portfolio.api.v1.domain.RequiredAccountAssignment;
import io.mifos.portfolio.service.internal.util.ChargeInstance;
+import javax.annotation.Nullable;
import java.math.BigDecimal;
+import java.time.LocalDate;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;
@@ -48,8 +51,17 @@
this.accrualAccounting = accrualAccounting;
}
- public Payment buildPayment(final Action action, final Set<String> forAccountDesignators) {
+ private Map<String, BigDecimal> copyBalanceAdjustments() {
+ return balanceAdjustments.entrySet().stream()
+ .filter(x -> x.getValue().compareTo(BigDecimal.ZERO) != 0)
+ .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
+ }
+ public Payment buildPayment(
+ final Action action,
+ final Set<String> forAccountDesignators,
+ final @Nullable LocalDate forDate)
+ {
if (!forAccountDesignators.isEmpty()) {
final Stream<Map.Entry<ChargeDefinition, CostComponent>> costComponentStream = stream()
.filter(costComponentEntry -> chargeReferencesAccountDesignators(
@@ -63,15 +75,17 @@
costComponentEntry.getValue().getAmount()))
.collect(Collectors.toList());
- return new Payment(costComponentList, balanceAdjustments);
+ final Payment ret = new Payment(costComponentList, copyBalanceAdjustments());
+ ret.setDate(forDate == null ? null : DateConverter.toIsoString(forDate.atStartOfDay()));
+ return ret;
}
else {
- return buildPayment();
+ return buildPayment(forDate);
}
}
- private Payment buildPayment() {
+ private Payment buildPayment(final @Nullable LocalDate forDate) {
final Stream<Map.Entry<ChargeDefinition, CostComponent>> costComponentStream = stream();
final List<CostComponent> costComponentList = costComponentStream
@@ -80,11 +94,15 @@
costComponentEntry.getValue().getAmount()))
.collect(Collectors.toList());
- return new Payment(costComponentList, balanceAdjustments);
+ final Payment ret = new Payment(costComponentList, copyBalanceAdjustments());
+ ret.setDate(forDate == null ? null : DateConverter.toIsoString(forDate.atStartOfDay()));
+ return ret;
}
- public PlannedPayment accumulatePlannedPayment(final SimulatedRunningBalances balances) {
- final Payment payment = buildPayment();
+ public PlannedPayment accumulatePlannedPayment(
+ final SimulatedRunningBalances balances,
+ final @Nullable LocalDate forDate) {
+ final Payment payment = buildPayment(forDate);
balanceAdjustments.forEach(balances::adjustBalance);
final Map<String, BigDecimal> balancesCopy = balances.snapshot();
@@ -101,7 +119,7 @@
.collect(Collectors.toList());
}
- BigDecimal getBalanceAdjustment(final String... accountDesignators) {
+ public BigDecimal getBalanceAdjustment(final String... accountDesignators) {
return Arrays.stream(accountDesignators)
.map(accountDesignator -> balanceAdjustments.getOrDefault(accountDesignator, BigDecimal.ZERO))
.reduce(BigDecimal.ZERO, BigDecimal::add);
@@ -154,7 +172,7 @@
return chargeDefinition.getAccrualAccountDesignator() != null;
}
- void addToBalance(
+ private void addToBalance(
final String accountDesignator,
final BigDecimal chargeAmount) {
final BigDecimal currentAdjustment = balanceAdjustments.getOrDefault(accountDesignator, BigDecimal.ZERO);
@@ -162,7 +180,7 @@
balanceAdjustments.put(accountDesignator, newAdjustment);
}
- void addToCostComponent(
+ private void addToCostComponent(
final ChargeDefinition chargeDefinition,
final BigDecimal amount) {
final CostComponent costComponent = costComponents
diff --git a/service/src/main/java/io/mifos/individuallending/internal/service/schedule/Period.java b/service/src/main/java/io/mifos/individuallending/internal/service/schedule/Period.java
index 0a0532b..db0e069 100644
--- a/service/src/main/java/io/mifos/individuallending/internal/service/schedule/Period.java
+++ b/service/src/main/java/io/mifos/individuallending/internal/service/schedule/Period.java
@@ -57,7 +57,7 @@
return beginDate;
}
- LocalDate getEndDate() {
+ public LocalDate getEndDate() {
return endDate;
}
diff --git a/service/src/test/java/io/mifos/individuallending/internal/service/costcomponent/AcceptPaymentBuilderServiceTest.java b/service/src/test/java/io/mifos/individuallending/internal/service/costcomponent/AcceptPaymentBuilderServiceTest.java
index 528270c..b5c8134 100644
--- a/service/src/test/java/io/mifos/individuallending/internal/service/costcomponent/AcceptPaymentBuilderServiceTest.java
+++ b/service/src/test/java/io/mifos/individuallending/internal/service/costcomponent/AcceptPaymentBuilderServiceTest.java
@@ -23,7 +23,7 @@
final PaymentBuilder paymentBuilder = PaymentBuilderServiceTestHarness.constructCallToPaymentBuilder(
AcceptPaymentBuilderService::new, testCase);
- final Payment payment = paymentBuilder.buildPayment(Action.ACCEPT_PAYMENT, Collections.emptySet());
+ final Payment payment = paymentBuilder.buildPayment(Action.ACCEPT_PAYMENT, Collections.emptySet(), testCase.forDate);
Assert.assertNotNull(payment);
final Map<String, CostComponent> mappedCostComponents = payment.getCostComponents().stream()
.collect(Collectors.toMap(CostComponent::getChargeIdentifier, x -> x));
diff --git a/service/src/test/java/io/mifos/individuallending/internal/service/costcomponent/ApplyInterestPaymentBuilderServiceTest.java b/service/src/test/java/io/mifos/individuallending/internal/service/costcomponent/ApplyInterestPaymentBuilderServiceTest.java
index 5b56481..cc2a384 100644
--- a/service/src/test/java/io/mifos/individuallending/internal/service/costcomponent/ApplyInterestPaymentBuilderServiceTest.java
+++ b/service/src/test/java/io/mifos/individuallending/internal/service/costcomponent/ApplyInterestPaymentBuilderServiceTest.java
@@ -22,7 +22,7 @@
final PaymentBuilder paymentBuilder = PaymentBuilderServiceTestHarness.constructCallToPaymentBuilder(
ApplyInterestPaymentBuilderService::new, testCase);
- final Payment payment = paymentBuilder.buildPayment(Action.APPLY_INTEREST, Collections.emptySet());
+ final Payment payment = paymentBuilder.buildPayment(Action.APPLY_INTEREST, Collections.emptySet(), testCase.forDate);
Assert.assertNotNull(payment);
final Map<String, CostComponent> mappedCostComponents = payment.getCostComponents().stream()
.collect(Collectors.toMap(CostComponent::getChargeIdentifier, x -> x));