FINERACT-2433: Fix interest and accrual calculation in case of last installment payment

This commit is contained in:
Adam Saghy
2026-01-08 12:22:48 +01:00
parent d42b4f7b6c
commit 8b3c8280de
9 changed files with 420 additions and 37 deletions
@@ -601,17 +601,20 @@ public final class ProgressiveEMICalculator implements EMICalculator {
public boolean recalculateModelOverdueAmountsTillDate(final ProgressiveLoanInterestScheduleModel scheduleModel,
final LocalDate targetDate, boolean prepayAttempt) {
boolean hasChange = false;
final List<RepaymentPeriod> overdueInstallmentsSortedByInstallmentNumber = findPossiblyOverdueRepaymentPeriods(targetDate,
scheduleModel);
LocalDate recalculatedTargetDate = DateUtils.isAfter(targetDate, scheduleModel.getLastRepaymentPeriod().getDueDate())
? scheduleModel.getLastRepaymentPeriod().getDueDate()
: targetDate;
final List<RepaymentPeriod> overdueInstallmentsSortedByInstallmentNumber = findPossiblyOverdueRepaymentPeriods(
recalculatedTargetDate, scheduleModel);
if (!overdueInstallmentsSortedByInstallmentNumber.isEmpty()) {
final RepaymentPeriod lastPeriod = scheduleModel.getLastRepaymentPeriod();
final RepaymentPeriod currentPeriod = scheduleModel.findRepaymentPeriod(targetDate).orElse(lastPeriod);
final RepaymentPeriod currentPeriod = scheduleModel.findRepaymentPeriod(recalculatedTargetDate).orElse(lastPeriod);
Money overDuePrincipal = scheduleModel.zero();
Money aggregatedOverDuePrincipal = scheduleModel.zero();
for (RepaymentPeriod processingPeriod : overdueInstallmentsSortedByInstallmentNumber) {
// add and subtract outstanding principal
if (!overDuePrincipal.isZero()) {
final boolean currentChanges = adjustOverduePrincipal(targetDate, processingPeriod, overDuePrincipal,
final boolean currentChanges = adjustOverduePrincipal(recalculatedTargetDate, processingPeriod, overDuePrincipal,
aggregatedOverDuePrincipal, scheduleModel, prepayAttempt);
hasChange = hasChange || currentChanges;
@@ -621,15 +624,15 @@ public final class ProgressiveEMICalculator implements EMICalculator {
aggregatedOverDuePrincipal = aggregatedOverDuePrincipal.add(overDuePrincipal);
}
if (!currentPeriod.equals(lastPeriod) || !targetDate.isAfter(lastPeriod.getDueDate())) {
final boolean currentChanges = adjustOverduePrincipal(targetDate, currentPeriod, overDuePrincipal,
if (!currentPeriod.equals(lastPeriod) || !recalculatedTargetDate.isAfter(lastPeriod.getDueDate())) {
final boolean currentChanges = adjustOverduePrincipal(recalculatedTargetDate, currentPeriod, overDuePrincipal,
aggregatedOverDuePrincipal, scheduleModel, prepayAttempt);
hasChange = hasChange || currentChanges;
}
if (aggregatedOverDuePrincipal.isGreaterThanZero() && (scheduleModel.lastOverdueBalanceChange() == null
|| scheduleModel.lastOverdueBalanceChange().isBefore(targetDate))) {
scheduleModel.lastOverdueBalanceChange(targetDate);
|| scheduleModel.lastOverdueBalanceChange().isBefore(recalculatedTargetDate))) {
scheduleModel.lastOverdueBalanceChange(recalculatedTargetDate);
}
}
@@ -87,6 +87,7 @@ public class RepaymentPeriod {
@Getter
private final ILoanConfigurationDetails loanProductRelatedDetail;
@JsonExclude
@Setter
private MonetaryCurrency currency;
@Getter
@@ -154,6 +155,10 @@ public class RepaymentPeriod {
repaymentPeriod.isReAgedEarlyRepaymentHolder(), repaymentPeriod.getReAgedInterest());
newRepaymentPeriod.setCreditedPrincipalMovedDueReAge(repaymentPeriod.getCreditedPrincipalMovedDueReAge());
newRepaymentPeriod.setCreditedInterestMovedDueReAge(repaymentPeriod.getCreditedInterestMovedDueReAge());
newRepaymentPeriod.setTotalDisbursedAmount(repaymentPeriod.getTotalDisbursedAmount());
newRepaymentPeriod.setTotalCapitalizedIncomeAmount(repaymentPeriod.getTotalCapitalizedIncomeAmount());
newRepaymentPeriod.setInterestMoved(repaymentPeriod.isInterestMoved());
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()) {
newRepaymentPeriod.getInterestPeriods().add(InterestPeriod.copy(newRepaymentPeriod, interestPeriod, mc));
@@ -169,6 +174,10 @@ public class RepaymentPeriod {
repaymentPeriod.isReAged(), repaymentPeriod.isReAgedEarlyRepaymentHolder(), repaymentPeriod.getReAgedInterest());
newRepaymentPeriod.setCreditedPrincipalMovedDueReAge(repaymentPeriod.getCreditedPrincipalMovedDueReAge());
newRepaymentPeriod.setCreditedInterestMovedDueReAge(repaymentPeriod.getCreditedInterestMovedDueReAge());
newRepaymentPeriod.setTotalDisbursedAmount(repaymentPeriod.getTotalDisbursedAmount());
newRepaymentPeriod.setTotalCapitalizedIncomeAmount(repaymentPeriod.getTotalCapitalizedIncomeAmount());
newRepaymentPeriod.setInterestMoved(repaymentPeriod.isInterestMoved());
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()) {
var interestPeriodCopy = InterestPeriod.copy(newRepaymentPeriod, interestPeriod);