FINERACT-2398: Add handling for equal overdue interest split re-amortization
This commit is contained in:
committed by
Arnold Gálovics
parent
a229a55c49
commit
36d779eabf
+68
-4
@@ -605,10 +605,74 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
|
||||
}
|
||||
}
|
||||
|
||||
private void handleReamortizationWithEqualAmortizationInterestSplitHandlingType(LoanTransaction loanTransaction,
|
||||
TransactionCtx transactionCtx) {
|
||||
throw new UnsupportedOperationException(
|
||||
"EQUAL_AMORTIZATION_INTEREST_SPLIT interest handling strategy for re-amortization is not implemented");
|
||||
private void handleReamortizationWithEqualAmortizationInterestSplitHandlingType(final LoanTransaction loanTransaction,
|
||||
final TransactionCtx transactionCtx) {
|
||||
if (transactionCtx instanceof ProgressiveTransactionCtx progressiveTransactionCtx
|
||||
&& loanTransaction.getLoan().isInterestBearingAndInterestRecalculationEnabled()) {
|
||||
final LocalDate transactionDate = loanTransaction.getTransactionDate();
|
||||
final MonetaryCurrency currency = progressiveTransactionCtx.getCurrency();
|
||||
|
||||
final List<LoanRepaymentScheduleInstallment> previousInstallments = progressiveTransactionCtx.getInstallments().stream()
|
||||
.filter(installment -> !installment.getDueDate().isAfter(transactionDate)).toList();
|
||||
|
||||
final List<LoanRepaymentScheduleInstallment> futureInstallments = progressiveTransactionCtx.getInstallments().stream()
|
||||
.filter(installment -> installment.getDueDate().isAfter(transactionDate))
|
||||
.filter(installment -> !installment.isAdditional() && !installment.isDownPayment() && !installment.isReAged()).toList();
|
||||
|
||||
Money totalOverDuePrincipal = Money.zero(currency);
|
||||
Money totalOverDueFee = Money.zero(currency);
|
||||
for (LoanRepaymentScheduleInstallment installment : previousInstallments) {
|
||||
final Money outstandingPrincipal = emiCalculator
|
||||
.findRepaymentPeriod(progressiveTransactionCtx.getModel(), installment.getFromDate(), installment.getDueDate())
|
||||
.map(RepaymentPeriod::getOutstandingPrincipal).map(amount -> Money.of(currency, amount.getAmount()))
|
||||
.orElse(Money.zero(currency));
|
||||
totalOverDuePrincipal = totalOverDuePrincipal.add(outstandingPrincipal);
|
||||
totalOverDueFee = totalOverDueFee.add(installment.getFeeChargesOutstanding(currency));
|
||||
installment.setFeeChargesCharged(installment.getFeeChargesPaid(currency).getAmount());
|
||||
}
|
||||
|
||||
final Money totalOverDueInterest = emiCalculator.getOutstandingInterestTillDate(progressiveTransactionCtx.getModel(),
|
||||
previousInstallments.getLast().getDueDate());
|
||||
|
||||
loanTransaction.resetDerivedComponents();
|
||||
loanTransaction.updateComponentsAndTotal(totalOverDuePrincipal, totalOverDueInterest, Money.zero(currency),
|
||||
Money.zero(currency));
|
||||
|
||||
emiCalculator.updateModelRepaymentPeriodsDuringReAmortizationWithEqualInterestSplit(progressiveTransactionCtx.getModel(),
|
||||
transactionDate);
|
||||
updateInstallmentsByRepaymentPeriods(loanTransaction, progressiveTransactionCtx);
|
||||
|
||||
distributeFeeAmongFutureInstallments(totalOverDueFee, futureInstallments);
|
||||
} else {
|
||||
// TODO: implement interestRecalculation = false logic
|
||||
throw new UnsupportedOperationException(
|
||||
"Logic for re-amortization when interest bearing loan has interestRecalculation disabled is not implemented");
|
||||
}
|
||||
}
|
||||
|
||||
private void distributeFeeAmongFutureInstallments(final Money totalOverDueFee,
|
||||
final List<LoanRepaymentScheduleInstallment> futureInstallments) {
|
||||
if (totalOverDueFee.isZero() || futureInstallments.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
final int numberOfFutureInstallments = futureInstallments.size();
|
||||
final MonetaryCurrency currency = totalOverDueFee.getCurrency();
|
||||
BigDecimal feePortionPerInstallment = totalOverDueFee.getAmount().divide(BigDecimal.valueOf(numberOfFutureInstallments),
|
||||
MoneyHelper.getMathContext());
|
||||
|
||||
final LoanRepaymentScheduleInstallment lastFutureInstallment = futureInstallments.stream()
|
||||
.max(Comparator.comparing(LoanRepaymentScheduleInstallment::getDueDate)).get();
|
||||
|
||||
BigDecimal remainingFee = totalOverDueFee.getAmount();
|
||||
for (final LoanRepaymentScheduleInstallment installment : futureInstallments) {
|
||||
if (lastFutureInstallment.equals(installment)) {
|
||||
installment.addToFeeCharges(Money.of(currency, remainingFee));
|
||||
} else {
|
||||
installment.addToFeeCharges(Money.of(currency, feePortionPerInstallment));
|
||||
remainingFee = remainingFee.subtract(feePortionPerInstallment);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void handleReamortizationWithWaiveInterestHandlingType(LoanTransaction loanTransaction, TransactionCtx transactionCtx) {
|
||||
|
||||
+3
@@ -172,4 +172,7 @@ public interface EMICalculator {
|
||||
LocalDate targetRepaymentPeriodDueDate, LocalDate newDueDate);
|
||||
|
||||
void updateModelRepaymentPeriodsDuringReAmortization(ProgressiveLoanInterestScheduleModel model, LocalDate transactionDate);
|
||||
|
||||
void updateModelRepaymentPeriodsDuringReAmortizationWithEqualInterestSplit(ProgressiveLoanInterestScheduleModel model,
|
||||
LocalDate transactionDate);
|
||||
}
|
||||
|
||||
+77
@@ -685,6 +685,83 @@ public final class ProgressiveEMICalculator implements EMICalculator {
|
||||
checkAndAdjustEmiIfNeededOnRelatedRepaymentPeriods(model, reAmortizedPeriods);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateModelRepaymentPeriodsDuringReAmortizationWithEqualInterestSplit(final ProgressiveLoanInterestScheduleModel model,
|
||||
final LocalDate transactionDate) {
|
||||
final MathContext mc = model.mc();
|
||||
final List<RepaymentPeriod> periodsBeforeTransactionDate = model.repaymentPeriods().stream()
|
||||
.filter(rp -> !rp.getDueDate().isAfter(transactionDate) && !rp.isFullyPaid()).toList();
|
||||
|
||||
Money totalAmortizedInterest = model.zero();
|
||||
for (RepaymentPeriod rp : periodsBeforeTransactionDate) {
|
||||
totalAmortizedInterest = totalAmortizedInterest.add(rp.getDueInterest().minus(rp.getPaidInterest(), mc), mc);
|
||||
}
|
||||
|
||||
final List<RepaymentPeriod> futurePeriods = model.repaymentPeriods().stream().filter(rp -> rp.getDueDate().isAfter(transactionDate))
|
||||
.toList();
|
||||
|
||||
final int numberOfFuturePeriods = futurePeriods.size();
|
||||
|
||||
Money interestPortionPerPeriod = model.zero();
|
||||
if (numberOfFuturePeriods > 0 && totalAmortizedInterest.isGreaterThanZero()) {
|
||||
interestPortionPerPeriod = totalAmortizedInterest.dividedBy(numberOfFuturePeriods, mc);
|
||||
}
|
||||
|
||||
model.repaymentPeriods().forEach(rp -> rp.getInterestPeriods().forEach(ip -> {
|
||||
if (!ip.getBalanceCorrectionAmount().isZero()) {
|
||||
ip.addBalanceCorrectionAmount(ip.getBalanceCorrectionAmount().negated());
|
||||
}
|
||||
}));
|
||||
|
||||
final List<RepaymentPeriod> allPeriods = model.repaymentPeriods();
|
||||
for (int i = 0; i < allPeriods.size(); i++) {
|
||||
final RepaymentPeriod rp = allPeriods.get(i);
|
||||
if (rp.isFullyPaid() && !rp.getDueDate().isAfter(transactionDate)) {
|
||||
Money nextPeriodCreditedPrincipal = model.zero();
|
||||
if (i + 1 < allPeriods.size()) {
|
||||
nextPeriodCreditedPrincipal = allPeriods.get(i + 1).getCreditedPrincipal();
|
||||
}
|
||||
final Money effectivePaidPrincipal = rp.getPaidPrincipal().minus(nextPeriodCreditedPrincipal, mc);
|
||||
if (effectivePaidPrincipal.isGreaterThanZero()) {
|
||||
final InterestPeriod lastInterestPeriod = rp.getInterestPeriods().getLast();
|
||||
lastInterestPeriod.addBalanceCorrectionAmount(effectivePaidPrincipal.negated());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
moveOutstandingAmountsFromPeriodsBeforeTransactionDateForEqualInterestSplit(periodsBeforeTransactionDate, transactionDate);
|
||||
|
||||
calculateRateFactorForPeriods(futurePeriods, model);
|
||||
|
||||
for (final RepaymentPeriod futurePeriod : futurePeriods) {
|
||||
futurePeriod.setReAgedInterest(MathUtil.plus(futurePeriod.getReAgedInterest(), interestPortionPerPeriod, mc));
|
||||
}
|
||||
|
||||
calculateEMIOnActualModel(futurePeriods, model);
|
||||
calculateOutstandingBalance(model);
|
||||
calculateLastUnpaidRepaymentPeriodEMI(model, transactionDate);
|
||||
checkAndAdjustEmiIfNeededOnRelatedRepaymentPeriods(model, futurePeriods);
|
||||
}
|
||||
|
||||
private void moveOutstandingAmountsFromPeriodsBeforeTransactionDateForEqualInterestSplit(
|
||||
final List<RepaymentPeriod> periodsBeforeTransactionDate, final LocalDate transactionDate) {
|
||||
periodsBeforeTransactionDate.forEach(rp -> {
|
||||
rp.getInterestPeriods().stream().filter(ip -> ip.getDueDate().isEqual(transactionDate)).forEach(ip -> {
|
||||
if (!ip.getBalanceCorrectionAmount().isZero()) {
|
||||
ip.addBalanceCorrectionAmount(ip.getBalanceCorrectionAmount().negated());
|
||||
}
|
||||
});
|
||||
final InterestPeriod lastInterestPeriod = rp.getInterestPeriods().getLast();
|
||||
Money paidPrincipal = rp.getPaidPrincipal();
|
||||
if (paidPrincipal.isGreaterThanZero()) {
|
||||
lastInterestPeriod.addBalanceCorrectionAmount(paidPrincipal.negated());
|
||||
}
|
||||
rp.setEmi(rp.getTotalPaidAmount());
|
||||
rp.moveOutstandingDueToReAging();
|
||||
rp.setNoUnrecognisedInterest(true);
|
||||
});
|
||||
}
|
||||
|
||||
private LocalDate calculateDateForFixedLength(final ProgressiveLoanInterestScheduleModel scheduleModel,
|
||||
final LoanApplicationTerms loanApplicationTerms, final LocalDate targetRepaymentPeriodDueDate, final LocalDate newDueDate) {
|
||||
final long variationDays = DateUtils.getDifferenceInDays(targetRepaymentPeriodDueDate, newDueDate);
|
||||
|
||||
Reference in New Issue
Block a user