FINERACT-2281: fix unrecognized interest handling when first period is unpaid

This commit is contained in:
mark.vituska
2025-06-18 10:53:30 +02:00
committed by Adam Saghy
parent 4d56a4f230
commit 5649aa3ded
4 changed files with 373 additions and 73 deletions
@@ -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) {
@@ -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() {
@@ -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());
}
/**