Finishing implementation of late fee
* time of transaction set from command for testability
* late fee add "on top" in suggested payment size.
* late fee only charged once per payment period.
* simple test for late payment
diff --git a/api/src/main/java/io/mifos/portfolio/api/v1/domain/ChargeDefinition.java b/api/src/main/java/io/mifos/portfolio/api/v1/domain/ChargeDefinition.java
index 3890867..50ffa13 100644
--- a/api/src/main/java/io/mifos/portfolio/api/v1/domain/ChargeDefinition.java
+++ b/api/src/main/java/io/mifos/portfolio/api/v1/domain/ChargeDefinition.java
@@ -88,6 +88,8 @@
@ValidIdentifier(optional = true)
private String toSegment;
+ private Boolean chargeOnTop;
+
public ChargeDefinition() {
}
@@ -220,6 +222,14 @@
this.toSegment = toSegment;
}
+ public Boolean getChargeOnTop() {
+ return chargeOnTop;
+ }
+
+ public void setChargeOnTop(Boolean chargeOnTop) {
+ this.chargeOnTop = chargeOnTop;
+ }
+
@Override
public boolean equals(Object o) {
if (this == o) return true;
@@ -240,12 +250,13 @@
forCycleSizeUnit == that.forCycleSizeUnit &&
Objects.equals(forSegmentSet, that.forSegmentSet) &&
Objects.equals(fromSegment, that.fromSegment) &&
- Objects.equals(toSegment, that.toSegment);
+ Objects.equals(toSegment, that.toSegment) &&
+ Objects.equals(chargeOnTop, that.chargeOnTop);
}
@Override
public int hashCode() {
- return Objects.hash(identifier, name, description, accrueAction, chargeAction, amount, chargeMethod, proportionalTo, fromAccountDesignator, accrualAccountDesignator, toAccountDesignator, forCycleSizeUnit, readOnly, forSegmentSet, fromSegment, toSegment);
+ return Objects.hash(identifier, name, description, accrueAction, chargeAction, amount, chargeMethod, proportionalTo, fromAccountDesignator, accrualAccountDesignator, toAccountDesignator, forCycleSizeUnit, readOnly, forSegmentSet, fromSegment, toSegment, chargeOnTop);
}
@Override
@@ -267,6 +278,7 @@
", forSegmentSet='" + forSegmentSet + '\'' +
", fromSegment='" + fromSegment + '\'' +
", toSegment='" + toSegment + '\'' +
+ ", chargeOnTop='" + chargeOnTop + '\'' +
'}';
}
}
diff --git a/component-test/src/main/java/io/mifos/portfolio/AccountingFixture.java b/component-test/src/main/java/io/mifos/portfolio/AccountingFixture.java
index fa38d9b..00e74f7 100644
--- a/component-test/src/main/java/io/mifos/portfolio/AccountingFixture.java
+++ b/component-test/src/main/java/io/mifos/portfolio/AccountingFixture.java
@@ -31,7 +31,6 @@
import javax.validation.Validation;
import javax.validation.Validator;
import java.math.BigDecimal;
-import java.time.Clock;
import java.time.LocalDateTime;
import java.util.*;
import java.util.stream.Collectors;
@@ -81,11 +80,11 @@
this.account.setBalance(balance);
}
- synchronized void addAccountEntry(final String message, final double amount) {
+ synchronized void addAccountEntry(final String message, final String date, final double amount) {
final AccountEntry accountEntry = new AccountEntry();
accountEntry.setAmount(amount);
accountEntry.setMessage(message);
- accountEntry.setTransactionDate(DateConverter.toIsoString(LocalDateTime.now(Clock.systemUTC())));
+ accountEntry.setTransactionDate(date);
accountEntries.add(accountEntry);
}
@@ -391,10 +390,12 @@
journalEntry.getCreditors().forEach(creditor ->
accountMap.get(creditor.getAccountNumber()).addAccountEntry(
journalEntry.getMessage(),
+ journalEntry.getTransactionDate(),
Double.valueOf(creditor.getAmount())));
journalEntry.getDebtors().forEach(debtor ->
accountMap.get(debtor.getAccountNumber()).addAccountEntry(
journalEntry.getMessage(),
+ journalEntry.getTransactionDate(),
Double.valueOf(debtor.getAmount())));
return null;
}
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 d2c78d2..50724ed 100644
--- a/component-test/src/main/java/io/mifos/portfolio/TestAccountingInteractionInLoanWorkflow.java
+++ b/component-test/src/main/java/io/mifos/portfolio/TestAccountingInteractionInLoanWorkflow.java
@@ -100,7 +100,7 @@
BigDecimal.valueOf(2_000_00, MINOR_CURRENCY_UNIT_DIGITS),
UPPER_RANGE_DISBURSEMENT_FEE_ID, BigDecimal.valueOf(20_00, MINOR_CURRENCY_UNIT_DIGITS));
step6CalculateInterestAccrualAndCheckForLateness(midnightToday(), null);
- step7PaybackPartialAmount(expectedCurrentBalance, today, 0);
+ step7PaybackPartialAmount(expectedCurrentBalance, today, 0, BigDecimal.ZERO);
step8Close();
}
@@ -119,7 +119,7 @@
BigDecimal.valueOf(1_500_00, MINOR_CURRENCY_UNIT_DIGITS),
UPPER_RANGE_DISBURSEMENT_FEE_ID, BigDecimal.valueOf(15_00, MINOR_CURRENCY_UNIT_DIGITS));
step6CalculateInterestAccrualAndCheckForLateness(midnightToday(), null);
- step7PaybackPartialAmount(expectedCurrentBalance, today, 0);
+ step7PaybackPartialAmount(expectedCurrentBalance, today, 0, BigDecimal.ZERO);
step8Close();
}
@@ -139,8 +139,8 @@
step7PaybackPartialAmount(
repayment1.setScale(MINOR_CURRENCY_UNIT_DIGITS, BigDecimal.ROUND_HALF_EVEN),
today,
- 0);
- step7PaybackPartialAmount(expectedCurrentBalance, today, 0);
+ 0, BigDecimal.ZERO);
+ step7PaybackPartialAmount(expectedCurrentBalance, today, 0, BigDecimal.ZERO);
step8Close();
}
@@ -174,17 +174,67 @@
final List<BigDecimal> repayments = new ArrayList<>();
while (expectedCurrentBalance.compareTo(BigDecimal.ZERO) > 0) {
logger.info("Simulating week {}. Expected current balance {}.", week, expectedCurrentBalance);
- step6CalculateInterestAndCheckForLatenessForWeek(today, week, null);
+ step6CalculateInterestAndCheckForLatenessForWeek(today, week);
final BigDecimal nextRepaymentAmount = findNextRepaymentAmount(today, (week+1)*7);
repayments.add(nextRepaymentAmount);
- step7PaybackPartialAmount(nextRepaymentAmount, today, (week+1)*7);
+ step7PaybackPartialAmount(nextRepaymentAmount, today, (week+1)*7, BigDecimal.ZERO);
week++;
}
final BigDecimal minPayment = repayments.stream().min(BigDecimal::compareTo).orElseThrow(IllegalStateException::new);
final BigDecimal maxPayment = repayments.stream().max(BigDecimal::compareTo).orElseThrow(IllegalStateException::new);
final BigDecimal delta = maxPayment.subtract(minPayment).abs();
- Assert.assertTrue("minPayment is " + minPayment + ", maxPayment is " + maxPayment,
+ Assert.assertTrue("Payments are " + repayments,
+ delta.divide(maxPayment, BigDecimal.ROUND_HALF_EVEN).compareTo(BigDecimal.valueOf(0.01)) <= 0);
+
+
+ step8Close();
+ }
+
+ @Test
+ public void workflowWithOneLateRepayment() throws InterruptedException {
+ final LocalDateTime today = midnightToday();
+
+ step1CreateProduct();
+ step2CreateCase();
+ step3OpenCase();
+ step4ApproveCase();
+ step5Disburse(
+ BigDecimal.valueOf(2_000_00, MINOR_CURRENCY_UNIT_DIGITS),
+ 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 (expectedCurrentBalance.compareTo(BigDecimal.ZERO) > 0) {
+ logger.info("Simulating week {}. Expected current balance {}.", week, expectedCurrentBalance);
+ if (week == weekOfLateRepayment) {
+ final BigDecimal lateFee = BigDecimal.valueOf(14_49, MINOR_CURRENCY_UNIT_DIGITS);
+ 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);
+ }
+ else {
+ step6CalculateInterestAndCheckForLatenessForWeek(today, week);
+ final BigDecimal nextRepaymentAmount = findNextRepaymentAmount(today, (week + 1) * 7);
+ repayments.add(nextRepaymentAmount);
+ step7PaybackPartialAmount(nextRepaymentAmount, today, (week + 1) * 7, BigDecimal.ZERO);
+ }
+ week++;
+ }
+
+ repayments.remove(3);
+
+ final BigDecimal minPayment = repayments.stream().min(BigDecimal::compareTo).orElseThrow(IllegalStateException::new);
+ final BigDecimal maxPayment = repayments.stream().max(BigDecimal::compareTo).orElseThrow(IllegalStateException::new);
+ final BigDecimal delta = maxPayment.subtract(minPayment).abs();
+ Assert.assertTrue("Payments are " + repayments,
delta.divide(maxPayment, BigDecimal.ROUND_HALF_EVEN).compareTo(BigDecimal.valueOf(0.01)) <= 0);
@@ -401,14 +451,32 @@
private void step6CalculateInterestAndCheckForLatenessForWeek(
final LocalDateTime referenceDate,
- final int weekNumber,
+ final int weekNumber) throws InterruptedException {
+ step6CalculateInterestAndCheckForLatenessForRangeOfDays(
+ referenceDate,
+ (weekNumber * 7) + 1,
+ (weekNumber + 1) * 7,
+ -1,
+ null);
+ }
+
+ private void step6CalculateInterestAndCheckForLatenessForRangeOfDays(
+ final LocalDateTime referenceDate,
+ final int startInclusive,
+ final int endInclusive,
+ final int dayOfLateFee,
final BigDecimal calculatedLateFee) throws InterruptedException {
try {
- IntStream.rangeClosed((weekNumber*7)+1, (weekNumber+1)*7)
+ IntStream.rangeClosed(startInclusive, endInclusive)
.mapToObj(referenceDate::plusDays)
.forEach(day -> {
try {
- step6CalculateInterestAccrualAndCheckForLateness(day, calculatedLateFee);
+ if (day.equals(referenceDate.plusDays(dayOfLateFee))) {
+ step6CalculateInterestAccrualAndCheckForLateness(day, calculatedLateFee);
+ }
+ else {
+ step6CalculateInterestAccrualAndCheckForLateness(day, null);
+ }
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
@@ -489,12 +557,13 @@
private void step7PaybackPartialAmount(
final BigDecimal amount,
final LocalDateTime referenceDate,
- final int dayNumber) throws InterruptedException {
+ final int dayNumber,
+ final BigDecimal lateFee) throws InterruptedException {
logger.info("step7PaybackPartialAmount '{}'", amount);
AccountingFixture.mockBalance(customerLoanAccountIdentifier, expectedCurrentBalance.negate());
- final BigDecimal principal = amount.subtract(interestAccrued);
+ final BigDecimal principal = amount.subtract(interestAccrued).subtract(lateFee);
checkCostComponentForActionCorrect(
product.getIdentifier(),
@@ -504,7 +573,8 @@
amount,
new CostComponent(ChargeIdentifiers.REPAYMENT_ID, amount),
new CostComponent(ChargeIdentifiers.TRACK_RETURN_PRINCIPAL_ID, principal),
- new CostComponent(ChargeIdentifiers.INTEREST_ID, interestAccrued));
+ new CostComponent(ChargeIdentifiers.INTEREST_ID, interestAccrued),
+ new CostComponent(ChargeIdentifiers.LATE_FEE_ID, lateFee));
checkStateTransfer(
product.getIdentifier(),
customerCase.getIdentifier(),
@@ -523,16 +593,20 @@
debtors.add(new Debtor(AccountingFixture.LOAN_FUNDS_SOURCE_ACCOUNT_IDENTIFIER, principal.toPlainString()));
if (interestAccrued.compareTo(BigDecimal.ZERO) != 0)
debtors.add(new Debtor(AccountingFixture.LOAN_INTEREST_ACCRUAL_ACCOUNT_IDENTIFIER, interestAccrued.toPlainString()));
+ if (lateFee.compareTo(BigDecimal.ZERO) != 0)
+ debtors.add(new Debtor(AccountingFixture.LATE_FEE_ACCRUAL_ACCOUNT_IDENTIFIER, lateFee.toPlainString()));
final Set<Creditor> creditors = new HashSet<>();
creditors.add(new Creditor(AccountingFixture.TELLER_ONE_ACCOUNT_IDENTIFIER, amount.toPlainString()));
creditors.add(new Creditor(AccountingFixture.LOANS_PAYABLE_ACCOUNT_IDENTIFIER, principal.toPlainString()));
if (interestAccrued.compareTo(BigDecimal.ZERO) != 0)
creditors.add(new Creditor(AccountingFixture.CONSUMER_LOAN_INTEREST_ACCOUNT_IDENTIFIER, interestAccrued.toPlainString()));
+ if (lateFee.compareTo(BigDecimal.ZERO) != 0)
+ creditors.add(new Creditor(AccountingFixture.LATE_FEE_INCOME_ACCOUNT_IDENTIFIER, lateFee.toPlainString()));
AccountingFixture.verifyTransfer(ledgerManager, debtors, creditors, product.getIdentifier(), customerCase.getIdentifier(), Action.ACCEPT_PAYMENT);
- expectedCurrentBalance = expectedCurrentBalance.subtract(amount);
+ expectedCurrentBalance = expectedCurrentBalance.subtract(amount).add(lateFee);
interestAccrued = BigDecimal.ZERO.setScale(MINOR_CURRENCY_UNIT_DIGITS, RoundingMode.HALF_EVEN);
}
diff --git a/service/src/main/java/io/mifos/individuallending/IndividualLendingPatternFactory.java b/service/src/main/java/io/mifos/individuallending/IndividualLendingPatternFactory.java
index 8fd47db..31ea45d 100644
--- a/service/src/main/java/io/mifos/individuallending/IndividualLendingPatternFactory.java
+++ b/service/src/main/java/io/mifos/individuallending/IndividualLendingPatternFactory.java
@@ -166,7 +166,6 @@
trackPrincipalDisbursePayment.setAmount(BigDecimal.valueOf(100));
trackPrincipalDisbursePayment.setReadOnly(true);
- //TODO: Make payable at time of ACCEPT_PAYMENT but accrued at MARK_LATE
final ChargeDefinition lateFee = charge(
LATE_FEE_NAME,
Action.ACCEPT_PAYMENT,
@@ -176,6 +175,7 @@
lateFee.setAccrueAction(Action.MARK_LATE.name());
lateFee.setAccrualAccountDesignator(LATE_FEE_ACCRUAL);
lateFee.setProportionalTo(ChargeProportionalDesignator.REPAYMENT_DESIGNATOR.getValue());
+ lateFee.setChargeOnTop(true);
lateFee.setReadOnly(false);
//TODO: Make multiple write off allowance charges.
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 a9cc03a..cb63a4f 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
@@ -30,10 +30,7 @@
import io.mifos.individuallending.internal.command.ApplyInterestCommand;
import io.mifos.individuallending.internal.command.CheckLateCommand;
import io.mifos.individuallending.internal.command.MarkLateCommand;
-import io.mifos.individuallending.internal.service.DataContextOfAction;
-import io.mifos.individuallending.internal.service.DataContextService;
-import io.mifos.individuallending.internal.service.DesignatorToAccountIdentifierMapper;
-import io.mifos.individuallending.internal.service.ScheduledActionHelpers;
+import io.mifos.individuallending.internal.service.*;
import io.mifos.portfolio.api.v1.domain.Case;
import io.mifos.portfolio.service.config.PortfolioProperties;
import io.mifos.portfolio.service.internal.command.CreateBeatPublishCommand;
@@ -49,6 +46,9 @@
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.Collections;
+import java.util.List;
+import java.util.Optional;
+import java.util.stream.Collectors;
import java.util.stream.Stream;
/**
@@ -141,11 +141,13 @@
ServiceException.badRequest("No last disbursal date for ''{0}.{1}'' could be determined. " +
"Therefore it cannot be checked for lateness.", productIdentifier, caseIdentifier));
- final long repaymentPeriodsBetweenBeginningAndToday = ScheduledActionHelpers.generateRepaymentPeriods(
+ final List<Period> repaymentPeriods = ScheduledActionHelpers.generateRepaymentPeriods(
dateOfMostRecentDisbursement.toLocalDate(),
forTime.toLocalDate(),
dataContextOfAction.getCaseParameters())
- .count() - 1;
+ .collect(Collectors.toList());
+
+ final long repaymentPeriodsBetweenBeginningAndToday = repaymentPeriods.size() - 1;
final BigDecimal expectedPaymentSum = dataContextOfAction
.getCaseParametersEntity()
@@ -162,9 +164,21 @@
dateOfMostRecentDisbursement.toLocalDate(),
dataContextOfAction.getMessageForCharge(Action.MARK_LATE));
- if (paymentsSum.compareTo(expectedPaymentSum.add(lateFeesAccrued)) < 0)
- commandBus.dispatch(new MarkLateCommand(productIdentifier, caseIdentifier, command.getForTime()));
+ if (paymentsSum.compareTo(expectedPaymentSum.add(lateFeesAccrued)) < 0) {
+ final Optional<LocalDateTime> dateOfMostRecentLateFee = accountingAdapter.getDateOfMostRecentEntryContainingMessage(customerLoanAccountIdentifier, dataContextOfAction.getMessageForCharge(Action.MARK_LATE));
+ if (!dateOfMostRecentLateFee.isPresent() ||
+ mostRecentLateFeeIsBeforeMostRecentRepaymentPeriod(repaymentPeriods, dateOfMostRecentLateFee.get())) {
+ commandBus.dispatch(new MarkLateCommand(productIdentifier, caseIdentifier, command.getForTime()));
+ }
+ }
return new IndividualLoanCommandEvent(productIdentifier, caseIdentifier, command.getForTime());
}
+
+ private boolean mostRecentLateFeeIsBeforeMostRecentRepaymentPeriod(
+ final List<Period> repaymentPeriods,
+ final LocalDateTime dateOfMostRecentLateFee) {
+ return repaymentPeriods.stream()
+ .anyMatch(x -> x.getBeginDate().isAfter(dateOfMostRecentLateFee.toLocalDate()));
+ }
}
\ 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 dd0822d..d6e6aed 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
@@ -118,6 +118,7 @@
accountingAdapter.bookCharges(charges,
command.getCommand().getNote(),
+ command.getCommand().getCreatedOn(),
dataContextOfAction.getMessageForCharge(Action.OPEN),
Action.OPEN.getTransactionType());
//Only move to new state if book charges command was accepted.
@@ -210,6 +211,7 @@
accountingAdapter.bookCharges(charges,
command.getCommand().getNote(),
+ command.getCommand().getCreatedOn(),
dataContextOfAction.getMessageForCharge(Action.APPROVE),
Action.APPROVE.getTransactionType());
@@ -254,6 +256,7 @@
accountingAdapter.bookCharges(charges,
command.getCommand().getNote(),
+ command.getCommand().getCreatedOn(),
dataContextOfAction.getMessageForCharge(Action.DISBURSE),
Action.DISBURSE.getTransactionType());
//Only move to new state if book charges command was accepted.
@@ -311,7 +314,8 @@
.collect(Collectors.toList());
accountingAdapter.bookCharges(charges,
- "",
+ "Applied interest on " + command.getForTime(),
+ command.getForTime(),
dataContextOfAction.getMessageForCharge(Action.APPLY_INTEREST),
Action.APPLY_INTEREST.getTransactionType());
@@ -358,6 +362,7 @@
accountingAdapter.bookCharges(charges,
command.getCommand().getNote(),
+ command.getCommand().getCreatedOn(),
dataContextOfAction.getMessageForCharge(Action.ACCEPT_PAYMENT),
Action.ACCEPT_PAYMENT.getTransactionType());
@@ -401,6 +406,7 @@
accountingAdapter.bookCharges(charges,
"Marked late on " + command.getForTime(),
+ command.getForTime(),
dataContextOfAction.getMessageForCharge(Action.MARK_LATE),
Action.MARK_LATE.getTransactionType());
@@ -460,6 +466,7 @@
accountingAdapter.bookCharges(charges,
command.getCommand().getNote(),
+ command.getCommand().getCreatedOn(),
dataContextOfAction.getMessageForCharge(Action.DISBURSE),
Action.DISBURSE.getTransactionType());
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 551c39e..120378b 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
@@ -267,20 +267,6 @@
dataContextOfAction.getCaseParameters()
);
- final BigDecimal loanPaymentSize;
-
- if (requestedLoanPaymentSize != null) {
- loanPaymentSize = requestedLoanPaymentSize;
- }
- else {
- if (scheduledAction.actionPeriod != null && scheduledAction.actionPeriod.isLastPeriod()) {
- loanPaymentSize = currentBalance;
- }
- else {
- loanPaymentSize = currentBalance.min(dataContextOfAction.getCaseParametersEntity().getPaymentSize());
- }
- }
-
final List<ScheduledCharge> scheduledChargesForThisAction = scheduledChargesService.getScheduledCharges(
productIdentifier,
Collections.singletonList(scheduledAction));
@@ -295,6 +281,28 @@
chargeDefinition -> getAccruedCostComponentToApply(dataContextOfAction, designatorToAccountIdentifierMapper, startOfTerm, chargeDefinition)));
+ final BigDecimal loanPaymentSize;
+
+ if (requestedLoanPaymentSize != null) {
+ loanPaymentSize = requestedLoanPaymentSize;
+ }
+ else {
+ if (scheduledAction.actionPeriod != null && scheduledAction.actionPeriod.isLastPeriod()) {
+ loanPaymentSize = currentBalance;
+ }
+ else {
+ final BigDecimal paymentSizeBeforeOnTopCharges = currentBalance.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;
+ }
+ }
+
return getCostComponentsForScheduledCharges(
accruedCostComponents,
diff --git a/service/src/main/java/io/mifos/individuallending/internal/service/Period.java b/service/src/main/java/io/mifos/individuallending/internal/service/Period.java
index 625f6cf..0ca16cd 100644
--- a/service/src/main/java/io/mifos/individuallending/internal/service/Period.java
+++ b/service/src/main/java/io/mifos/individuallending/internal/service/Period.java
@@ -26,7 +26,7 @@
/**
* @author Myrle Krantz
*/
-class Period implements Comparable<Period> {
+public class Period implements Comparable<Period> {
final private LocalDate beginDate;
final private LocalDate endDate;
final private boolean lastPeriod;
@@ -55,7 +55,7 @@
this.lastPeriod = false;
}
- LocalDate getBeginDate() {
+ public LocalDate getBeginDate() {
return beginDate;
}
diff --git a/service/src/main/java/io/mifos/portfolio/service/internal/mapper/ChargeDefinitionMapper.java b/service/src/main/java/io/mifos/portfolio/service/internal/mapper/ChargeDefinitionMapper.java
index f99d272..d09fab1 100644
--- a/service/src/main/java/io/mifos/portfolio/service/internal/mapper/ChargeDefinitionMapper.java
+++ b/service/src/main/java/io/mifos/portfolio/service/internal/mapper/ChargeDefinitionMapper.java
@@ -57,6 +57,7 @@
ret.setFromSegment(fromSegment.getSegmentIdentifier());
ret.setToSegment(toSegment.getSegmentIdentifier());
}
+ ret.setOnTop(chargeDefinition.getChargeOnTop());
return ret;
}
@@ -82,6 +83,7 @@
ret.setFromSegment(from.getFromSegment());
ret.setToSegment(from.getToSegment());
}
+ ret.setChargeOnTop(from.getOnTop());
return ret;
}
diff --git a/service/src/main/java/io/mifos/portfolio/service/internal/repository/ChargeDefinitionEntity.java b/service/src/main/java/io/mifos/portfolio/service/internal/repository/ChargeDefinitionEntity.java
index 11e154e..4c9bb15 100644
--- a/service/src/main/java/io/mifos/portfolio/service/internal/repository/ChargeDefinitionEntity.java
+++ b/service/src/main/java/io/mifos/portfolio/service/internal/repository/ChargeDefinitionEntity.java
@@ -88,6 +88,9 @@
@Column(name = "to_segment")
private String toSegment;
+ @Column(name = "on_top")
+ private Boolean onTop;
+
public ChargeDefinitionEntity() {
}
@@ -235,6 +238,14 @@
this.toSegment = toSegment;
}
+ public Boolean getOnTop() {
+ return onTop;
+ }
+
+ public void setOnTop(Boolean onTop) {
+ this.onTop = onTop;
+ }
+
@Override
public boolean equals(Object o) {
if (this == o) return true;
diff --git a/service/src/main/java/io/mifos/portfolio/service/internal/util/AccountingAdapter.java b/service/src/main/java/io/mifos/portfolio/service/internal/util/AccountingAdapter.java
index 910cb8f..4396b00 100644
--- a/service/src/main/java/io/mifos/portfolio/service/internal/util/AccountingAdapter.java
+++ b/service/src/main/java/io/mifos/portfolio/service/internal/util/AccountingAdapter.java
@@ -63,6 +63,7 @@
public void bookCharges(final List<ChargeInstance> costComponents,
final String note,
+ final String transactionDate,
final String message,
final String transactionType) {
final Set<Creditor> creditors = costComponents.stream()
@@ -80,7 +81,7 @@
journalEntry.setCreditors(creditors);
journalEntry.setDebtors(debtors);
journalEntry.setClerk(UserContextHolder.checkedGetUser());
- journalEntry.setTransactionDate(DateConverter.toIsoString(LocalDateTime.now()));
+ journalEntry.setTransactionDate(transactionDate);
journalEntry.setMessage(message);
journalEntry.setTransactionType(transactionType);
journalEntry.setNote(note);
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
index cb3bc59..7d5c8c7 100644
--- 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
@@ -14,4 +14,6 @@
-- 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
+ALTER TABLE bastet_il_cases ADD COLUMN payment_size DECIMAL(19,4) NULL DEFAULT NULL;
+
+ALTER TABLE bastet_p_chrg_defs ADD COLUMN on_top BOOLEAN NULL DEFAULT NULL;
\ No newline at end of file