Skip to content

Commit

Permalink
FINERACT-1981: Interest recalculation - Recalculate Interest For Loan…
Browse files Browse the repository at this point in the history
… - Business step for COB - progressive loan
  • Loading branch information
somasorosdpc authored and adamsaghy committed Sep 18, 2024
1 parent 7b058d6 commit 35c04c0
Show file tree
Hide file tree
Showing 4 changed files with 1,073 additions and 65 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@
import org.apache.commons.lang3.tuple.Pair;
import org.apache.fineract.infrastructure.core.service.DateUtils;
import org.apache.fineract.infrastructure.core.service.MathUtil;
import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil;
import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
import org.apache.fineract.organisation.monetary.domain.Money;
import org.apache.fineract.organisation.monetary.domain.MoneyHelper;
Expand Down Expand Up @@ -180,16 +181,19 @@ public Pair<ChangedTransactionDetail, ProgressiveLoanInterestScheduleModel> repr
ProgressiveLoanInterestScheduleModel scheduleModel = emiCalculator.generateModel(loanProductRelatedDetail,
installmentAmountInMultiplesOf, installments, mc);

ProgressiveTransactionCtx ctx = new ProgressiveTransactionCtx(currency, installments, charges, overpaymentHolder,
changedTransactionDetail, scheduleModel);

for (final ChargeOrTransaction chargeOrTransaction : chargeOrTransactions) {
chargeOrTransaction.getLoanTransaction().ifPresent(loanTransaction -> processSingleTransaction(loanTransaction, currency,
installments, charges, changedTransactionDetail, overpaymentHolder, scheduleModel));
chargeOrTransaction.getLoanTransaction().ifPresent(loanTransaction -> processSingleTransaction(loanTransaction, ctx));
chargeOrTransaction.getLoanCharge()
.ifPresent(loanCharge -> processSingleCharge(loanCharge, currency, installments, disbursementDate));
}
List<LoanTransaction> txs = chargeOrTransactions.stream() //
.map(ChargeOrTransaction::getLoanTransaction) //
.filter(Optional::isPresent) //
.map(Optional::get).toList();
recalculateInterestForDate(ThreadLocalContextUtil.getBusinessDate(), ctx, true);
reprocessInstallments(disbursementDate, txs, installments, currency);
return Pair.of(changedTransactionDetail, scheduleModel);
}
Expand Down Expand Up @@ -562,11 +566,10 @@ protected void handleRefund(LoanTransaction loanTransaction, MonetaryCurrency cu
loanTransaction.updateLoanTransactionToRepaymentScheduleMappings(transactionMappings);
}

