FINERACT-2281: fix unrecognized interest handling when first period is unpaid
This commit is contained in:
+110
-41
@@ -53,6 +53,7 @@ import org.apache.fineract.portfolio.loanproduct.calc.data.ProgressiveLoanIntere
|
||||
import org.apache.fineract.portfolio.loanproduct.calc.data.RepaymentPeriod;
|
||||
import org.apache.fineract.portfolio.loanproduct.domain.LoanProductMinimumRepaymentScheduleRelatedDetail;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
@@ -182,16 +183,25 @@ public final class ProgressiveEMICalculator implements EMICalculator {
|
||||
scheduleModel.zero()).ifPresent(repaymentPeriod -> {
|
||||
calculateRateFactorForRepaymentPeriod(repaymentPeriod, scheduleModel);
|
||||
calculateOutstandingBalance(scheduleModel);
|
||||
calculateLastUnpaidRepaymentPeriodEMI(scheduleModel);
|
||||
calculateLastUnpaidRepaymentPeriodEMI(scheduleModel, balanceCorrectionDate);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void payInterest(ProgressiveLoanInterestScheduleModel scheduleModel, LocalDate repaymentPeriodDueDate, LocalDate transactionDate,
|
||||
Money interestAmount) {
|
||||
findRepaymentPeriod(scheduleModel, repaymentPeriodDueDate).ifPresent(rp -> rp.addPaidInterestAmount(interestAmount));
|
||||
Optional<RepaymentPeriod> repaymentPeriod = findRepaymentPeriod(scheduleModel, repaymentPeriodDueDate);
|
||||
|
||||
Optional<RepaymentPeriod> latestNotLastOpenRepaymentPeriodBeforeDate = getLatestNotLastOpenRepaymentPeriodBeforeDate(scheduleModel,
|
||||
transactionDate);
|
||||
if (latestNotLastOpenRepaymentPeriodBeforeDate.isPresent() && repaymentPeriod.equals(latestNotLastOpenRepaymentPeriodBeforeDate)) {
|
||||
calculateUnrecognizedInterestTillDateOnScheduleModelCopyAndDefer(scheduleModel,
|
||||
latestNotLastOpenRepaymentPeriodBeforeDate.get(), transactionDate);
|
||||
}
|
||||
|
||||
repaymentPeriod.ifPresent(rp -> rp.addPaidInterestAmount(interestAmount));
|
||||
calculateOutstandingBalance(scheduleModel);
|
||||
calculateLastUnpaidRepaymentPeriodEMI(scheduleModel);
|
||||
calculateLastUnpaidRepaymentPeriodEMI(scheduleModel, transactionDate);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -211,17 +221,38 @@ public final class ProgressiveEMICalculator implements EMICalculator {
|
||||
repaymentPeriod.ifPresent(rp -> {
|
||||
// If any period total paid > calculated EMI, then set EMI to total paid -> effectively it is marked as
|
||||
// fully paid
|
||||
if (transactionDateIsBefore && rp.getTotalPaidAmount().isGreaterThan(rp.getEmiPlusCreditedAmounts())) {
|
||||
if (transactionDateIsBefore
|
||||
&& rp.getTotalPaidAmount().isGreaterThan(rp.getEmiPlusCreditedAmountsPlusFutureUnrecognizedInterest())) {
|
||||
rp.setEmi(rp.getTotalPaidAmount().minus(rp.getTotalCreditedAmount()));
|
||||
} else if (transactionDateIsBefore
|
||||
&& rp.getTotalPaidAmount().isEqualTo(rp.getOriginalEmi().add(rp.getTotalCreditedAmount()))) {
|
||||
rp.setEmi(rp.getTotalPaidAmount().minus(rp.getTotalCreditedAmount()));
|
||||
}
|
||||
calculateLastUnpaidRepaymentPeriodEMI(scheduleModel);
|
||||
calculateLastUnpaidRepaymentPeriodEMI(scheduleModel, balanceCorrectionDate);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private Optional<RepaymentPeriod> getLatestNotLastOpenRepaymentPeriodBeforeDate(ProgressiveLoanInterestScheduleModel scheduleModel,
|
||||
LocalDate transactionDate) {
|
||||
List<RepaymentPeriod> unpaidRepaymentPeriods = scheduleModel.repaymentPeriods() //
|
||||
.stream() //
|
||||
.filter(rp -> !rp.isFullyPaid()) //
|
||||
.toList(); //
|
||||
|
||||
if (CollectionUtils.isEmpty(unpaidRepaymentPeriods)
|
||||
|| unpaidRepaymentPeriods.getLast().equals(scheduleModel.repaymentPeriods().getLast())) {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
RepaymentPeriod latestNotLastOpenRepaymentPeriod = unpaidRepaymentPeriods.getLast();
|
||||
if (DateUtils.isBefore(transactionDate, latestNotLastOpenRepaymentPeriod.getDueDate())) {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
return Optional.of(latestNotLastOpenRepaymentPeriod);
|
||||
}
|
||||
|
||||
private void addCreditedAmountsToInterestPeriod(ProgressiveLoanInterestScheduleModel scheduleModel, LocalDate transactionDate,
|
||||
Money creditedPrincipalAmount, Money creditedInterestAmount) {
|
||||
scheduleModel.repaymentPeriods().stream().filter(checkRepaymentPeriodIsInCreditRange(scheduleModel, transactionDate)).findFirst()
|
||||
@@ -260,7 +291,7 @@ public final class ProgressiveEMICalculator implements EMICalculator {
|
||||
addCreditedAmountsToInterestPeriod(scheduleModel, transactionDate, creditedPrincipalAmount, creditedInterestAmount);
|
||||
calculateRateFactorForRepaymentPeriod(repaymentPeriod, scheduleModel);
|
||||
calculateOutstandingBalance(scheduleModel);
|
||||
calculateLastUnpaidRepaymentPeriodEMI(scheduleModel);
|
||||
calculateLastUnpaidRepaymentPeriodEMI(scheduleModel, transactionDate);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -334,22 +365,9 @@ public final class ProgressiveEMICalculator implements EMICalculator {
|
||||
public OutstandingDetails getOutstandingAmountsTillDate(ProgressiveLoanInterestScheduleModel scheduleModel, LocalDate targetDate) {
|
||||
MathContext mc = scheduleModel.mc();
|
||||
ProgressiveLoanInterestScheduleModel scheduleModelCopy = scheduleModel.deepCopy(mc);
|
||||
// TODO use findInterestPeriod
|
||||
scheduleModelCopy.repaymentPeriods().stream()//
|
||||
.filter(rp -> targetDate.isAfter(rp.getFromDate()) && !targetDate.isAfter(rp.getDueDate())).findFirst()//
|
||||
.flatMap(rp -> rp.getInterestPeriods().stream()//
|
||||
.filter(ip -> targetDate.isAfter(ip.getFromDate()) && !targetDate.isAfter(ip.getDueDate())) //
|
||||
.reduce((one, two) -> two))
|
||||
.ifPresent(ip -> ip.setDueDate(targetDate)); //
|
||||
|
||||
calculateRateFactorForPeriods(scheduleModelCopy.repaymentPeriods(), scheduleModelCopy);
|
||||
scheduleModelCopy.repaymentPeriods()
|
||||
.forEach(rp -> rp.getInterestPeriods().stream().filter(ip -> targetDate.isBefore(ip.getDueDate())).forEach(ip -> {
|
||||
ip.setRateFactor(BigDecimal.ZERO);
|
||||
ip.setRateFactorTillPeriodDueDate(BigDecimal.ZERO);
|
||||
}));
|
||||
calculateRateFactorForScheduleTillDateInclusive(scheduleModelCopy, targetDate);
|
||||
calculateOutstandingBalance(scheduleModelCopy);
|
||||
calculateLastUnpaidRepaymentPeriodEMI(scheduleModelCopy);
|
||||
calculateLastUnpaidRepaymentPeriodEMI(scheduleModelCopy, targetDate);
|
||||
|
||||
Money totalOutstandingPrincipal = MathUtil
|
||||
.negativeToZero(scheduleModelCopy.getTotalDuePrincipal().minus(scheduleModelCopy.getTotalPaidPrincipal()));
|
||||
@@ -421,7 +439,7 @@ public final class ProgressiveEMICalculator implements EMICalculator {
|
||||
});
|
||||
calculateRateFactorForPeriods(scheduleModelCopy.repaymentPeriods(), scheduleModelCopy);
|
||||
calculateOutstandingBalance(scheduleModelCopy);
|
||||
calculateLastUnpaidRepaymentPeriodEMI(scheduleModelCopy);
|
||||
calculateLastUnpaidRepaymentPeriodEMI(scheduleModelCopy, targetDate);
|
||||
}
|
||||
return scheduleModelCopy;
|
||||
}
|
||||
@@ -443,37 +461,63 @@ public final class ProgressiveEMICalculator implements EMICalculator {
|
||||
calculateEMIOnNewModelAndMerge(relatedRepaymentPeriods, scheduleModel, operation);
|
||||
}
|
||||
calculateOutstandingBalance(scheduleModel);
|
||||
calculateLastUnpaidRepaymentPeriodEMI(scheduleModel);
|
||||
calculateLastUnpaidRepaymentPeriodEMI(scheduleModel, calculateFromRepaymentPeriodDueDate);
|
||||
if (onlyOnActualModelShouldApply && (scheduleModel.loanTermVariations() == null
|
||||
|| scheduleModel.loanTermVariations().get(LoanTermVariationType.DUE_DATE) == null)) {
|
||||
checkAndAdjustEmiIfNeededOnRelatedRepaymentPeriods(scheduleModel, relatedRepaymentPeriods);
|
||||
}
|
||||
}
|
||||
|
||||
private void calculateLastUnpaidRepaymentPeriodEMI(ProgressiveLoanInterestScheduleModel scheduleModel) {
|
||||
MathContext mc = scheduleModel.mc();
|
||||
Money totalDueInterest = scheduleModel.repaymentPeriods().stream().map(RepaymentPeriod::getDueInterest).reduce(scheduleModel.zero(),
|
||||
(m1, m2) -> m1.plus(m2, mc)); // 1.46
|
||||
Money totalEMI = scheduleModel.repaymentPeriods().stream().map(RepaymentPeriod::getEmiPlusCreditedAmounts)
|
||||
.reduce(scheduleModel.zero(), (m1, m2) -> m1.plus(m2, mc)); // 101.48
|
||||
Money totalDisbursedAmount = scheduleModel.repaymentPeriods().stream()
|
||||
.flatMap(rp -> rp.getInterestPeriods().stream().map(InterestPeriod::getDisbursementAmount))
|
||||
.reduce(scheduleModel.zero(), (m1, m2) -> m1.plus(m2, mc)); // 100
|
||||
Money totalCapitalizedIncome = scheduleModel.repaymentPeriods().stream()
|
||||
.flatMap(rp -> rp.getInterestPeriods().stream().map(InterestPeriod::getCapitalizedIncomePrincipal))
|
||||
.reduce(scheduleModel.zero(), (m1, m2) -> m1.plus(m2, mc)); // 100
|
||||
|
||||
Money diff = totalDisbursedAmount.plus(totalCapitalizedIncome, mc).plus(scheduleModel.getTotalCreditedPrincipal(), mc)
|
||||
.plus(totalDueInterest, mc).minus(totalEMI, mc);
|
||||
private void calculateLastUnpaidRepaymentPeriodEMI(ProgressiveLoanInterestScheduleModel scheduleModel, LocalDate tillDate) {
|
||||
Optional<RepaymentPeriod> findLastUnpaidRepaymentPeriod = scheduleModel.repaymentPeriods().stream().filter(rp -> !rp.isFullyPaid())
|
||||
.reduce((first, second) -> second);
|
||||
|
||||
findLastUnpaidRepaymentPeriod.ifPresent(repaymentPeriod -> {
|
||||
repaymentPeriod.setFutureUnrecognizedInterest(scheduleModel.zero());
|
||||
scheduleModel.repaymentPeriods().forEach(rp -> {
|
||||
rp.setInterestMoved(false);
|
||||
});
|
||||
|
||||
MathContext mc = scheduleModel.mc();
|
||||
Money totalDueInterest = scheduleModel.repaymentPeriods().stream().map(RepaymentPeriod::getDueInterest)
|
||||
.reduce(scheduleModel.zero(), (m1, m2) -> m1.plus(m2, mc)); // 1.46
|
||||
Money totalEMI = scheduleModel.repaymentPeriods().stream()
|
||||
.map(RepaymentPeriod::getEmiPlusCreditedAmountsPlusFutureUnrecognizedInterest)
|
||||
.reduce(scheduleModel.zero(), (m1, m2) -> m1.plus(m2, mc)); // 101.48
|
||||
Money totalDisbursedAmount = scheduleModel.repaymentPeriods().stream()
|
||||
.flatMap(rp -> rp.getInterestPeriods().stream().map(InterestPeriod::getDisbursementAmount))
|
||||
.reduce(scheduleModel.zero(), (m1, m2) -> m1.plus(m2, mc)); // 100
|
||||
Money totalCapitalizedIncome = scheduleModel.repaymentPeriods().stream()
|
||||
.flatMap(rp -> rp.getInterestPeriods().stream().map(InterestPeriod::getCapitalizedIncomePrincipal))
|
||||
.reduce(scheduleModel.zero(), (m1, m2) -> m1.plus(m2, mc)); // 100
|
||||
|
||||
Money diff = totalDisbursedAmount.plus(totalCapitalizedIncome, mc).plus(scheduleModel.getTotalCreditedPrincipal(), mc)
|
||||
.plus(totalDueInterest, mc).minus(totalEMI, mc);
|
||||
|
||||
repaymentPeriod.setEmi(repaymentPeriod.getEmi().add(diff, mc));
|
||||
if (repaymentPeriod.getEmi()
|
||||
.isLessThan(repaymentPeriod.getTotalPaidAmount().minus(repaymentPeriod.getTotalCreditedAmount(), mc))) {
|
||||
repaymentPeriod.setEmi(repaymentPeriod.getTotalPaidAmount().minus(repaymentPeriod.getTotalCreditedAmount(), mc));
|
||||
calculateLastUnpaidRepaymentPeriodEMI(scheduleModel);
|
||||
calculateLastUnpaidRepaymentPeriodEMI(scheduleModel, tillDate);
|
||||
}
|
||||
|
||||
calculateUnrecognizedInterestTillDateOnScheduleModelCopyAndDefer(scheduleModel, repaymentPeriod, tillDate);
|
||||
});
|
||||
}
|
||||
|
||||
private void calculateUnrecognizedInterestTillDateOnScheduleModelCopyAndDefer(ProgressiveLoanInterestScheduleModel scheduleModel,
|
||||
RepaymentPeriod repaymentPeriod, LocalDate tillDate) {
|
||||
MathContext mc = scheduleModel.mc();
|
||||
ProgressiveLoanInterestScheduleModel scheduleModelCopy = scheduleModel.deepCopy(mc);
|
||||
calculateRateFactorForScheduleTillDateInclusive(scheduleModelCopy, tillDate);
|
||||
Optional<RepaymentPeriod> futureUnrecognizedInterestPeriod = getPeriodWithUnrecognizedInterest(repaymentPeriod, scheduleModelCopy);
|
||||
|
||||
futureUnrecognizedInterestPeriod.ifPresent(period -> {
|
||||
repaymentPeriod.setFutureUnrecognizedInterest(period.getUnrecognizedInterest());
|
||||
scheduleModel.repaymentPeriods().stream().filter(rp -> rp.getDueDate().isAfter(repaymentPeriod.getDueDate())) //
|
||||
.forEach(rp -> {
|
||||
rp.setInterestMoved(true);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -509,7 +553,7 @@ public final class ProgressiveEMICalculator implements EMICalculator {
|
||||
}
|
||||
});
|
||||
calculateOutstandingBalance(newScheduleModel);
|
||||
calculateLastUnpaidRepaymentPeriodEMI(newScheduleModel);
|
||||
calculateLastUnpaidRepaymentPeriodEMI(newScheduleModel, relatedPeriodsFirstDueDate);
|
||||
if (!getEmiAdjustment(newScheduleModel.repaymentPeriods()).hasLessEmiDifference(emiAdjustment)) {
|
||||
break;
|
||||
}
|
||||
@@ -861,6 +905,31 @@ public final class ProgressiveEMICalculator implements EMICalculator {
|
||||
return new EmiAdjustment(repaymentPeriods.get(0).getEmi(), repaymentPeriods.get(0).getEmi().copy(0.0), repaymentPeriods, 0);
|
||||
}
|
||||
|
||||
private void calculateRateFactorForScheduleTillDateInclusive(ProgressiveLoanInterestScheduleModel scheduleModelCopy,
|
||||
LocalDate targetDate) {
|
||||
scheduleModelCopy.findRepaymentPeriod(targetDate).flatMap(rp -> rp.findInterestPeriod(targetDate))
|
||||
.ifPresent(ip -> ip.setDueDate(targetDate));
|
||||
|
||||
calculateRateFactorForPeriods(scheduleModelCopy.repaymentPeriods(), scheduleModelCopy);
|
||||
|
||||
scheduleModelCopy.repaymentPeriods()
|
||||
.forEach(rp -> rp.getInterestPeriods().stream().filter(ip -> targetDate.isBefore(ip.getDueDate())).forEach(ip -> {
|
||||
ip.setRateFactor(BigDecimal.ZERO);
|
||||
ip.setRateFactorTillPeriodDueDate(BigDecimal.ZERO);
|
||||
}));
|
||||
}
|
||||
|
||||
private Optional<RepaymentPeriod> getPeriodWithUnrecognizedInterest(RepaymentPeriod lastUnpaidRepaymentPeriod,
|
||||
ProgressiveLoanInterestScheduleModel scheduleModelCopy) {
|
||||
for (RepaymentPeriod period : scheduleModelCopy.repaymentPeriods().reversed()) {
|
||||
if (MathUtil.isGreaterThanZero(period.getUnrecognizedInterest())
|
||||
&& period.getDueDate().isAfter(lastUnpaidRepaymentPeriod.getDueDate())) {
|
||||
return Optional.of(period);
|
||||
}
|
||||
}
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate Rate Factor Product from rate factors
|
||||
*/
|
||||
@@ -1082,7 +1151,7 @@ public final class ProgressiveEMICalculator implements EMICalculator {
|
||||
final List<RepaymentPeriod> relatedRepaymentPeriods = scheduleModel.getRelatedRepaymentPeriods(startDate);
|
||||
calculateRateFactorForPeriods(relatedRepaymentPeriods, scheduleModel);
|
||||
calculateOutstandingBalance(scheduleModel);
|
||||
calculateLastUnpaidRepaymentPeriodEMI(scheduleModel);
|
||||
calculateLastUnpaidRepaymentPeriodEMI(scheduleModel, startDate);
|
||||
}
|
||||
|
||||
private long getUncountablePeriods(final List<RepaymentPeriod> relatedRepaymentPeriods, final Money originalEmi) {
|
||||
|
||||
+1
-1
@@ -145,7 +145,7 @@ public class InterestPeriod implements Comparable<InterestPeriod> {
|
||||
.multiply(getRateFactorTillPeriodDueDate(), mc) //
|
||||
.divide(BigDecimal.valueOf(lengthTillPeriodDueDate), mc) //
|
||||
.multiply(BigDecimal.valueOf(getLength()), mc); //
|
||||
return MathUtil.negativeToZero(MathUtil.add(creditedInterest.getAmount(), interestDueTillRepaymentDueDate, mc));
|
||||
return MathUtil.negativeToZero(MathUtil.add(mc, creditedInterest.getAmount(), interestDueTillRepaymentDueDate));
|
||||
}
|
||||
|
||||
public long getLength() {
|
||||
|
||||
+33
-20
@@ -60,6 +60,9 @@ public final class RepaymentPeriod {
|
||||
private Money paidPrincipal;
|
||||
@Getter
|
||||
private Money paidInterest;
|
||||
@Setter
|
||||
@Getter
|
||||
private Money futureUnrecognizedInterest;
|
||||
|
||||
@JsonExclude
|
||||
private final MathContext mc;
|
||||
@@ -72,9 +75,12 @@ public final class RepaymentPeriod {
|
||||
private Memo<Money> dueInterestCalculation;
|
||||
@JsonExclude
|
||||
private Memo<Money> outstandingBalanceCalculation;
|
||||
@Getter
|
||||
@Setter
|
||||
private boolean isInterestMoved = false;
|
||||
|
||||
private RepaymentPeriod(RepaymentPeriod previous, LocalDate fromDate, LocalDate dueDate, List<InterestPeriod> interestPeriods,
|
||||
Money emi, Money originalEmi, Money paidPrincipal, Money paidInterest, MathContext mc) {
|
||||
Money emi, Money originalEmi, Money paidPrincipal, Money paidInterest, Money futureUnrecognizedInterest, MathContext mc) {
|
||||
this.previous = previous;
|
||||
this.fromDate = fromDate;
|
||||
this.dueDate = dueDate;
|
||||
@@ -83,17 +89,18 @@ public final class RepaymentPeriod {
|
||||
this.originalEmi = originalEmi;
|
||||
this.paidPrincipal = paidPrincipal;
|
||||
this.paidInterest = paidInterest;
|
||||
this.futureUnrecognizedInterest = futureUnrecognizedInterest;
|
||||
this.mc = mc;
|
||||
}
|
||||
|
||||
public static RepaymentPeriod empty(RepaymentPeriod previous, MathContext mc) {
|
||||
return new RepaymentPeriod(previous, null, null, new ArrayList<>(), null, null, null, null, mc);
|
||||
return new RepaymentPeriod(previous, null, null, new ArrayList<>(), null, null, null, null, null, mc);
|
||||
}
|
||||
|
||||
public static RepaymentPeriod create(RepaymentPeriod previous, LocalDate fromDate, LocalDate dueDate, Money emi, MathContext mc) {
|
||||
final Money zero = emi.zero();
|
||||
final RepaymentPeriod newRepaymentPeriod = new RepaymentPeriod(previous, fromDate, dueDate, new ArrayList<>(), emi, emi, zero, zero,
|
||||
mc);
|
||||
zero, mc);
|
||||
// There is always at least 1 interest period, by default with same from-due date as repayment period
|
||||
newRepaymentPeriod.interestPeriods.add(InterestPeriod.withEmptyAmounts(newRepaymentPeriod, fromDate, dueDate));
|
||||
return newRepaymentPeriod;
|
||||
@@ -102,7 +109,7 @@ public final class RepaymentPeriod {
|
||||
public static RepaymentPeriod copy(RepaymentPeriod previous, RepaymentPeriod repaymentPeriod, MathContext mc) {
|
||||
final RepaymentPeriod newRepaymentPeriod = new RepaymentPeriod(previous, repaymentPeriod.fromDate, repaymentPeriod.dueDate,
|
||||
new ArrayList<>(), repaymentPeriod.emi, repaymentPeriod.originalEmi, repaymentPeriod.paidPrincipal,
|
||||
repaymentPeriod.paidInterest, mc);
|
||||
repaymentPeriod.paidInterest, repaymentPeriod.futureUnrecognizedInterest, mc);
|
||||
// There is always at least 1 interest period, by default with same from-due date as repayment period
|
||||
for (InterestPeriod interestPeriod : repaymentPeriod.interestPeriods) {
|
||||
newRepaymentPeriod.interestPeriods.add(InterestPeriod.copy(newRepaymentPeriod, interestPeriod, mc));
|
||||
@@ -113,7 +120,7 @@ public final class RepaymentPeriod {
|
||||
public static RepaymentPeriod copyWithoutPaidAmounts(RepaymentPeriod previous, RepaymentPeriod repaymentPeriod, MathContext mc) {
|
||||
final Money zero = repaymentPeriod.emi.zero();
|
||||
final RepaymentPeriod newRepaymentPeriod = new RepaymentPeriod(previous, repaymentPeriod.fromDate, repaymentPeriod.dueDate,
|
||||
new ArrayList<>(), repaymentPeriod.emi, repaymentPeriod.originalEmi, zero, zero, mc);
|
||||
new ArrayList<>(), repaymentPeriod.emi, repaymentPeriod.originalEmi, zero, zero, zero, mc);
|
||||
// There is always at least 1 interest period, by default with same from-due date as repayment period
|
||||
for (InterestPeriod interestPeriod : repaymentPeriod.interestPeriods) {
|
||||
var interestPeriodCopy = InterestPeriod.copy(newRepaymentPeriod, interestPeriod);
|
||||
@@ -154,14 +161,19 @@ public final class RepaymentPeriod {
|
||||
public Money getCalculatedDueInterest() {
|
||||
if (calculatedDueInterestCalculation == null) {
|
||||
calculatedDueInterestCalculation = Memo.of(this::calculateCalculatedDueInterest,
|
||||
() -> new Object[] { this.previous, this.interestPeriods });
|
||||
() -> new Object[] { this.previous, this.interestPeriods, this.futureUnrecognizedInterest, this.isInterestMoved });
|
||||
}
|
||||
return calculatedDueInterestCalculation.get();
|
||||
}
|
||||
|
||||
private Money calculateCalculatedDueInterest() {
|
||||
Money calculatedDueInterest = Money.of(emi.getCurrencyData(),
|
||||
getInterestPeriods().stream().map(InterestPeriod::getCalculatedDueInterest).reduce(BigDecimal.ZERO, BigDecimal::add), mc);
|
||||
Money calculatedDueInterest = getZero(mc);
|
||||
if (!isInterestMoved) {
|
||||
calculatedDueInterest = Money.of(emi.getCurrencyData(),
|
||||
getInterestPeriods().stream().map(InterestPeriod::getCalculatedDueInterest).reduce(BigDecimal.ZERO, BigDecimal::add),
|
||||
mc);
|
||||
}
|
||||
calculatedDueInterest = calculatedDueInterest.add(getFutureUnrecognizedInterest(), mc);
|
||||
if (getPrevious().isPresent()) {
|
||||
calculatedDueInterest = calculatedDueInterest.add(getPrevious().get().getUnrecognizedInterest(), mc);
|
||||
}
|
||||
@@ -176,22 +188,22 @@ public final class RepaymentPeriod {
|
||||
public Money getDueInterest() {
|
||||
if (dueInterestCalculation == null) {
|
||||
// Due interest might be the maximum paid if there is pay-off or early repayment
|
||||
dueInterestCalculation = Memo
|
||||
.of(() -> MathUtil.max(
|
||||
getPaidPrincipal().isGreaterThan(getCalculatedDuePrincipal()) ? getPaidInterest()
|
||||
: MathUtil.min(getCalculatedDueInterest(), getEmiPlusCreditedAmounts(), false),
|
||||
getPaidInterest(), false), () -> new Object[] { paidPrincipal, paidInterest, interestPeriods });
|
||||
dueInterestCalculation = Memo.of(
|
||||
() -> MathUtil.max(getPaidPrincipal().isGreaterThan(getCalculatedDuePrincipal()) ? getPaidInterest()
|
||||
: MathUtil.min(getCalculatedDueInterest(), getEmiPlusCreditedAmountsPlusFutureUnrecognizedInterest(), false),
|
||||
getPaidInterest(), false),
|
||||
() -> new Object[] { paidPrincipal, paidInterest, interestPeriods, futureUnrecognizedInterest });
|
||||
}
|
||||
return dueInterestCalculation.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gives back an EMI amount which includes credited amounts as well
|
||||
* Gives back an EMI amount which includes credited amounts and future unrecognized interest as well
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public Money getEmiPlusCreditedAmounts() {
|
||||
return getEmi().plus(getTotalCreditedAmount(), mc); //
|
||||
public Money getEmiPlusCreditedAmountsPlusFutureUnrecognizedInterest() {
|
||||
return getEmi().plus(getTotalCreditedAmount(), mc).plus(getFutureUnrecognizedInterest(), mc); //
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -200,7 +212,7 @@ public final class RepaymentPeriod {
|
||||
* @return
|
||||
*/
|
||||
public Money getCalculatedDuePrincipal() {
|
||||
return MathUtil.negativeToZero(getEmiPlusCreditedAmounts().minus(getCalculatedDueInterest(), mc), mc);
|
||||
return MathUtil.negativeToZero(getEmiPlusCreditedAmountsPlusFutureUnrecognizedInterest().minus(getCalculatedDueInterest(), mc), mc);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -243,8 +255,9 @@ public final class RepaymentPeriod {
|
||||
*/
|
||||
public Money getDuePrincipal() {
|
||||
// Due principal might be the maximum paid if there is pay-off or early repayment
|
||||
return MathUtil.max(MathUtil.negativeToZero(getEmiPlusCreditedAmounts().minus(getDueInterest(), mc), mc), getPaidPrincipal(),
|
||||
false);
|
||||
return MathUtil.max(
|
||||
MathUtil.negativeToZero(getEmiPlusCreditedAmountsPlusFutureUnrecognizedInterest().minus(getDueInterest(), mc), mc),
|
||||
getPaidPrincipal(), false);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -266,7 +279,7 @@ public final class RepaymentPeriod {
|
||||
}
|
||||
|
||||
public boolean isFullyPaid() {
|
||||
return getEmiPlusCreditedAmounts().isEqualTo(getTotalPaidAmount());
|
||||
return getEmiPlusCreditedAmountsPlusFutureUnrecognizedInterest().isEqualTo(getTotalPaidAmount());
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user