Skip to content

Commit e7ae6e5

Browse files
committed
FINERACT-2354: Re-aging: -Interest Handling Option: Equal amortization -- EMICalculator
1 parent a462ad2 commit e7ae6e5

4 files changed

Lines changed: 83 additions & 19 deletions

File tree

fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/AdvancedPaymentScheduleTransactionProcessor.java

Lines changed: 60 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2966,11 +2966,15 @@ private void handleReAge(LoanTransaction loanTransaction, TransactionCtx ctx) {
29662966
} else {
29672967
if (LoanReAgeInterestHandlingType.EQUAL_AMORTIZATION_FULL_INTEREST.equals(loanReAgeParameter.getInterestHandlingType())) {
29682968
CommonReAgeSettings settings = new CommonReAgeSettings(false, true, true, true, true);
2969-
handleReAgeWithCommonStrategy(loanTransaction, settings, ctx);
2969+
if (ctx instanceof ProgressiveTransactionCtx progressiveTransactionCtx) {
2970+
handleReAgeEqualAmortizationEMICalculator(loanTransaction, settings, progressiveTransactionCtx);
2971+
}
29702972
} else if (LoanReAgeInterestHandlingType.EQUAL_AMORTIZATION_PAYABLE_INTEREST
29712973
.equals(loanReAgeParameter.getInterestHandlingType())) {
29722974
CommonReAgeSettings settings = new CommonReAgeSettings(true, true, true, true, true);
2973-
handleReAgeWithCommonStrategy(loanTransaction, settings, ctx);
2975+
if (ctx instanceof ProgressiveTransactionCtx progressiveTransactionCtx) {
2976+
handleReAgeEqualAmortizationEMICalculator(loanTransaction, settings, progressiveTransactionCtx);
2977+
}
29742978
}
29752979
}
29762980
} else {
@@ -3284,14 +3288,65 @@ private static class HorizontalPaymentAllocationContext implements LoopContext {
32843288
private boolean isInterestRecalculationSupported(TransactionCtx ctx, Loan loan) {
32853289
if (ctx instanceof ProgressiveTransactionCtx progressiveTransactionCtx) {
32863290
return loan.isInterestBearingAndInterestRecalculationEnabled() && !progressiveTransactionCtx.isChargedOff()
3287-
&& !progressiveTransactionCtx.isWrittenOff() && !progressiveTransactionCtx.isContractTerminated()
3288-
&& keepUsingEmiCalculatorBasedOnReAge(progressiveTransactionCtx);
3291+
&& !progressiveTransactionCtx.isWrittenOff() && !progressiveTransactionCtx.isContractTerminated();
32893292
} else {
32903293
return false;
32913294
}
32923295
}
32933296

3294-
private void handleReAgeWithCommonStrategy(LoanTransaction loanTransaction, CommonReAgeSettings settings, TransactionCtx ctx) {
3297+
private void handleReAgeEqualAmortizationEMICalculator(LoanTransaction loanTransaction, CommonReAgeSettings settings, ProgressiveTransactionCtx ctx) {
3298+
ProgressiveLoanInterestScheduleModel model = ctx.getModel();
3299+
MonetaryCurrency currency = ctx.getCurrency();
3300+
3301+
OutstandingDetails outstandingDetails = emiCalculator.precalculateReAgeEqualAmortizationAmount(model, loanTransaction.getTransactionDate(), loanTransaction.getLoanReAgeParameter());
3302+
3303+
loanTransaction.updateComponentsAndTotal(outstandingDetails.getOutstandingPrincipal(), outstandingDetails.getOutstandingInterest(), model.zero(), model.zero());
3304+
3305+
if (loanTransaction.getAmount().compareTo(ZERO) == 0) {
3306+
loanTransaction.reverse();
3307+
}
3308+
3309+
emiCalculator.reAgeEqualAmortization(model, loanTransaction.getTransactionDate(), loanTransaction.getLoanReAgeParameter());
3310+
3311+
loanTransaction.getLoan().getRepaymentScheduleInstallments().removeIf(i-> i.getInstallmentNumber() != null && !i.getDueDate().isBefore(loanTransaction.getTransactionDate()) && (!i.getDueDate().isAfter(model.getMaturityDate()) || !i.isAdditional() ));
3312+
3313+
loanTransaction.getLoan().getRepaymentScheduleInstallments().stream()
3314+
.filter(LoanRepaymentScheduleInstallment::isAdditional)
3315+
.forEach(i -> {
3316+
i.setFromDate(model.getMaturityDate());
3317+
i.setInstallmentNumber(model.repaymentPeriods().size());
3318+
});
3319+
3320+
for (int index= 0; index < model.repaymentPeriods().size(); index++) {
3321+
RepaymentPeriod rp = model.repaymentPeriods().get(index);
3322+
if (rp.getDueDate().isBefore(loanTransaction.getTransactionDate())) {
3323+
// update existing
3324+
Optional<LoanRepaymentScheduleInstallment> notReagedInstallment = ctx.getInstallments().stream().filter(i -> i.getDueDate().isEqual(rp.getDueDate()) && i.getFromDate().isEqual(rp.getFromDate())).findFirst();
3325+
LoanRepaymentScheduleInstallment installment = notReagedInstallment.orElseThrow();
3326+
installment.setInterestCharged(rp.getDueInterest().getAmount());
3327+
installment.setInterestPaid(rp.getPaidInterest().getAmount());
3328+
installment.setPrincipal(rp.getDuePrincipal().getAmount());
3329+
installment.setPrincipalCompleted(rp.getPaidPrincipal().getAmount());
3330+
installment.updateObligationsMet(currency, loanTransaction.getTransactionDate());
3331+
installment.setInstallmentNumber(index+1);
3332+
// TODO add remaining components
3333+
} else {
3334+
LoanRepaymentScheduleInstallment created = LoanRepaymentScheduleInstallment.newReAgedInstallment(loanTransaction.getLoan(), index+1, rp.getFromDate(), rp.getDueDate(), rp.getDuePrincipal().getAmount(), rp.getDueInterest().getAmount(), ZERO, ZERO);
3335+
created.setPrincipalCompleted(rp.getPaidPrincipal().getAmount());
3336+
created.setInterestPaid(rp.getPaidInterest().getAmount());
3337+
created.updateObligationsMet(currency, loanTransaction.getTransactionDate());
3338+
if (!rp.isReAgedEarlyRepaymentHolder()) {
3339+
// TODO add remainingComponents
3340+
} else {
3341+
// TODO add remaining earlyRepaidCompionents
3342+
}
3343+
loanTransaction.getLoan().getRepaymentScheduleInstallments().add(created);
3344+
}
3345+
}
3346+
}
3347+
3348+
3349+
private void handleReAgeWithCommonStrategy(LoanTransaction loanTransaction, CommonReAgeSettings settings, TransactionCtx ctx) {
32953350
MonetaryCurrency currency = ctx.getCurrency();
32963351
Loan loan = loanTransaction.getLoan();
32973352
List<LoanRepaymentScheduleInstallment> installments = ctx.getInstallments();

fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/calc/ProgressiveEMICalculator.java

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -420,7 +420,7 @@ public Money getPeriodInterestTillDate(@NotNull ProgressiveLoanInterestScheduleM
420420
targetDate);
421421
RepaymentPeriod repaymentPeriod = recalculatedScheduleModelTillDate.findRepaymentPeriodByDueDate(periodDueDate).orElseThrow();
422422
return includeCreditedInterest ? repaymentPeriod.getCalculatedDueInterest()
423-
: repaymentPeriod.getCalculatedDueInterest().minus(repaymentPeriod.getCreditedInterest(),
423+
: repaymentPeriod.getCalculatedDueInterest().minus(repaymentPeriod.getReAgedInterest(), recalculatedScheduleModelTillDate.mc()).minus(repaymentPeriod.getCreditedInterest(),
424424
recalculatedScheduleModelTillDate.mc());
425425
}
426426

@@ -1519,12 +1519,15 @@ private void updateEMIForReAgeEqualAmortization(List<RepaymentPeriod> repaymentP
15191519
Money interestAdjustment = interest.minus(interestPortion.multipliedBy(repaymentPeriods.size()));
15201520
RepaymentPeriod last = repaymentPeriods.getLast();
15211521
repaymentPeriods.forEach(rp -> {
1522-
rp.getLastInterestPeriod().addCreditedInterestAmount(interestPortion);
1522+
rp.setReAgedInterest(interestPortion);
15231523
if (last == rp) {
1524-
rp.getLastInterestPeriod().addCreditedInterestAmount(interestAdjustment);
1525-
rp.setEmi(principalPortion.add(principalAdjustment));
1524+
rp.setReAgedInterest(interestPortion.add(interestAdjustment));
1525+
Money newEmi = principalPortion.add(principalAdjustment);
1526+
rp.setEmi(newEmi);
1527+
rp.setOriginalEmi(newEmi);
15261528
} else {
15271529
rp.setEmi(principalPortion);
1530+
rp.setOriginalEmi(principalPortion);
15281531
}
15291532
});
15301533
}

fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/calc/data/RepaymentPeriod.java

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -96,10 +96,13 @@ public class RepaymentPeriod {
9696
@Setter
9797
@Getter
9898
private boolean reAgedEarlyRepaymentHolder = false;
99+
@Getter
100+
@Setter
101+
private Money reAgedInterest;
99102

100103
protected RepaymentPeriod(RepaymentPeriod previous, LocalDate fromDate, LocalDate dueDate, List<InterestPeriod> interestPeriods,
101104
Money emi, Money originalEmi, Money paidPrincipal, Money paidInterest, Money futureUnrecognizedInterest, MathContext mc,
102-
LoanProductMinimumRepaymentScheduleRelatedDetail loanProductRelatedDetail, boolean noUnrecognisedInterest, boolean reAged, boolean reAgedEarlyRepaymentHolder) {
105+
LoanProductMinimumRepaymentScheduleRelatedDetail loanProductRelatedDetail, boolean noUnrecognisedInterest, boolean reAged, boolean reAgedEarlyRepaymentHolder, Money reAgedInterest) {
103106
this.previous = previous;
104107
this.fromDate = fromDate;
105108
this.dueDate = dueDate;
@@ -114,19 +117,20 @@ protected RepaymentPeriod(RepaymentPeriod previous, LocalDate fromDate, LocalDat
114117
this.noUnrecognisedInterest = noUnrecognisedInterest;
115118
this.reAged = reAged;
116119
this.reAgedEarlyRepaymentHolder = reAgedEarlyRepaymentHolder;
120+
this.reAgedInterest = reAgedInterest;
117121
}
118122

119123
public static RepaymentPeriod empty(RepaymentPeriod previous, MathContext mc,
120124
LoanProductMinimumRepaymentScheduleRelatedDetail loanProductRelatedDetail) {
121125
return new RepaymentPeriod(previous, null, null, new ArrayList<>(), null, null, null, null, null, mc, loanProductRelatedDetail,
122-
false, false, false);
126+
false, false, false, null);
123127
}
124128

125129
public static RepaymentPeriod create(RepaymentPeriod previous, LocalDate fromDate, LocalDate dueDate, Money emi, MathContext mc,
126130
LoanProductMinimumRepaymentScheduleRelatedDetail loanProductRelatedDetail) {
127131
final Money zero = emi.zero();
128132
final RepaymentPeriod newRepaymentPeriod = new RepaymentPeriod(previous, fromDate, dueDate, new ArrayList<>(), emi, emi, zero, zero,
129-
zero, mc, loanProductRelatedDetail, false, false, false);
133+
zero, mc, loanProductRelatedDetail, false, false, false, zero);
130134
// There is always at least 1 interest period, by default with same from-due date as repayment period
131135
newRepaymentPeriod.getInterestPeriods().add(InterestPeriod.withEmptyAmounts(newRepaymentPeriod, fromDate, dueDate));
132136
return newRepaymentPeriod;
@@ -136,7 +140,7 @@ public static RepaymentPeriod copy(RepaymentPeriod previous, RepaymentPeriod rep
136140
final RepaymentPeriod newRepaymentPeriod = new RepaymentPeriod(previous, repaymentPeriod.getFromDate(),
137141
repaymentPeriod.getDueDate(), new ArrayList<>(), repaymentPeriod.getEmi(), repaymentPeriod.getOriginalEmi(),
138142
repaymentPeriod.getPaidPrincipal(), repaymentPeriod.getPaidInterest(), repaymentPeriod.getFutureUnrecognizedInterest(), mc,
139-
repaymentPeriod.getLoanProductRelatedDetail(), repaymentPeriod.isNoUnrecognisedInterest(), repaymentPeriod.isReAged(), repaymentPeriod.isReAgedEarlyRepaymentHolder());
143+
repaymentPeriod.getLoanProductRelatedDetail(), repaymentPeriod.isNoUnrecognisedInterest(), repaymentPeriod.isReAged(), repaymentPeriod.isReAgedEarlyRepaymentHolder(), repaymentPeriod.getReAgedInterest());
140144
// There is always at least 1 interest period, by default with same from-due date as repayment period
141145
for (InterestPeriod interestPeriod : repaymentPeriod.getInterestPeriods()) {
142146
newRepaymentPeriod.getInterestPeriods().add(InterestPeriod.copy(newRepaymentPeriod, interestPeriod, mc));
@@ -148,7 +152,7 @@ public static RepaymentPeriod copyWithoutPaidAmounts(RepaymentPeriod previous, R
148152
final Money zero = Money.zero(repaymentPeriod.getCurrency(), mc);
149153
final RepaymentPeriod newRepaymentPeriod = new RepaymentPeriod(previous, repaymentPeriod.getFromDate(),
150154
repaymentPeriod.getDueDate(), new ArrayList<>(), repaymentPeriod.getEmi(), repaymentPeriod.getOriginalEmi(), zero, zero,
151-
zero, mc, repaymentPeriod.getLoanProductRelatedDetail(), repaymentPeriod.isNoUnrecognisedInterest(), repaymentPeriod.isReAged(), repaymentPeriod.isReAgedEarlyRepaymentHolder());
155+
zero, mc, repaymentPeriod.getLoanProductRelatedDetail(), repaymentPeriod.isNoUnrecognisedInterest(), repaymentPeriod.isReAged(), repaymentPeriod.isReAgedEarlyRepaymentHolder(), repaymentPeriod.getReAgedInterest());
152156
// There is always at least 1 interest period, by default with same from-due date as repayment period
153157
for (InterestPeriod interestPeriod : repaymentPeriod.getInterestPeriods()) {
154158
var interestPeriodCopy = InterestPeriod.copy(newRepaymentPeriod, interestPeriod);
@@ -188,8 +192,8 @@ private BigDecimal calculateRateFactorPlus1() {
188192
@NotNull
189193
public Money getCalculatedDueInterest() {
190194
if (calculatedDueInterestCalculation == null) {
191-
calculatedDueInterestCalculation = Memo.of(this::calculateCalculatedDueInterest, () -> new Object[] { this.previous,
192-
this.interestPeriods, this.futureUnrecognizedInterest, this.isInterestMoved, this.totalDisbursedAmount });
195+
calculatedDueInterestCalculation = Memo.of(this::calculateCalculatedDueInterest, () -> new Object[] { previous,
196+
interestPeriods, futureUnrecognizedInterest, isInterestMoved, totalDisbursedAmount , reAgedInterest, reAged});
193197
}
194198
return calculatedDueInterestCalculation.get();
195199
}
@@ -201,6 +205,7 @@ public Money calculateCalculatedDueInterest() {
201205
getInterestPeriods().stream().map(InterestPeriod::getCalculatedDueInterest).reduce(BigDecimal.ZERO, BigDecimal::add),
202206
mc);
203207
}
208+
calculatedDueInterest = calculatedDueInterest.add(reAgedInterest);
204209
calculatedDueInterest = calculatedDueInterest.add(getFutureUnrecognizedInterest(), getMc());
205210
if (getPrevious().isPresent()) {
206211
calculatedDueInterest = calculatedDueInterest.add(getPrevious().get().getUnrecognizedInterest(), getMc());
@@ -220,7 +225,7 @@ public Money getDueInterest() {
220225
() -> MathUtil.max(getPaidPrincipal().isGreaterThan(getCalculatedDuePrincipal()) ? getPaidInterest()
221226
: MathUtil.min(getCalculatedDueInterest(), getEmiPlusCreditedAmountsPlusFutureUnrecognizedInterest(), false),
222227
getPaidInterest(), false),
223-
() -> new Object[] { paidPrincipal, paidInterest, interestPeriods, futureUnrecognizedInterest, totalDisbursedAmount,
228+
() -> new Object[] { paidPrincipal, paidInterest, interestPeriods, futureUnrecognizedInterest, totalDisbursedAmount, reAgedInterest, reAged,
224229
emi });
225230
}
226231
return dueInterestCalculation.get();
@@ -296,7 +301,7 @@ public Money getDuePrincipal() {
296301
* @return
297302
*/
298303
public Money getTotalCreditedAmount() {
299-
return getCreditedPrincipal().plus(getCreditedInterest(), getMc());
304+
return getCreditedPrincipal().plus(getCreditedInterest(), getMc()).plus(getReAgedInterest());
300305
}
301306

302307
/**

fineract-progressive-loan/src/test/java/org/apache/fineract/portfolio/loanproduct/calc/data/RepaymentPeriodTest.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,8 @@ void testEmptyRepaymentPeriod() {
125125
loanProductRelatedDetail, //
126126
false, // noUnrecognizedInterest
127127
false, // reAged
128-
false // reAgedEarlyRepaymentHolder
128+
false, // reAgedEarlyRepaymentHolder
129+
null // reAgedInterest
129130
);
130131

131132
// Test that getters don't throw and return non-null

0 commit comments

Comments
 (0)