FINERACT-2398: Re-amortization:- Interest handling-Default behavior
This commit is contained in:
committed by
Adam Saghy
parent
8391614330
commit
3c4d8da1c8
+71
-8
@@ -96,6 +96,7 @@ import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionToRepayme
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.SingleLoanChargeRepaymentScheduleProcessingWrapper;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.reaging.LoanReAgeInterestHandlingType;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.reaging.LoanReAgeParameter;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.reamortization.LoanReAmortizationParameter;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.AbstractLoanRepaymentScheduleTransactionProcessor;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.MoneyHolder;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.TransactionCtx;
|
||||
@@ -590,6 +591,42 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
|
||||
}
|
||||
|
||||
private void handleReAmortization(LoanTransaction loanTransaction, TransactionCtx transactionCtx) {
|
||||
final Loan loan = loanTransaction.getLoan();
|
||||
final LoanReAmortizationParameter loanReAmortizationParameter = loanTransaction.getLoanReAmortizationParameter();
|
||||
if (loan.isInterestBearing()) {
|
||||
switch (loanReAmortizationParameter.getInterestHandlingType()) {
|
||||
case DEFAULT -> handleReamortizationWithDefaultInterestHandlingType(loanTransaction, transactionCtx);
|
||||
case WAIVE_INTEREST -> handleReamortizationWithWaiveInterestHandlingType(loanTransaction, transactionCtx);
|
||||
case EQUAL_AMORTIZATION_INTEREST_SPLIT ->
|
||||
handleReamortizationWithEqualAmortizationInterestSplitHandlingType(loanTransaction, transactionCtx);
|
||||
}
|
||||
} else {
|
||||
reamortizationOnNonInterestBearingLoan(loanTransaction, transactionCtx);
|
||||
}
|
||||
}
|
||||
|
||||
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 handleReamortizationWithWaiveInterestHandlingType(LoanTransaction loanTransaction, TransactionCtx transactionCtx) {
|
||||
throw new UnsupportedOperationException("WAIVE_INTEREST interest handling strategy for re-amortization is not implemented");
|
||||
}
|
||||
|
||||
private void handleReamortizationWithDefaultInterestHandlingType(LoanTransaction loanTransaction, TransactionCtx transactionCtx) {
|
||||
// re-amortization logic for interest-bearing loans
|
||||
if (transactionCtx instanceof ProgressiveTransactionCtx progressiveTransactionCtx
|
||||
&& loanTransaction.getLoan().isInterestBearingAndInterestRecalculationEnabled()) {
|
||||
handleReAmortizationWithInterestRecalculationEnabled(loanTransaction, progressiveTransactionCtx);
|
||||
}
|
||||
// TODO: implement interestRecalculation = false logic
|
||||
throw new UnsupportedOperationException(
|
||||
"Logic for re-amortization when interest bearing loan has interestRecalculation disabled is not implemented");
|
||||
}
|
||||
|
||||
private static void reamortizationOnNonInterestBearingLoan(LoanTransaction loanTransaction, TransactionCtx transactionCtx) {
|
||||
LocalDate transactionDate = loanTransaction.getTransactionDate();
|
||||
List<LoanRepaymentScheduleInstallment> previousInstallments = transactionCtx.getInstallments().stream() //
|
||||
.filter(installment -> !installment.getDueDate().isAfter(transactionDate)) //
|
||||
@@ -634,6 +671,32 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
|
||||
}
|
||||
}
|
||||
|
||||
private void handleReAmortizationWithInterestRecalculationEnabled(final LoanTransaction loanTransaction,
|
||||
final ProgressiveTransactionCtx ctx) {
|
||||
final LocalDate transactionDate = loanTransaction.getTransactionDate();
|
||||
final MonetaryCurrency currency = ctx.getCurrency();
|
||||
|
||||
final List<LoanRepaymentScheduleInstallment> previousInstallments = ctx.getInstallments().stream() //
|
||||
.filter(installment -> !installment.getDueDate().isAfter(transactionDate)) //
|
||||
.toList();
|
||||
|
||||
Money totalOverDuePrincipal = Money.zero(currency);
|
||||
for (LoanRepaymentScheduleInstallment installment : previousInstallments) {
|
||||
final Money principalCompleted = installment.getPrincipalCompleted(currency);
|
||||
totalOverDuePrincipal = totalOverDuePrincipal.add(installment.getPrincipal(currency).minus(principalCompleted));
|
||||
}
|
||||
|
||||
final Money totalOverDueInterest = emiCalculator.getOutstandingInterestTillDate(ctx.getModel(),
|
||||
previousInstallments.getLast().getDueDate());
|
||||
|
||||
loanTransaction.resetDerivedComponents();
|
||||
loanTransaction.updateComponentsAndTotal(totalOverDuePrincipal, totalOverDueInterest, Money.zero(currency), Money.zero(currency));
|
||||
|
||||
// Update the existing model with re-amortized periods
|
||||
emiCalculator.updateModelRepaymentPeriodsDuringReAmortization(ctx.getModel(), transactionDate);
|
||||
updateInstallmentsByRepaymentPeriods(loanTransaction, ctx);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void handleChargeback(LoanTransaction loanTransaction, TransactionCtx ctx) {
|
||||
processCreditTransaction(loanTransaction, ctx);
|
||||
@@ -671,12 +734,12 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
|
||||
if (!currentInstallment.isAdditional() && DateUtils.isAfter(currentInstallment.getDueDate(), transactionDate)) {
|
||||
|
||||
emiCalculator.creditPrincipal(model, transactionDate, transactionAmount);
|
||||
updateRepaymentPeriods(loanTransaction, progressiveTransactionCtx);
|
||||
updateInstallmentsByRepaymentPeriods(loanTransaction, progressiveTransactionCtx);
|
||||
|
||||
if (repaidAmount.isGreaterThanZero()) {
|
||||
emiCalculator.payPrincipal(model, currentInstallment.getFromDate(), currentInstallment.getDueDate(),
|
||||
transactionDate, repaidAmount);
|
||||
updateRepaymentPeriods(loanTransaction, progressiveTransactionCtx);
|
||||
updateInstallmentsByRepaymentPeriods(loanTransaction, progressiveTransactionCtx);
|
||||
currentInstallment.payPrincipalComponent(transactionDate, repaidAmount);
|
||||
transactionMappings.add(LoanTransactionToRepaymentScheduleMapping.createFrom(loanTransaction,
|
||||
currentInstallment, repaidAmount, zeroMoney, zeroMoney, zeroMoney));
|
||||
@@ -709,12 +772,12 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
|
||||
LoanRepaymentScheduleInstallment currentInstallment = installmentToBeProcessed.getLast();
|
||||
|
||||
emiCalculator.creditPrincipal(model, transactionDate, transactionAmount);
|
||||
updateRepaymentPeriods(loanTransaction, progressiveTransactionCtx);
|
||||
updateInstallmentsByRepaymentPeriods(loanTransaction, progressiveTransactionCtx);
|
||||
|
||||
if (repaidAmount.isGreaterThanZero()) {
|
||||
emiCalculator.payPrincipal(model, currentInstallment.getFromDate(), currentInstallment.getDueDate(),
|
||||
transactionDate, repaidAmount);
|
||||
updateRepaymentPeriods(loanTransaction, progressiveTransactionCtx);
|
||||
updateInstallmentsByRepaymentPeriods(loanTransaction, progressiveTransactionCtx);
|
||||
currentInstallment.payPrincipalComponent(transactionDate, repaidAmount);
|
||||
transactionMappings.add(LoanTransactionToRepaymentScheduleMapping.createFrom(loanTransaction,
|
||||
currentInstallment, repaidAmount, zeroMoney, zeroMoney, zeroMoney));
|
||||
@@ -866,7 +929,7 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
|
||||
}
|
||||
|
||||
// update repayment periods until maturity date, for principal and interest portions
|
||||
updateRepaymentPeriods(loanTransaction, ctx);
|
||||
updateInstallmentsByRepaymentPeriods(loanTransaction, ctx);
|
||||
|
||||
// search last instalment if transaction is posted on last installment's due date
|
||||
// otherwise search installment where transaction date is in from date (inclusive) to due date (exclusive)
|
||||
@@ -2589,15 +2652,15 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
|
||||
|
||||
if (PRINCIPAL.equals(paymentAllocationType.getAllocationType())) {
|
||||
emiCalculator.payPrincipal(model, installment.getFromDate(), installment.getDueDate(), payDate, paidPortion);
|
||||
updateRepaymentPeriods(loanTransaction, ctx);
|
||||
updateInstallmentsByRepaymentPeriods(loanTransaction, ctx);
|
||||
} else if (INTEREST.equals(paymentAllocationType.getAllocationType())) {
|
||||
emiCalculator.payInterest(model, installment.getFromDate(), installment.getDueDate(), payDate, paidPortion);
|
||||
updateRepaymentPeriods(loanTransaction, ctx);
|
||||
updateInstallmentsByRepaymentPeriods(loanTransaction, ctx);
|
||||
}
|
||||
return paidPortion;
|
||||
}
|
||||
|
||||
private void updateRepaymentPeriods(LoanTransaction loanTransaction, ProgressiveTransactionCtx ctx) {
|
||||
private void updateInstallmentsByRepaymentPeriods(LoanTransaction loanTransaction, ProgressiveTransactionCtx ctx) {
|
||||
ctx.getModel().repaymentPeriods().forEach(rm -> {
|
||||
LoanRepaymentScheduleInstallment installment = ctx.getInstallments().stream().filter(
|
||||
ri -> ri.getFromDate().equals(rm.getFromDate()) && ri.getDueDate().equals(rm.getDueDate()) && !ri.isDownPayment())
|
||||
|
||||
+2
@@ -170,4 +170,6 @@ public interface EMICalculator {
|
||||
|
||||
void changeDueDate(ProgressiveLoanInterestScheduleModel scheduleModel, LoanApplicationTerms loanApplicationTerms,
|
||||
LocalDate targetRepaymentPeriodDueDate, LocalDate newDueDate);
|
||||
|
||||
void updateModelRepaymentPeriodsDuringReAmortization(ProgressiveLoanInterestScheduleModel model, LocalDate transactionDate);
|
||||
}
|
||||
|
||||
+21
-9
@@ -570,7 +570,7 @@ public final class ProgressiveEMICalculator implements EMICalculator {
|
||||
public void updateModelRepaymentPeriodsDuringReAge(final ProgressiveLoanInterestScheduleModel scheduleModel,
|
||||
final LocalDate reAgePeriodStartDate, final LocalDate reAgeFirstDueDate, final LocalDate targetDate,
|
||||
final LoanApplicationTerms loanApplicationTerms, final MathContext mc) {
|
||||
moveOutstandingAmountsFromPeriodsBeforeReAging(scheduleModel.repaymentPeriods(), targetDate);
|
||||
moveOutstandingAmountsFromPeriodsBeforeTransactionDate(scheduleModel.repaymentPeriods(), targetDate);
|
||||
|
||||
// calculate already paid balances from transaction date
|
||||
final OutstandingDetails paidBalancesFromTransactionDate = calculatePaidBalancesAfterDate(scheduleModel, targetDate);
|
||||
@@ -673,6 +673,18 @@ public final class ProgressiveEMICalculator implements EMICalculator {
|
||||
calculateLastUnpaidRepaymentPeriodEMI(scheduleModel, targetRepaymentPeriodDueDate);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateModelRepaymentPeriodsDuringReAmortization(final ProgressiveLoanInterestScheduleModel model,
|
||||
final LocalDate transactionDate) {
|
||||
moveOutstandingAmountsFromPeriodsBeforeTransactionDate(model.repaymentPeriods(), transactionDate);
|
||||
final List<RepaymentPeriod> reAmortizedPeriods = model.repaymentPeriods().stream()
|
||||
.filter(rp -> rp.getDueDate().isAfter(transactionDate)).toList();
|
||||
calculateEMIOnActualModel(reAmortizedPeriods, model);
|
||||
calculateOutstandingBalance(model);
|
||||
calculateLastUnpaidRepaymentPeriodEMI(model, transactionDate);
|
||||
checkAndAdjustEmiIfNeededOnRelatedRepaymentPeriods(model, reAmortizedPeriods);
|
||||
}
|
||||
|
||||
private LocalDate calculateDateForFixedLength(final ProgressiveLoanInterestScheduleModel scheduleModel,
|
||||
final LoanApplicationTerms loanApplicationTerms, final LocalDate targetRepaymentPeriodDueDate, final LocalDate newDueDate) {
|
||||
final long variationDays = DateUtils.getDifferenceInDays(targetRepaymentPeriodDueDate, newDueDate);
|
||||
@@ -825,16 +837,16 @@ public final class ProgressiveEMICalculator implements EMICalculator {
|
||||
}
|
||||
|
||||
/**
|
||||
* * Zeroing out the EMI of the repayment periods, that are before re-aging and not been fully paid. And decreases
|
||||
* the balance correction amount (added during interest recalculation for the business date) by the amount of the
|
||||
* principal that was moved.
|
||||
* * Zeroing out the EMI of the repayment periods, that are before the transaction date and not been fully paid. And
|
||||
* decreases the balance correction amount (added during interest recalculation for the business date) by the amount
|
||||
* of the principal that was moved.
|
||||
*/
|
||||
private static void moveOutstandingAmountsFromPeriodsBeforeReAging(final List<RepaymentPeriod> existingRepaymentPeriods,
|
||||
private static void moveOutstandingAmountsFromPeriodsBeforeTransactionDate(final List<RepaymentPeriod> existingRepaymentPeriods,
|
||||
final LocalDate transactionDate) {
|
||||
final List<RepaymentPeriod> periodsBeforeReAging = existingRepaymentPeriods.stream()
|
||||
final List<RepaymentPeriod> periodsBeforeTransactionDate = existingRepaymentPeriods.stream()
|
||||
.filter(rp -> !rp.getFromDate().isAfter(transactionDate) && !rp.isFullyPaid()).toList();
|
||||
|
||||
periodsBeforeReAging.forEach(rp -> {
|
||||
periodsBeforeTransactionDate.forEach(rp -> {
|
||||
rp.getInterestPeriods().stream().filter(ip -> ip.getDueDate().isEqual(transactionDate)).forEach(ip -> {
|
||||
if (!ip.getBalanceCorrectionAmount().isZero()) {
|
||||
ip.addBalanceCorrectionAmount(ip.getBalanceCorrectionAmount().negated());
|
||||
@@ -845,7 +857,7 @@ public final class ProgressiveEMICalculator implements EMICalculator {
|
||||
lastInterestPeriod.addBalanceCorrectionAmount(rp.getOutstandingPrincipal().negated());
|
||||
}
|
||||
rp.setEmi(rp.getTotalPaidAmount());
|
||||
rp.setOutstandingMovedDueToReAging(true);
|
||||
rp.setOutstandingMoved(true);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1725,7 +1737,7 @@ public final class ProgressiveEMICalculator implements EMICalculator {
|
||||
rp.getInterestPeriods().getLast()
|
||||
.addCreditedInterestAmount(MathUtil.min(rp.getOutstandingInterest(), rp.getCreditedInterest(), false).negated());
|
||||
rp.setEmi(rp.getTotalPaidAmount());
|
||||
rp.setOutstandingMovedDueToReAging(true);
|
||||
rp.setOutstandingMoved(true);
|
||||
});
|
||||
|
||||
// stop calculate unrecognised interest at this point because all
|
||||
|
||||
+4
-5
@@ -91,7 +91,7 @@ public class RepaymentPeriod {
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
private boolean isOutstandingMovedDueToReAging = false;
|
||||
private boolean isOutstandingMoved = false;
|
||||
|
||||
@Setter
|
||||
@Getter
|
||||
@@ -148,7 +148,7 @@ public class RepaymentPeriod {
|
||||
repaymentPeriod.getPaidPrincipal(), repaymentPeriod.getPaidInterest(), repaymentPeriod.getFutureUnrecognizedInterest(), mc,
|
||||
repaymentPeriod.getLoanProductRelatedDetail(), repaymentPeriod.isNoUnrecognisedInterest(), repaymentPeriod.isReAged(),
|
||||
repaymentPeriod.isReAgedEarlyRepaymentHolder(), repaymentPeriod.getReAgedInterest());
|
||||
newRepaymentPeriod.setOutstandingMovedDueToReAging(repaymentPeriod.isOutstandingMovedDueToReAging());
|
||||
newRepaymentPeriod.setOutstandingMoved(repaymentPeriod.isOutstandingMoved());
|
||||
// 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));
|
||||
@@ -162,7 +162,7 @@ public class RepaymentPeriod {
|
||||
repaymentPeriod.getDueDate(), new ArrayList<>(), repaymentPeriod.getEmi(), repaymentPeriod.getOriginalEmi(), zero, zero,
|
||||
zero, mc, repaymentPeriod.getLoanProductRelatedDetail(), repaymentPeriod.isNoUnrecognisedInterest(),
|
||||
repaymentPeriod.isReAged(), repaymentPeriod.isReAgedEarlyRepaymentHolder(), repaymentPeriod.getReAgedInterest());
|
||||
newRepaymentPeriod.setOutstandingMovedDueToReAging(repaymentPeriod.isOutstandingMovedDueToReAging());
|
||||
newRepaymentPeriod.setOutstandingMoved(repaymentPeriod.isOutstandingMoved());
|
||||
// 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);
|
||||
@@ -311,8 +311,7 @@ public class RepaymentPeriod {
|
||||
* @return
|
||||
*/
|
||||
public Money getTotalCreditedAmount() {
|
||||
return isOutstandingMovedDueToReAging ? Money.zero(getCurrency(), getMc())
|
||||
: getCreditedPrincipal().plus(getCreditedInterest(), getMc());
|
||||
return isOutstandingMoved ? Money.zero(getCurrency(), getMc()) : getCreditedPrincipal().plus(getCreditedInterest(), getMc());
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user