Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -1512,7 +1512,6 @@ private List<ChangeOperation> createSortedChangeList(final List<LoanTermVariatio
}

private void handleDisbursement(LoanTransaction disbursementTransaction, TransactionCtx transactionCtx) {
// TODO: Fix this and enhance EMICalculator to support reamortization and reaging
if (shouldUseEmiCalculation(transactionCtx, disbursementTransaction.getTransactionDate())) {
handleDisbursementWithEMICalculator(disbursementTransaction, transactionCtx);
} else {
Expand Down Expand Up @@ -1644,7 +1643,6 @@ private void handleDisbursementWithoutEMICalculator(LoanTransaction disbursement
}

private void handleCapitalizedIncome(LoanTransaction capitalizedIncomeTransaction, TransactionCtx transactionCtx) {
// TODO: Fix this and enhance EMICalculator to support reamortization and reaging
if (shouldUseEmiCalculation(transactionCtx, capitalizedIncomeTransaction.getTransactionDate())) {
handleCapitalizedIncomeWithEMICalculator(capitalizedIncomeTransaction, transactionCtx);
} else {
Expand Down Expand Up @@ -1767,19 +1765,18 @@ private void allocateOverpayment(LoanTransaction loanTransaction, TransactionCtx

private boolean shouldUseEmiCalculation(TransactionCtx transactionCtx, LocalDate transactionDate) {
if (transactionCtx instanceof ProgressiveTransactionCtx progressiveTransactionCtx) {
boolean hasActiveReAmortization = progressiveTransactionCtx.getAlreadyProcessedTransactions().stream()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you explain the removal of this logic? hasActiveReAmortization. Even through tests wise code is ok.

Interested in usecase related point of view. why re amortize logic is being removed here/

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It was removed, because now EmiCalculator is supporting reamortization handling.
To be honest i think the reage part should be also removed. We should only not use emicalculator if current date is after maturity date. @oleksii-novikov-onix FYI

.anyMatch(t -> t.getTypeOf().isReAmortize() && t.isNotReversed());
boolean hasActiveReAge = progressiveTransactionCtx.getAlreadyProcessedTransactions().stream()
.anyMatch(t -> t.getTypeOf().isReAge() && t.isNotReversed());
if (hasActiveReAmortization) {
return false;
} else {
return !hasActiveReAge || !DateUtils.isAfter(transactionDate, progressiveTransactionCtx.getModel().getMaturityDate());
final Loan loan = progressiveTransactionCtx.getInstallments().getFirst().getLoan();

if (!loan.isInterestBearing()) {
boolean hasActiveReAmortization = progressiveTransactionCtx.getAlreadyProcessedTransactions().stream()
.anyMatch(t -> t.getTypeOf().isReAmortize() && t.isNotReversed());
if (hasActiveReAmortization) {
return false;
}
}

return !DateUtils.isAfter(transactionDate, progressiveTransactionCtx.getModel().getMaturityDate());
}
// From now on we are defaulting to using the EMICalculator on all progressive loans. However currently the
// model is not aware of re-aging and re-amortization. So only these specific cases should ignore this
// requirement. This method can be removed once these operations are supported by the EMI model.
return true;
}

Expand Down Expand Up @@ -1999,17 +1996,39 @@ private void handleAccelerateMaturityDate(final LoanTransaction loanTransaction,
final LocalDate transactionDate = loanTransaction.getTransactionDate();
final List<LoanRepaymentScheduleInstallment> installments = transactionCtx.getInstallments();
final Loan loan = loanTransaction.getLoan();
final LoanRepaymentScheduleInstallment currentInstallment = loan.getRelatedRepaymentScheduleInstallment(transactionDate);

final boolean isReAmortizedLoan = transactionCtx instanceof ProgressiveTransactionCtx ptx
&& ptx.getModel().repaymentPeriods().stream().anyMatch(RepaymentPeriod::isReAmortized);
final LoanRepaymentScheduleInstallment currentInstallment = isReAmortizedLoan
? installments.stream().filter(LoanRepaymentScheduleInstallment::isNotFullyPaidOff).findFirst().orElse(null)
: loan.getRelatedRepaymentScheduleInstallment(transactionDate);

if (!installments.isEmpty() && transactionDate.isBefore(loan.getMaturityDate()) && currentInstallment != null) {
final List<LoanRepaymentScheduleInstallment> reAmortizedFutureInstallments = isReAmortizedLoan
? installments.stream().filter(i -> i.getFromDate().isAfter(currentInstallment.getFromDate())).toList()
: null;

if (currentInstallment.isNotFullyPaidOff()) {
if (transactionCtx instanceof ProgressiveTransactionCtx progressiveTransactionCtx
&& loan.isInterestBearingAndInterestRecalculationEnabled()) {
final BigDecimal interestOutstanding = currentInstallment.getInterestOutstanding(loan.getCurrency()).getAmount();
final BigDecimal newInterest = emiCalculator.getPeriodInterestTillDate(progressiveTransactionCtx.getModel(),
currentInstallment.getFromDate(), currentInstallment.getDueDate(), transactionDate, true).getAmount();
if (interestOutstanding.compareTo(BigDecimal.ZERO) > 0 || newInterest.compareTo(BigDecimal.ZERO) > 0) {
currentInstallment.updateInterestCharged(newInterest);
if (isReAmortizedLoan) {
BigDecimal totalInterest = emiCalculator.getPeriodInterestTillDate(progressiveTransactionCtx.getModel(),
currentInstallment.getFromDate(), currentInstallment.getDueDate(), transactionDate, true).getAmount();
for (LoanRepaymentScheduleInstallment futureInst : reAmortizedFutureInstallments) {
if (!futureInst.getFromDate().isAfter(transactionDate)) {
totalInterest = totalInterest
.add(emiCalculator.getPeriodInterestTillDate(progressiveTransactionCtx.getModel(),
futureInst.getFromDate(), futureInst.getDueDate(), transactionDate, true).getAmount());
}
}
currentInstallment.updateInterestCharged(totalInterest);
} else {
final BigDecimal interestOutstanding = currentInstallment.getInterestOutstanding(loan.getCurrency()).getAmount();
final BigDecimal newInterest = emiCalculator.getPeriodInterestTillDate(progressiveTransactionCtx.getModel(),
currentInstallment.getFromDate(), currentInstallment.getDueDate(), transactionDate, true).getAmount();
if (interestOutstanding.compareTo(BigDecimal.ZERO) > 0 || newInterest.compareTo(BigDecimal.ZERO) > 0) {
currentInstallment.updateInterestCharged(newInterest);
}
}
} else {
final BigDecimal totalInterest = currentInstallment.getInterestOutstanding(transactionCtx.getCurrency()).getAmount();
Expand All @@ -2029,8 +2048,8 @@ private void handleAccelerateMaturityDate(final LoanTransaction loanTransaction,

currentInstallment.updateDueDate(transactionDate);

final List<LoanRepaymentScheduleInstallment> futureInstallments = installments.stream()
.filter(installment -> transactionDate.isBefore(installment.getDueDate())).toList();
final List<LoanRepaymentScheduleInstallment> futureInstallments = isReAmortizedLoan ? reAmortizedFutureInstallments
: installments.stream().filter(installment -> transactionDate.isBefore(installment.getDueDate())).toList();

final BigDecimal futurePrincipal = futureInstallments.stream().map(LoanRepaymentScheduleInstallment::getPrincipal)
.filter(Objects::nonNull).reduce(BigDecimal.ZERO, BigDecimal::add);
Expand Down Expand Up @@ -2058,8 +2077,9 @@ private void handleAccelerateMaturityDate(final LoanTransaction loanTransaction,
MathUtil.nullToZero(currentInstallment.getTotalPaidInAdvance()).add(futureTotalPaidInAdvance));
}

final List<LoanRepaymentScheduleInstallment> installmentsUpToTransactionDate = loan
.getInstallmentsUpToTransactionDate(transactionDate);
final List<LoanRepaymentScheduleInstallment> installmentsUpToTransactionDate = isReAmortizedLoan ? installments.stream()
.filter(i -> i.isObligationsMet() || i.equals(currentInstallment)).collect(Collectors.toCollection(ArrayList::new))
: loan.getInstallmentsUpToTransactionDate(transactionDate);

final List<LoanTransaction> transactionsToBeReprocessed = loan.getLoanTransactions().stream()
.filter(transaction -> transaction.getTransactionDate().isBefore(transactionDate))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -796,7 +796,7 @@ private LocalDate calculateDateForFixedLength(final ProgressiveLoanInterestSched
private List<RepaymentPeriod> findPossiblyOverdueRepaymentPeriods(final LocalDate targetDate,
final ProgressiveLoanInterestScheduleModel model) {
return model.repaymentPeriods().stream() //
.filter(repaymentPeriod -> !repaymentPeriod.isReAgedEarlyRepaymentHolder()
.filter(repaymentPeriod -> !repaymentPeriod.isReAgedEarlyRepaymentHolder() && !repaymentPeriod.isReAmortized()
&& DateUtils.isAfter(targetDate, repaymentPeriod.getDueDate()))
.toList();
}
Expand Down Expand Up @@ -951,6 +951,7 @@ private static void moveOutstandingAmountsFromPeriodsBeforeTransactionDate(final
}
rp.setEmi(rp.getTotalPaidAmount());
rp.moveOutstandingDueToReAging();
rp.setReAmortized(true);
});
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,7 @@ public Optional<RepaymentPeriod> updateInterestPeriodsForInterestPause(final Loc
}

final List<RepaymentPeriod> affectedPeriods = repaymentPeriods.stream()//
.filter(period -> !period.isReAmortized())//
.filter(period -> period.getFromDate().isBefore(endDate) && !period.getDueDate().isBefore(fromDate))//
.toList();
affectedPeriods.forEach(period -> insertInterestPausePeriods(period, fromDate, endDate));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,9 @@ public class RepaymentPeriod {
@Setter
@Getter
private boolean reAgedEarlyRepaymentHolder;
@Setter
@Getter
private boolean reAmortized;
@Getter
@Setter
private Money reAgedInterest;
Expand Down Expand Up @@ -159,6 +162,7 @@ public static RepaymentPeriod copy(RepaymentPeriod previous, RepaymentPeriod rep
newRepaymentPeriod.setTotalCapitalizedIncomeAmount(repaymentPeriod.getTotalCapitalizedIncomeAmount());
newRepaymentPeriod.setInterestMoved(repaymentPeriod.isInterestMoved());
newRepaymentPeriod.setCurrency(repaymentPeriod.getCurrency());
newRepaymentPeriod.setReAmortized(repaymentPeriod.isReAmortized());
// 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));
Expand All @@ -178,6 +182,7 @@ public static RepaymentPeriod copyWithoutPaidAmounts(RepaymentPeriod previous, R
newRepaymentPeriod.setTotalCapitalizedIncomeAmount(repaymentPeriod.getTotalCapitalizedIncomeAmount());
newRepaymentPeriod.setInterestMoved(repaymentPeriod.isInterestMoved());
newRepaymentPeriod.setCurrency(repaymentPeriod.getCurrency());
newRepaymentPeriod.setReAmortized(repaymentPeriod.isReAmortized());
// 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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -538,6 +538,7 @@ public void testDisbursementAfterMaturityDateWithEMICalculator() {

Loan loan = mock(Loan.class);
when(loan.getLoanRepaymentScheduleDetail()).thenReturn(loanProductRelatedDetail);
when(loan.isInterestBearing()).thenReturn(true);

LoanRepaymentScheduleInstallment installment1 = spy(
new LoanRepaymentScheduleInstallment(loan, 1, disbursementDate, disbursementDate.plusMonths(1), BigDecimal.valueOf(200.0),
Expand Down Expand Up @@ -566,6 +567,7 @@ public void testDisbursementAfterMaturityDateWithEMICalculator() {
Mockito.doNothing().when(loan).addLoanRepaymentScheduleInstallment(installmentCaptor.capture());

ProgressiveLoanInterestScheduleModel model = mock(ProgressiveLoanInterestScheduleModel.class);
when(model.getMaturityDate()).thenReturn(LocalDate.of(2023, 12, 31));

TransactionCtx ctx = new ProgressiveTransactionCtx(currency, spyInstallments, Set.of(), new MoneyHolder(Money.zero(currency)),
mock(ChangedTransactionDetail.class), model, Money.zero(currency));
Expand Down
Loading