private void processSingleTransaction(LoanTransaction loanTransaction, MonetaryCurrency currency,
List<LoanRepaymentScheduleInstallment> installments, Set<LoanCharge> charges, ChangedTransactionDetail changedTransactionDetail,
MoneyHolder overpaymentHolder, ProgressiveLoanInterestScheduleModel scheduleModel) {
TransactionCtx ctx = new ProgressiveTransactionCtx(currency, installments, charges, overpaymentHolder, changedTransactionDetail,
scheduleModel);
private void processSingleTransaction(LoanTransaction loanTransaction, final ProgressiveTransactionCtx ctx) {
final MonetaryCurrency currency = ctx.getCurrency();
final ChangedTransactionDetail changedTransactionDetail = ctx.getChangedTransactionDetail();

if (loanTransaction.getId() == null) {
processLatestTransaction(loanTransaction, ctx);
if (loanTransaction.isInterestWaiver()) {
Expand Down Expand Up @@ -765,7 +768,107 @@ private void allocateOverpayment(LoanTransaction loanTransaction, TransactionCtx
}
}

private List<LoanRepaymentScheduleInstallment> findOverdueInstallmentsBeforeDateSortedByInstallmentNumber(LocalDate currentDate,
ProgressiveTransactionCtx transactionCtx) {
final LocalDate fromDate = transactionCtx.getInstallments().get(0).getLoan().getApprovedOnDate();
return transactionCtx.getInstallments().stream().filter(installment -> !installment.getFromDate().isBefore(fromDate))
.filter(installment -> installment.getDueDate().isBefore(currentDate))
.filter(installment -> installment.isOverdueOn(currentDate))
.sorted(Comparator.comparing(LoanRepaymentScheduleInstallment::getInstallmentNumber)).toList();
}

private void recalculateInterestForDate(LocalDate currentDate, ProgressiveTransactionCtx ctx, boolean isLastRecalculation) {
if (ctx.getInstallments() != null && !ctx.getInstallments().isEmpty()
&& ctx.getInstallments().get(0).getLoan().isInterestRecalculationEnabledForProduct()
&& !ctx.getInstallments().get(0).getLoan().isNpa() && !ctx.getInstallments().get(0).getLoan().isChargedOff()) {
List<LoanRepaymentScheduleInstallment> overdueInstallmentsSortedByInstallmentNumber = findOverdueInstallmentsBeforeDateSortedByInstallmentNumber(
currentDate, ctx);
if (!overdueInstallmentsSortedByInstallmentNumber.isEmpty()) {
List<LoanRepaymentScheduleInstallment> possibleCurrentInstallment = ctx.getInstallments().stream().filter(
installment -> installment.getFromDate().isBefore(currentDate) && !installment.getDueDate().isBefore(currentDate))
.toList();

// get DUE installment or last installment
LoanRepaymentScheduleInstallment currentInstallment = !possibleCurrentInstallment.isEmpty()
? possibleCurrentInstallment.get(0)
: ctx.getInstallments().stream().max(Comparator.comparing(LoanRepaymentScheduleInstallment::getInstallmentNumber))
.orElseThrow();

Money overDuePrincipal = Money.zero(ctx.getCurrency());
for (LoanRepaymentScheduleInstallment processingInstallment : overdueInstallmentsSortedByInstallmentNumber) {
// add and subtract outstanding principal
if (overDuePrincipal.compareTo(Money.zero(ctx.getCurrency())) != 0) {
adjustOverduePrincipalForInstallment(currentDate, isLastRecalculation, processingInstallment, overDuePrincipal,
ctx);
}

overDuePrincipal = overDuePrincipal.add(processingInstallment.getPrincipalOutstanding(ctx.getCurrency()).getAmount());
}
adjustOverduePrincipalForInstallment(currentDate, isLastRecalculation, currentInstallment, overDuePrincipal, ctx);
}
}
}

private void adjustOverduePrincipalForInstallment(LocalDate currentDate, boolean isLastRecalculation,
LoanRepaymentScheduleInstallment currentInstallment, Money overduePrincipal, ProgressiveTransactionCtx ctx) {

LocalDate fromDate = currentInstallment.getFromDate();
boolean hasUpdate = false;

if (currentInstallment.getLoan().getLoanInterestRecalculationDetails().getRestFrequencyType().isSameAsRepayment()) {
// if we have same date for fromDate & last overdue balance change then it meas we have the up-to-date
// model.
if (ctx.getLastOverdueBalanceChange() == null || fromDate.isAfter(ctx.getLastOverdueBalanceChange())) {
emiCalculator.addBalanceCorrection(ctx.getModel(), fromDate, overduePrincipal);
ctx.setLastOverdueBalanceChange(fromDate);
hasUpdate = true;
}
}

if (currentInstallment.getLoan().getLoanInterestRecalculationDetails().getRestFrequencyType().isDaily()
// if we have same date for currentDate & last overdue balance change then it meas we have the
// up-to-date model.
&& !currentDate.equals(ctx.getLastOverdueBalanceChange())) {
if (ctx.getLastOverdueBalanceChange() == null || currentInstallment.getFromDate().isAfter(ctx.getLastOverdueBalanceChange())) {
// first overdue hit for installment. setting overdue balance correction from instalment from date.
emiCalculator.addBalanceCorrection(ctx.getModel(), fromDate, overduePrincipal);
} else {
// not the first balance correction on installment period, then setting overdue balance correction from
// last balance change's current date. previous interest period already has the correct balanec
// correction
emiCalculator.addBalanceCorrection(ctx.getModel(), ctx.getLastOverdueBalanceChange(), overduePrincipal);
}

// setting negative correction for the period from current date, expecting the overdue balance's full
// repayment on that day.
if (currentDate.isAfter(currentInstallment.getFromDate()) && currentDate.isBefore(currentInstallment.getDueDate())) {
emiCalculator.addBalanceCorrection(ctx.getModel(), currentDate, overduePrincipal.negated());
ctx.setLastOverdueBalanceChange(currentDate);
}
hasUpdate = true;
}

if (hasUpdate) {
updateInstallmentsPrincipalAndInterestByModel(ctx);
}
}

private void updateInstallmentsPrincipalAndInterestByModel(ProgressiveTransactionCtx ctx) {
ctx.getModel().repayments().forEach(rm -> {
LoanRepaymentScheduleInstallment installment = ctx.getInstallments().stream()
.filter(ri -> !ri.isDownPayment() && Objects.equals(ri.getFromDate(), rm.getFromDate())).findFirst().orElse(null);
if (installment != null) {
installment.updatePrincipal(rm.getPrincipalDue().getAmount());
installment.updateInterestCharged(rm.getInterestDue().getAmount());
installment.setRecalculatedInterestComponent(true);
}
});
}

private void handleRepayment(LoanTransaction loanTransaction, TransactionCtx transactionCtx) {
if (transactionCtx instanceof ProgressiveTransactionCtx) {
recalculateInterestForDate(loanTransaction.getTransactionDate(), (ProgressiveTransactionCtx) transactionCtx, false);
}
if (loanTransaction.isRepaymentLikeType() || loanTransaction.isInterestWaiver() || loanTransaction.isRecoveryRepayment()) {
loanTransaction.resetDerivedComponents();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,11 @@
*/
package org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.impl;

import java.time.LocalDate;
import java.util.List;
import java.util.Set;
import lombok.Getter;
import lombok.Setter;
import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
import org.apache.fineract.portfolio.loanaccount.domain.ChangedTransactionDetail;
import org.apache.fineract.portfolio.loanaccount.domain.LoanCharge;
Expand All @@ -28,9 +31,12 @@
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.TransactionCtx;
import org.apache.fineract.portfolio.loanaccount.loanschedule.data.ProgressiveLoanInterestScheduleModel;

@Getter
public class ProgressiveTransactionCtx extends TransactionCtx {

private final ProgressiveLoanInterestScheduleModel model;
@Setter
private LocalDate lastOverdueBalanceChange = null;

public ProgressiveTransactionCtx(MonetaryCurrency currency, List<LoanRepaymentScheduleInstallment> installments,
Set<LoanCharge> charges, MoneyHolder overpaymentHolder, ChangedTransactionDetail changedTransactionDetail,
Expand All @@ -39,7 +45,4 @@ public ProgressiveTransactionCtx(MonetaryCurrency currency, List<LoanRepaymentSc
this.model = model;
}

public ProgressiveLoanInterestScheduleModel getModel() {
return model;
}
}
Loading

0 comments on commit 35c04c0

Please sign in to comment.