Skip to content

Commit a462ad2

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

6 files changed

Lines changed: 103 additions & 11 deletions

File tree

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,9 @@ Money getPeriodInterestTillDate(@NotNull ProgressiveLoanInterestScheduleModel sc
147147
void updateModelRepaymentPeriodsDuringReAge(ProgressiveLoanInterestScheduleModel scheduleModel, LoanTransaction loanTransaction,
148148
LoanApplicationTerms loanApplicationTerms, MathContext mc);
149149

150+
OutstandingDetails precalculateReAgeEqualAmortizationAmount(ProgressiveLoanInterestScheduleModel interestSchedule, LocalDate transactionDate,
151+
LoanReAgeParameter reageParameter);
152+
150153
void reAgeEqualAmortization(ProgressiveLoanInterestScheduleModel interestSchedule, LocalDate transactionDate,
151154
LoanReAgeParameter reageParameter);
152155
}

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

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1529,16 +1529,23 @@ private void updateEMIForReAgeEqualAmortization(List<RepaymentPeriod> repaymentP
15291529
});
15301530
}
15311531

1532+
@Override
1533+
public OutstandingDetails precalculateReAgeEqualAmortizationAmount(ProgressiveLoanInterestScheduleModel interestSchedule, LocalDate transactionDate,
1534+
LoanReAgeParameter reageParameter) {
1535+
return getOutstandingAmountsTillDate(interestSchedule,
1536+
reageParameter.getInterestHandlingType().equals(LoanReAgeInterestHandlingType.EQUAL_AMORTIZATION_PAYABLE_INTEREST)
1537+
? transactionDate
1538+
: interestSchedule.getMaturityDate());
1539+
}
1540+
1541+
15321542
@Override
15331543
public void reAgeEqualAmortization(ProgressiveLoanInterestScheduleModel interestSchedule, LocalDate transactionDate,
15341544
LoanReAgeParameter reageParameter) {
15351545
LocalDate originalMaturityDate = interestSchedule.getMaturityDate();
15361546
boolean isAfterOriginalMaturityDate = transactionDate.isAfter(originalMaturityDate);
15371547
List<RepaymentPeriod> reAgedRepaymentPeriods = new ArrayList<>(reageParameter.getNumberOfInstallments());
1538-
OutstandingDetails reAgeingAmounts = getOutstandingAmountsTillDate(interestSchedule,
1539-
reageParameter.getInterestHandlingType().equals(LoanReAgeInterestHandlingType.EQUAL_AMORTIZATION_PAYABLE_INTEREST)
1540-
? transactionDate
1541-
: interestSchedule.getMaturityDate());
1548+
OutstandingDetails reAgeingAmounts = precalculateReAgeEqualAmortizationAmount(interestSchedule, transactionDate, reageParameter);
15421549

15431550
// calculate already paid balances from transaction date
15441551
OutstandingDetails paidBalancesFromTransactionDate = liftPaidBalancesAfterDate(interestSchedule, transactionDate);
@@ -1587,7 +1594,10 @@ private void updateModelForReageEqualAmortization(ProgressiveLoanInterestSchedul
15871594
RepaymentPeriod firstReAgedPeriod = interestSchedule.getLastRepaymentPeriod();
15881595
firstReAgedPeriod.setDueDate(toDate);
15891596
firstReAgedPeriod.getLastInterestPeriod().setDueDate(toDate);
1597+
firstReAgedPeriod.setReAged(true);
1598+
firstReAgedPeriod.getPrevious().ifPresent(prev->prev.setNoUnrecognisedInterest(true));
15901599
reAgedRepaymentPeriods.add(firstReAgedPeriod);
1600+
15911601
// insert remaining
15921602
numberOfInstallmentsToAdd--;
15931603
toDate = toDate.plus(frequency, frequencyType);
@@ -1598,6 +1608,7 @@ private void updateModelForReageEqualAmortization(ProgressiveLoanInterestSchedul
15981608
previous.getMc(), previous.getLoanProductRelatedDetail());
15991609
repaymentPeriod.setTotalCapitalizedIncomeAmount(previous.getTotalCapitalizedIncomeAmount());
16001610
repaymentPeriod.setTotalDisbursedAmount(previous.getTotalDisbursedAmount());
1611+
repaymentPeriod.setReAged(true);
16011612
interestSchedule.repaymentPeriods().add(repaymentPeriod);
16021613
reAgedRepaymentPeriods.add(repaymentPeriod);
16031614
previous = repaymentPeriod;
@@ -1618,6 +1629,8 @@ private void createRepaymentPeriodForEarlyRepaidAmountsDuringReAgeing(Progressiv
16181629
RepaymentPeriod repaymentPeriodToInsert = RepaymentPeriod.create(targetPeriod, targetPeriod.getDueDate(),
16191630
interestSchedule.getMaturityDate(), interestSchedule.zero(), interestSchedule.mc(),
16201631
interestSchedule.loanProductRelatedDetail());
1632+
repaymentPeriodToInsert.setReAged(true);
1633+
repaymentPeriodToInsert.setReAgedEarlyRepaymentHolder(true);
16211634
interestSchedule.repaymentPeriods().add(repaymentPeriodToInsert);
16221635
}
16231636

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,7 @@ public void addCapitalizedIncomePrincipalAmount(final Money additionalCapitalize
137137
}
138138

139139
public BigDecimal getCalculatedDueInterest() {
140-
if (isPaused()) {
140+
if (isPaused() || getRepaymentPeriod().isReAged()) {
141141
return getCreditedInterest().getAmount();
142142
}
143143

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

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -90,10 +90,16 @@ public class RepaymentPeriod {
9090
@Setter
9191
@Getter
9292
private boolean noUnrecognisedInterest;
93+
@Setter
94+
@Getter
95+
private boolean reAged = false;
96+
@Setter
97+
@Getter
98+
private boolean reAgedEarlyRepaymentHolder = false;
9399

94100
protected RepaymentPeriod(RepaymentPeriod previous, LocalDate fromDate, LocalDate dueDate, List<InterestPeriod> interestPeriods,
95101
Money emi, Money originalEmi, Money paidPrincipal, Money paidInterest, Money futureUnrecognizedInterest, MathContext mc,
96-
LoanProductMinimumRepaymentScheduleRelatedDetail loanProductRelatedDetail, boolean noUnrecognisedInterest) {
102+
LoanProductMinimumRepaymentScheduleRelatedDetail loanProductRelatedDetail, boolean noUnrecognisedInterest, boolean reAged, boolean reAgedEarlyRepaymentHolder) {
97103
this.previous = previous;
98104
this.fromDate = fromDate;
99105
this.dueDate = dueDate;
@@ -106,19 +112,21 @@ protected RepaymentPeriod(RepaymentPeriod previous, LocalDate fromDate, LocalDat
106112
this.mc = mc;
107113
this.loanProductRelatedDetail = loanProductRelatedDetail;
108114
this.noUnrecognisedInterest = noUnrecognisedInterest;
115+
this.reAged = reAged;
116+
this.reAgedEarlyRepaymentHolder = reAgedEarlyRepaymentHolder;
109117
}
110118

111119
public static RepaymentPeriod empty(RepaymentPeriod previous, MathContext mc,
112120
LoanProductMinimumRepaymentScheduleRelatedDetail loanProductRelatedDetail) {
113121
return new RepaymentPeriod(previous, null, null, new ArrayList<>(), null, null, null, null, null, mc, loanProductRelatedDetail,
114-
false);
122+
false, false, false);
115123
}
116124

117125
public static RepaymentPeriod create(RepaymentPeriod previous, LocalDate fromDate, LocalDate dueDate, Money emi, MathContext mc,
118126
LoanProductMinimumRepaymentScheduleRelatedDetail loanProductRelatedDetail) {
119127
final Money zero = emi.zero();
120128
final RepaymentPeriod newRepaymentPeriod = new RepaymentPeriod(previous, fromDate, dueDate, new ArrayList<>(), emi, emi, zero, zero,
121-
zero, mc, loanProductRelatedDetail, false);
129+
zero, mc, loanProductRelatedDetail, false, false, false);
122130
// There is always at least 1 interest period, by default with same from-due date as repayment period
123131
newRepaymentPeriod.getInterestPeriods().add(InterestPeriod.withEmptyAmounts(newRepaymentPeriod, fromDate, dueDate));
124132
return newRepaymentPeriod;
@@ -128,7 +136,7 @@ public static RepaymentPeriod copy(RepaymentPeriod previous, RepaymentPeriod rep
128136
final RepaymentPeriod newRepaymentPeriod = new RepaymentPeriod(previous, repaymentPeriod.getFromDate(),
129137
repaymentPeriod.getDueDate(), new ArrayList<>(), repaymentPeriod.getEmi(), repaymentPeriod.getOriginalEmi(),
130138
repaymentPeriod.getPaidPrincipal(), repaymentPeriod.getPaidInterest(), repaymentPeriod.getFutureUnrecognizedInterest(), mc,
131-
repaymentPeriod.getLoanProductRelatedDetail(), repaymentPeriod.isNoUnrecognisedInterest());
139+
repaymentPeriod.getLoanProductRelatedDetail(), repaymentPeriod.isNoUnrecognisedInterest(), repaymentPeriod.isReAged(), repaymentPeriod.isReAgedEarlyRepaymentHolder());
132140
// There is always at least 1 interest period, by default with same from-due date as repayment period
133141
for (InterestPeriod interestPeriod : repaymentPeriod.getInterestPeriods()) {
134142
newRepaymentPeriod.getInterestPeriods().add(InterestPeriod.copy(newRepaymentPeriod, interestPeriod, mc));
@@ -140,7 +148,7 @@ public static RepaymentPeriod copyWithoutPaidAmounts(RepaymentPeriod previous, R
140148
final Money zero = Money.zero(repaymentPeriod.getCurrency(), mc);
141149
final RepaymentPeriod newRepaymentPeriod = new RepaymentPeriod(previous, repaymentPeriod.getFromDate(),
142150
repaymentPeriod.getDueDate(), new ArrayList<>(), repaymentPeriod.getEmi(), repaymentPeriod.getOriginalEmi(), zero, zero,
143-
zero, mc, repaymentPeriod.getLoanProductRelatedDetail(), repaymentPeriod.isNoUnrecognisedInterest());
151+
zero, mc, repaymentPeriod.getLoanProductRelatedDetail(), repaymentPeriod.isNoUnrecognisedInterest(), repaymentPeriod.isReAged(), repaymentPeriod.isReAgedEarlyRepaymentHolder());
144152
// There is always at least 1 interest period, by default with same from-due date as repayment period
145153
for (InterestPeriod interestPeriod : repaymentPeriod.getInterestPeriods()) {
146154
var interestPeriodCopy = InterestPeriod.copy(newRepaymentPeriod, interestPeriod);

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

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4214,6 +4214,72 @@ public class ReAgeEqualAmortization {
42144214
// TODO Cleanup Test
42154215
// TODO Add repayment schedule verification
42164216

4217+
@Test
4218+
public void test_transactionInMiddleOfPeriod_EQUAL_AMORTIZATION_FULL_INTEREST_noTransactionTilDate_noInterestRecalc() {
4219+
final List<LoanScheduleModelRepaymentPeriod> expectedRepaymentPeriods = new ArrayList<>();
4220+
4221+
expectedRepaymentPeriods.add(repayment(1, LocalDate.of(2024, 1, 1), LocalDate.of(2024, 2, 1)));
4222+
expectedRepaymentPeriods.add(repayment(2, LocalDate.of(2024, 2, 1), LocalDate.of(2024, 3, 1)));
4223+
expectedRepaymentPeriods.add(repayment(3, LocalDate.of(2024, 3, 1), LocalDate.of(2024, 4, 1)));
4224+
expectedRepaymentPeriods.add(repayment(4, LocalDate.of(2024, 4, 1), LocalDate.of(2024, 5, 1)));
4225+
expectedRepaymentPeriods.add(repayment(5, LocalDate.of(2024, 5, 1), LocalDate.of(2024, 6, 1)));
4226+
expectedRepaymentPeriods.add(repayment(6, LocalDate.of(2024, 6, 1), LocalDate.of(2024, 7, 1)));
4227+
4228+
final BigDecimal interestRate = BigDecimal.valueOf(15.678);
4229+
final Integer installmentAmountInMultiplesOf = null;
4230+
4231+
Mockito.when(loanProductRelatedDetail.getAnnualNominalInterestRate()).thenReturn(interestRate);
4232+
Mockito.when(loanProductRelatedDetail.getDaysInYearType()).thenReturn(DaysInYearType.DAYS_365.getValue());
4233+
Mockito.when(loanProductRelatedDetail.getDaysInMonthType()).thenReturn(DaysInMonthType.ACTUAL.getValue());
4234+
Mockito.when(loanProductRelatedDetail.getRepaymentPeriodFrequencyType()).thenReturn(PeriodFrequencyType.MONTHS);
4235+
Mockito.when(loanProductRelatedDetail.getRepayEvery()).thenReturn(1);
4236+
Mockito.when(loanProductRelatedDetail.getCurrencyData()).thenReturn(currency);
4237+
4238+
final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generatePeriodInterestScheduleModel(
4239+
expectedRepaymentPeriods, loanProductRelatedDetail, List.of(), installmentAmountInMultiplesOf, mc);
4240+
4241+
final Money disbursedAmount = toMoney(100.0);
4242+
emiCalculator.addDisbursement(interestSchedule, LocalDate.of(2024, 1, 1), disbursedAmount);
4243+
4244+
checkPeriod(interestSchedule, 0, 0, 17.43, 0.0, 0.0, 1.33, 16.1, 83.9);
4245+
checkPeriod(interestSchedule, 0, 1, 17.43, 0.013315561644, 1.3315561644, 1.33, 16.1, 83.9);
4246+
checkPeriod(interestSchedule, 1, 0, 17.43, 0.012456493151, 1.04509977537, 1.05, 16.38, 67.52);
4247+
checkPeriod(interestSchedule, 2, 0, 17.43, 0.013315561644, 0.899066722202, 0.90, 16.53, 50.99);
4248+
checkPeriod(interestSchedule, 3, 0, 17.43, 0.012886027397, 0.657058536972, 0.66, 16.77, 34.22);
4249+
checkPeriod(interestSchedule, 4, 0, 17.43, 0.013315561644, 0.455658519458, 0.46, 16.97, 17.25);
4250+
checkPeriod(interestSchedule, 5, 0, 17.47, 0.012886027397, 0.222283972598, 0.22, 17.25, 0.0);
4251+
4252+
// No repayment no interest recalculation
4253+
LocalDate reAgingStartDate = LocalDate.of(2024, 4, 20);
4254+
4255+
OutstandingDetails outstandingAmountsTillDate = emiCalculator.getOutstandingAmountsTillDate(interestSchedule,
4256+
interestSchedule.getMaturityDate());
4257+
System.out.println("outstandingAmountsTillDate: " + outstandingAmountsTillDate);
4258+
4259+
LoanTransaction loanTransaction = new LoanTransaction(null, null, LoanTransactionType.REAGE, LocalDate.of(2024, 4, 15),
4260+
outstandingAmountsTillDate.getOutstandingPrincipal().add(outstandingAmountsTillDate.getOutstandingInterest())
4261+
.getAmount(),
4262+
outstandingAmountsTillDate.getOutstandingPrincipal().getAmount(),
4263+
outstandingAmountsTillDate.getOutstandingInterest().getAmount(), ZERO, ZERO, ZERO, false, null, null);
4264+
LoanReAgeParameter reageParameter = new LoanReAgeParameter(loanTransaction, PeriodFrequencyType.MONTHS, 1, reAgingStartDate, 6,
4265+
LoanReAgeInterestHandlingType.EQUAL_AMORTIZATION_FULL_INTEREST, null);
4266+
loanTransaction.setLoanReAgeParameter(reageParameter);
4267+
4268+
// Update the existing model with re-aged periods
4269+
emiCalculator.reAgeEqualAmortization(interestSchedule, loanTransaction.getTransactionDate(), reageParameter);
4270+
4271+
logPeriods(interestSchedule);
4272+
4273+
OutstandingDetails outstandingAmountsTillDateAfterReage = emiCalculator.getOutstandingAmountsTillDate(interestSchedule,
4274+
interestSchedule.getMaturityDate());
4275+
4276+
Assertions.assertEquals(outstandingAmountsTillDate.getOutstandingInterest().getAmount(),
4277+
outstandingAmountsTillDateAfterReage.getOutstandingInterest().getAmount());
4278+
Assertions.assertEquals(outstandingAmountsTillDate.getOutstandingPrincipal().getAmount(),
4279+
outstandingAmountsTillDateAfterReage.getOutstandingPrincipal().getAmount());
4280+
4281+
}
4282+
42174283
@Test
42184284
public void test_transactionInMiddleOfPeriod_EQUAL_AMORTIZATION_FULL_INTEREST() {
42194285
final List<LoanScheduleModelRepaymentPeriod> expectedRepaymentPeriods = new ArrayList<>();

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,9 @@ void testEmptyRepaymentPeriod() {
123123
null, // futureUnrecognizedInterest
124124
MC, // mc
125125
loanProductRelatedDetail, //
126-
false // noUnrecognizedInterest
126+
false, // noUnrecognizedInterest
127+
false, // reAged
128+
false // reAgedEarlyRepaymentHolder
127129
);
128130

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

0 commit comments

Comments
 (0)