FINERACT-2413: Re-amortization:- Accrual and Accrual Activity handling - Equal outstanding interest split

This commit is contained in:
Soma Sörös
2026-02-23 10:10:38 +00:00
committed by Adam Saghy
parent c7b4fc5404
commit fb43706e6c
4 changed files with 60 additions and 50 deletions
@@ -896,7 +896,7 @@ public final class ProgressiveEMICalculator implements EMICalculator {
}
rp.setEmi(rp.getTotalPaidAmount());
rp.moveOutstandingDueToReAging();
rp.setNoUnrecognisedInterest(true);
rp.setInterestMovedDownward(true);
});
}
@@ -1098,7 +1098,7 @@ public final class ProgressiveEMICalculator implements EMICalculator {
findLastUnpaidRepaymentPeriod.ifPresent(repaymentPeriod -> {
repaymentPeriod.setFutureUnrecognizedInterest(scheduleModel.zero());
scheduleModel.repaymentPeriods().forEach(rp -> {
rp.setInterestMoved(false);
rp.setInterestMovedUpward(false);
});
MathContext mc = scheduleModel.mc();
@@ -1161,7 +1161,7 @@ public final class ProgressiveEMICalculator implements EMICalculator {
repaymentPeriod.setFutureUnrecognizedInterest(period.getUnrecognizedInterest());
scheduleModel.repaymentPeriods().stream().filter(rp -> rp.getDueDate().isAfter(repaymentPeriod.getDueDate())) //
.forEach(rp -> {
rp.setInterestMoved(true);
rp.setInterestMovedUpward(true);
});
});
}
@@ -1530,7 +1530,7 @@ public final class ProgressiveEMICalculator implements EMICalculator {
final CurrencyData currency = scheduleModel.loanProductRelatedDetail().getCurrencyData();
RepaymentPeriod firstRepaymentPeriod = repaymentPeriods.getFirst();
RepaymentPeriod lastRepaymentPeriod = repaymentPeriods.getLast();
Money sumOfInterest = Money.zero(currency);
Money sumOfInterest = Money.of(currency, calculateEMIValueForFixedInterest(repaymentPeriods, mc), mc);
for (RepaymentPeriod rp : repaymentPeriods) {
Money interest = rp.calculateCalculatedDueInterest();
sumOfInterest = sumOfInterest.add(interest);
@@ -1604,8 +1604,9 @@ public final class ProgressiveEMICalculator implements EMICalculator {
final Money outstandingBalance = startPeriod.getInitialBalanceForEmiRecalculation();
final Money equalMonthlyInstallment = Money.of(outstandingBalance.getCurrencyData(),
calculateEMIValue(rateFactorN, outstandingBalance.getAmount(), fnResult, mc), mc);
final Money equalMonthlyInstallment = Money
.of(outstandingBalance.getCurrencyData(), calculateEMIValue(rateFactorN, outstandingBalance.getAmount(), fnResult, mc), mc)
.add(calculateEMIValueForFixedInterest(repaymentPeriods, mc));
final Money finalEqualMonthlyInstallment = applyInstallmentAmountInMultiplesOf(scheduleModel, equalMonthlyInstallment);
repaymentPeriods.forEach(period -> {
@@ -1716,6 +1717,14 @@ public final class ProgressiveEMICalculator implements EMICalculator {
return rateFactorPlus1N.multiply(outstandingBalanceForRest, mc).divide(fnResult, mc);
}
/**
* Calculate the EMI (Equal Monthly Installment) value for fixed interest portion
*/
private BigDecimal calculateEMIValueForFixedInterest(final List<RepaymentPeriod> repaymentPeriods, MathContext mc) {
return repaymentPeriods.stream().map(RepaymentPeriod::getFixedInterest).map(Money::getAmount).reduce(ZERO, BigDecimal::add)
.divide(BigDecimal.valueOf(repaymentPeriods.isEmpty() ? 1 : repaymentPeriods.size()), mc);
}
/**
* To calculate the daily payment, we first need to calculate something called the Rate Factor. We're going to be
* using simple interest. The Rate Factor for simple interest is calculated by the following formula:
@@ -1994,12 +2003,9 @@ public final class ProgressiveEMICalculator implements EMICalculator {
.addCreditedInterestAmount(MathUtil.min(rp.getOutstandingInterest(), rp.getCreditedInterest(), false).negated());
rp.setEmi(rp.getTotalPaidAmount());
rp.moveOutstandingDueToReAging();
rp.setNoUnrecognisedInterest(true);
rp.setInterestMovedDownward(true);
});
// stop calculate unrecognised interest at this point because all
interestSchedule.getLastRepaymentPeriod().setNoUnrecognisedInterest(true);
if (!originalMaturityDate.isBefore(transactionDate)) {
createRepaymentPeriodForEarlyRepaidAmountsDuringReAgeing(interestSchedule,
paidBalancesFromTransactionDate.getOutstandingPrincipal(), paidBalancesFromTransactionDate.getOutstandingInterest(),
@@ -77,7 +77,7 @@ public class RepaymentPeriod {
private Memo<Money> outstandingBalanceCalculation;
@Getter
@Setter
private boolean isInterestMoved = false;
private boolean isInterestMovedUpward = false;
@Setter
private Money totalDisbursedAmount;
@@ -99,7 +99,7 @@ public class RepaymentPeriod {
private Money creditedInterestMovedDueReAge;
@Setter
@Getter
private boolean noUnrecognisedInterest;
private boolean isInterestMovedDownward;
@Setter
@Getter
private boolean reAged;
@@ -111,7 +111,7 @@ public class RepaymentPeriod {
protected RepaymentPeriod(RepaymentPeriod previous, LocalDate fromDate, LocalDate dueDate, List<InterestPeriod> interestPeriods,
Money emi, Money originalEmi, Money paidPrincipal, Money paidInterest, Money futureUnrecognizedInterest, MathContext mc,
ILoanConfigurationDetails loanProductRelatedDetail, boolean noUnrecognisedInterest, boolean reAged,
ILoanConfigurationDetails loanProductRelatedDetail, boolean isInterestMovedDownward, boolean reAged,
boolean reAgedEarlyRepaymentHolder, Money fixedInterest) {
this.previous = previous;
this.fromDate = fromDate;
@@ -124,7 +124,7 @@ public class RepaymentPeriod {
this.futureUnrecognizedInterest = futureUnrecognizedInterest;
this.mc = mc;
this.loanProductRelatedDetail = loanProductRelatedDetail;
this.noUnrecognisedInterest = noUnrecognisedInterest;
this.isInterestMovedDownward = isInterestMovedDownward;
this.reAged = reAged;
this.reAgedEarlyRepaymentHolder = reAgedEarlyRepaymentHolder;
this.fixedInterest = fixedInterest;
@@ -151,13 +151,13 @@ public class RepaymentPeriod {
final RepaymentPeriod newRepaymentPeriod = new RepaymentPeriod(previous, repaymentPeriod.getFromDate(),
repaymentPeriod.getDueDate(), new ArrayList<>(), repaymentPeriod.getEmi(), repaymentPeriod.getOriginalEmi(),
repaymentPeriod.getPaidPrincipal(), repaymentPeriod.getPaidInterest(), repaymentPeriod.getFutureUnrecognizedInterest(), mc,
repaymentPeriod.getLoanProductRelatedDetail(), repaymentPeriod.isNoUnrecognisedInterest(), repaymentPeriod.isReAged(),
repaymentPeriod.getLoanProductRelatedDetail(), repaymentPeriod.isInterestMovedDownward(), repaymentPeriod.isReAged(),
repaymentPeriod.isReAgedEarlyRepaymentHolder(), repaymentPeriod.getFixedInterest());
newRepaymentPeriod.setCreditedPrincipalMovedDueReAge(repaymentPeriod.getCreditedPrincipalMovedDueReAge());
newRepaymentPeriod.setCreditedInterestMovedDueReAge(repaymentPeriod.getCreditedInterestMovedDueReAge());
newRepaymentPeriod.setTotalDisbursedAmount(repaymentPeriod.getTotalDisbursedAmount());
newRepaymentPeriod.setTotalCapitalizedIncomeAmount(repaymentPeriod.getTotalCapitalizedIncomeAmount());
newRepaymentPeriod.setInterestMoved(repaymentPeriod.isInterestMoved());
newRepaymentPeriod.setInterestMovedUpward(repaymentPeriod.isInterestMovedUpward());
newRepaymentPeriod.setCurrency(repaymentPeriod.getCurrency());
// There is always at least 1 interest period, by default with same from-due date as repayment period
for (InterestPeriod interestPeriod : repaymentPeriod.getInterestPeriods()) {
@@ -170,13 +170,16 @@ public class RepaymentPeriod {
final Money zero = Money.zero(repaymentPeriod.getCurrency(), mc);
final RepaymentPeriod newRepaymentPeriod = new RepaymentPeriod(previous, repaymentPeriod.getFromDate(),
repaymentPeriod.getDueDate(), new ArrayList<>(), repaymentPeriod.getEmi(), repaymentPeriod.getOriginalEmi(), zero, zero,
zero, mc, repaymentPeriod.getLoanProductRelatedDetail(), repaymentPeriod.isNoUnrecognisedInterest(),
zero, mc, repaymentPeriod.getLoanProductRelatedDetail(), repaymentPeriod.isInterestMovedDownward(),
repaymentPeriod.isReAged(), repaymentPeriod.isReAgedEarlyRepaymentHolder(), repaymentPeriod.getFixedInterest());
newRepaymentPeriod.setCreditedPrincipalMovedDueReAge(repaymentPeriod.getCreditedPrincipalMovedDueReAge());
newRepaymentPeriod.setCreditedInterestMovedDueReAge(repaymentPeriod.getCreditedInterestMovedDueReAge());
if (repaymentPeriod.isInterestMovedDownward()) {
newRepaymentPeriod.setFixedInterest(repaymentPeriod.getPaidInterest());
}
newRepaymentPeriod.setTotalDisbursedAmount(repaymentPeriod.getTotalDisbursedAmount());
newRepaymentPeriod.setTotalCapitalizedIncomeAmount(repaymentPeriod.getTotalCapitalizedIncomeAmount());
newRepaymentPeriod.setInterestMoved(repaymentPeriod.isInterestMoved());
newRepaymentPeriod.setInterestMovedUpward(repaymentPeriod.isInterestMovedUpward());
newRepaymentPeriod.setCurrency(repaymentPeriod.getCurrency());
// There is always at least 1 interest period, by default with same from-due date as repayment period
for (InterestPeriod interestPeriod : repaymentPeriod.getInterestPeriods()) {
@@ -217,8 +220,9 @@ public class RepaymentPeriod {
@NotNull
public Money getCalculatedDueInterest() {
if (calculatedDueInterestCalculation == null) {
calculatedDueInterestCalculation = Memo.of(this::calculateCalculatedDueInterest, () -> new Object[] { previous, interestPeriods,
futureUnrecognizedInterest, isInterestMoved, totalDisbursedAmount, fixedInterest, reAged });
calculatedDueInterestCalculation = Memo.of(this::calculateCalculatedDueInterest,
() -> new Object[] { previous, interestPeriods, futureUnrecognizedInterest, isInterestMovedUpward,
isInterestMovedDownward, totalDisbursedAmount, fixedInterest, reAged });
}
return calculatedDueInterestCalculation.get();
}
@@ -242,7 +246,7 @@ public class RepaymentPeriod {
public Money calculateCalculatedDueInterest() {
Money calculatedDueInterest = getZero();
if (!isInterestMoved()) {
if (!isInterestMovedUpward() && !isInterestMovedDownward()) {
calculatedDueInterest = Money.of(getEmi().getCurrencyData(),
getInterestPeriods().stream().map(InterestPeriod::getCalculatedDueInterest).reduce(BigDecimal.ZERO, BigDecimal::add),
mc);
@@ -367,7 +371,7 @@ public class RepaymentPeriod {
* @return
*/
public Money getUnrecognizedInterest() {
return noUnrecognisedInterest ? getZero() : getCalculatedDueInterest().minus(getDueInterest(), getMc());
return MathUtil.negativeToZero(getCalculatedDueInterest().minus(getDueInterest(), getMc()), getMc());
}
public Money getCreditedAmounts() {