Skip to content

Commit ca2e498

Browse files
FINERACT-2354: Re-aging:- Accrual and Accrual Activity handling - Equal Amortization
1 parent 2936ab5 commit ca2e498

File tree

13 files changed

+1460
-172
lines changed

13 files changed

+1460
-172
lines changed

fineract-e2e-tests-runner/src/test/resources/features/LoanReAgingAccrual.feature

Lines changed: 625 additions & 1 deletion
Large diffs are not rendered by default.

fineract-e2e-tests-runner/src/test/resources/features/LoanReAgingEqualAmortization.feature

Lines changed: 618 additions & 114 deletions
Large diffs are not rendered by default.

fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1763,6 +1763,10 @@ public LoanTransaction findContractTerminationTransaction() {
17631763
return getLoanTransaction(e -> e.isNotReversed() && e.isContractTermination());
17641764
}
17651765

1766+
public LoanTransaction findReAgeTransaction() {
1767+
return getLoanTransaction(LoanTransaction::isReAge);
1768+
}
1769+
17661770
public void handleMaturityDateActivate() {
17671771
if (this.expectedMaturityDate != null && !this.expectedMaturityDate.equals(this.actualMaturityDate)) {
17681772
this.actualMaturityDate = this.expectedMaturityDate;

fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanAccrualActivityProcessingService.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,10 @@
1919
package org.apache.fineract.portfolio.loanaccount.service;
2020

2121
import java.time.LocalDate;
22+
import java.util.List;
2223
import org.apache.fineract.portfolio.loanaccount.domain.ChangedTransactionDetail;
2324
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
25+
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
2426
import org.springframework.lang.NonNull;
2527
import org.springframework.transaction.annotation.Transactional;
2628

@@ -31,7 +33,8 @@ public interface LoanAccrualActivityProcessingService {
3133

3234
void makeAccrualActivityTransaction(@NonNull Loan loan, @NonNull LocalDate currentDate);
3335

34-
void recalculateAccrualActivityTransaction(Loan loan, ChangedTransactionDetail changedTransactionDetail);
36+
void recalculateAccrualActivityTransaction(Loan loan, ChangedTransactionDetail changedTransactionDetail,
37+
List<LoanTransaction> loanTransactions);
3538

3639
@Transactional
3740
void processAccrualActivityForLoanClosure(@NonNull Loan loan);

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

Lines changed: 47 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2006,8 +2006,21 @@ private void handleAccelerateMaturityDate(final LoanTransaction loanTransaction,
20062006
if (transactionCtx instanceof ProgressiveTransactionCtx progressiveTransactionCtx
20072007
&& loan.isInterestBearingAndInterestRecalculationEnabled()) {
20082008
final BigDecimal interestOutstanding = currentInstallment.getInterestOutstanding(loan.getCurrency()).getAmount();
2009-
final BigDecimal newInterest = emiCalculator.getPeriodInterestTillDate(progressiveTransactionCtx.getModel(),
2010-
currentInstallment.getFromDate(), currentInstallment.getDueDate(), transactionDate, true).getAmount();
2009+
final LoanTransaction reAgeTransaction = progressiveTransactionCtx.getAlreadyProcessedTransactions().stream()
2010+
.filter(t -> t.isReAge() && t.isNotReversed()).findFirst().orElse(null);
2011+
final LoanReAgeParameter loanReAgeParameter = reAgeTransaction != null ? reAgeTransaction.getLoanReAgeParameter()
2012+
: null;
2013+
2014+
final boolean equalAmortizationReAge = loanReAgeParameter != null
2015+
&& (LoanReAgeInterestHandlingType.EQUAL_AMORTIZATION_FULL_INTEREST
2016+
.equals(loanReAgeParameter.getInterestHandlingType())
2017+
|| LoanReAgeInterestHandlingType.EQUAL_AMORTIZATION_PAYABLE_INTEREST
2018+
.equals(loanReAgeParameter.getInterestHandlingType()));
2019+
2020+
final BigDecimal newInterest = emiCalculator
2021+
.getPeriodInterestTillDate(progressiveTransactionCtx.getModel(), currentInstallment.getFromDate(),
2022+
currentInstallment.getDueDate(), transactionDate, true, equalAmortizationReAge)
2023+
.getAmount();
20112024
if (interestOutstanding.compareTo(BigDecimal.ZERO) > 0 || newInterest.compareTo(BigDecimal.ZERO) > 0) {
20122025
currentInstallment.updateInterestCharged(newInterest);
20132026
}
@@ -2119,8 +2132,20 @@ private void handleZeroInterestChargeOff(final LoanTransaction loanTransaction,
21192132
installments.stream().filter(installment -> !installment.getFromDate().isAfter(transactionDate)
21202133
&& installment.getDueDate().isAfter(transactionDate)).forEach(installment -> {
21212134
final BigDecimal interestOutstanding = installment.getInterestOutstanding(currency).getAmount();
2135+
final LoanTransaction reAgeTransaction = progressiveTransactionCtx.getAlreadyProcessedTransactions().stream()
2136+
.filter(t -> t.isReAge() && t.isNotReversed()).findFirst().orElse(null);
2137+
final LoanReAgeParameter loanReAgeParameter = reAgeTransaction != null
2138+
? reAgeTransaction.getLoanReAgeParameter()
2139+
: null;
2140+
2141+
final boolean equalAmortizationReAge = loanReAgeParameter != null
2142+
&& (LoanReAgeInterestHandlingType.EQUAL_AMORTIZATION_FULL_INTEREST
2143+
.equals(loanReAgeParameter.getInterestHandlingType())
2144+
|| LoanReAgeInterestHandlingType.EQUAL_AMORTIZATION_PAYABLE_INTEREST
2145+
.equals(loanReAgeParameter.getInterestHandlingType()));
21222146
final BigDecimal newInterest = emiCalculator.getPeriodInterestTillDate(progressiveTransactionCtx.getModel(),
2123-
installment.getFromDate(), installment.getDueDate(), transactionDate, true).getAmount();
2147+
installment.getFromDate(), installment.getDueDate(), transactionDate, true, equalAmortizationReAge)
2148+
.getAmount();
21242149
if (MathUtil.isGreaterThanZero(interestOutstanding) || MathUtil.isGreaterThanZero(newInterest)) {
21252150
final BigDecimal interestRemoved = MathUtil.subtract(MathUtil.nullToZero(installment.getInterestCharged()),
21262151
newInterest);
@@ -3362,9 +3387,16 @@ private void updateRepaymentPeriodsAfterAccelerateMaturityDate(final Progressive
33623387

33633388
final BigDecimal totalPrincipal = periodsToRemove.stream().map(rp -> rp.getDuePrincipal().getAmount()).reduce(BigDecimal.ZERO,
33643389
BigDecimal::add);
3390+
final LoanTransaction reAgeTransaction = transactionCtx.getAlreadyProcessedTransactions().stream()
3391+
.filter(t -> t.isReAge() && t.isNotReversed()).findFirst().orElse(null);
3392+
final LoanReAgeParameter loanReAgeParameter = reAgeTransaction != null ? reAgeTransaction.getLoanReAgeParameter() : null;
3393+
3394+
final boolean equalAmortizationReAge = loanReAgeParameter != null && (LoanReAgeInterestHandlingType.EQUAL_AMORTIZATION_FULL_INTEREST
3395+
.equals(loanReAgeParameter.getInterestHandlingType())
3396+
|| LoanReAgeInterestHandlingType.EQUAL_AMORTIZATION_PAYABLE_INTEREST.equals(loanReAgeParameter.getInterestHandlingType()));
33653397

33663398
final BigDecimal newInterest = emiCalculator.getPeriodInterestTillDate(transactionCtx.getModel(), lastPeriod.getFromDate(),
3367-
lastPeriod.getDueDate(), transactionDate, false).getAmount();
3399+
lastPeriod.getDueDate(), transactionDate, false, equalAmortizationReAge).getAmount();
33683400

33693401
lastPeriod.setEmi(lastPeriod.getDuePrincipal().add(totalPrincipal).add(newInterest));
33703402

@@ -3407,8 +3439,18 @@ private BigDecimal getInterestTillChargeOffForPeriod(final Loan loan, final Loca
34073439
} else if (isInPeriod) {
34083440
if (transactionCtx instanceof ProgressiveTransactionCtx progressiveTransactionCtx
34093441
&& loan.isInterestBearingAndInterestRecalculationEnabled()) {
3442+
final LoanTransaction reAgeTransaction = progressiveTransactionCtx.getAlreadyProcessedTransactions().stream()
3443+
.filter(t -> t.isReAge() && t.isNotReversed()).findFirst().orElse(null);
3444+
final LoanReAgeParameter loanReAgeParameter = reAgeTransaction != null ? reAgeTransaction.getLoanReAgeParameter()
3445+
: null;
3446+
3447+
final boolean equalAmortizationReAge = loanReAgeParameter != null
3448+
&& (LoanReAgeInterestHandlingType.EQUAL_AMORTIZATION_FULL_INTEREST
3449+
.equals(loanReAgeParameter.getInterestHandlingType())
3450+
|| LoanReAgeInterestHandlingType.EQUAL_AMORTIZATION_PAYABLE_INTEREST
3451+
.equals(loanReAgeParameter.getInterestHandlingType()));
34103452
interest = emiCalculator.getPeriodInterestTillDate(progressiveTransactionCtx.getModel(), installment.getFromDate(),
3411-
installment.getDueDate(), chargeOffDate, true).getAmount();
3453+
installment.getDueDate(), chargeOffDate, true, equalAmortizationReAge).getAmount();
34123454
} else {
34133455
final BigDecimal totalInterest = installment.getInterestOutstanding(currency).getAmount();
34143456
if (LoanChargeOffBehaviour.ZERO_INTEREST.equals(loan.getLoanProductRelatedDetail().getChargeOffBehaviour())

fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/ProgressiveLoanScheduleGenerator.java

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,9 @@
4141
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
4242
import org.apache.fineract.portfolio.loanaccount.domain.LoanCharge;
4343
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment;
44+
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
45+
import org.apache.fineract.portfolio.loanaccount.domain.reaging.LoanReAgeInterestHandlingType;
46+
import org.apache.fineract.portfolio.loanaccount.domain.reaging.LoanReAgeParameter;
4447
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.LoanRepaymentScheduleTransactionProcessor;
4548
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.impl.AdvancedPaymentScheduleTransactionProcessor;
4649
import org.apache.fineract.portfolio.loanaccount.loanschedule.data.LoanScheduleDTO;
@@ -246,14 +249,20 @@ public Money getPeriodInterestTillDate(@NotNull LoanRepaymentScheduleInstallment
246249
if (!(transactionProcessor instanceof AdvancedPaymentScheduleTransactionProcessor processor)) {
247250
throw new IllegalStateException("Expected an AdvancedPaymentScheduleTransactionProcessor");
248251
}
249-
// if (installment.isAdditional() || installment.isDownPayment() || installment.isReAged()) {
250-
// TODO fix???
251-
if (installment.isAdditional() || installment.isDownPayment()) {
252+
final LoanTransaction reAgeTransaction = loan.findReAgeTransaction();
253+
final LoanReAgeParameter loanReAgeParameter = reAgeTransaction != null ? reAgeTransaction.getLoanReAgeParameter() : null;
254+
255+
final boolean equalAmortizationReAge = loanReAgeParameter != null && (LoanReAgeInterestHandlingType.EQUAL_AMORTIZATION_FULL_INTEREST
256+
.equals(loanReAgeParameter.getInterestHandlingType())
257+
|| LoanReAgeInterestHandlingType.EQUAL_AMORTIZATION_PAYABLE_INTEREST.equals(loanReAgeParameter.getInterestHandlingType()));
258+
259+
if (installment.isAdditional() || installment.isDownPayment() || (installment.isReAged() && !equalAmortizationReAge)) {
252260
return Money.zero(loan.getCurrency());
253261
}
254262
Optional<ProgressiveLoanInterestScheduleModel> savedModel = interestScheduleModelRepositoryWrapper.getSavedModel(loan, targetDate);
255263
ProgressiveLoanInterestScheduleModel model = savedModel.orElseThrow();
256-
return emiCalculator.getPeriodInterestTillDate(model, installment.getFromDate(), installment.getDueDate(), targetDate, false);
264+
return emiCalculator.getPeriodInterestTillDate(model, installment.getFromDate(), installment.getDueDate(), targetDate, false,
265+
equalAmortizationReAge);
257266
}
258267

259268
// Private, internal methods

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,8 @@ PeriodDueDetails getDueAmounts(@NotNull ProgressiveLoanInterestScheduleModel sch
128128
*/
129129
@NotNull
130130
Money getPeriodInterestTillDate(@NotNull ProgressiveLoanInterestScheduleModel scheduleModel, @NotNull LocalDate periodFromDate,
131-
@NotNull LocalDate periodDueDate, @NotNull LocalDate targetDate, boolean includeChargebackInterest);
131+
@NotNull LocalDate periodDueDate, @NotNull LocalDate targetDate, boolean includeChargebackInterest,
132+
boolean equalAmortizationReAge);
132133

133134
Money getOutstandingLoanBalanceOfPeriod(ProgressiveLoanInterestScheduleModel interestScheduleModel, LocalDate targetDate);
134135

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

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -416,15 +416,18 @@ public PeriodDueDetails getDueAmounts(@NotNull ProgressiveLoanInterestScheduleMo
416416

417417
@Override
418418
@NotNull
419-
public Money getPeriodInterestTillDate(@NotNull ProgressiveLoanInterestScheduleModel scheduleModel, @NotNull LocalDate periodFromDate,
420-
@NotNull LocalDate periodDueDate, @NotNull LocalDate targetDate, boolean includeCreditedInterest) {
421-
ProgressiveLoanInterestScheduleModel recalculatedScheduleModelTillDate = recalculateScheduleModelTillDate(scheduleModel,
419+
public Money getPeriodInterestTillDate(@NotNull final ProgressiveLoanInterestScheduleModel scheduleModel,
420+
@NotNull final LocalDate periodFromDate, @NotNull final LocalDate periodDueDate, @NotNull final LocalDate targetDate,
421+
final boolean includeCreditedInterest, final boolean equalAmortizationReAge) {
422+
final ProgressiveLoanInterestScheduleModel recalculatedScheduleModelTillDate = recalculateScheduleModelTillDate(scheduleModel,
422423
targetDate);
423-
RepaymentPeriod repaymentPeriod = recalculatedScheduleModelTillDate
424+
final RepaymentPeriod repaymentPeriod = recalculatedScheduleModelTillDate
424425
.findRepaymentPeriodByFromAndDueDate(periodFromDate, periodDueDate).orElseThrow();
425-
return includeCreditedInterest ? repaymentPeriod.getCalculatedDueInterest()
426-
: repaymentPeriod.getCalculatedDueInterest().minus(repaymentPeriod.getCreditedInterest(),
427-
recalculatedScheduleModelTillDate.mc());
426+
final Money calculatedDueInterest = equalAmortizationReAge && repaymentPeriod.isReAged()
427+
? repaymentPeriod.calculateDueInterestTillDateForReAgedPeriodWithEqualAmortization()
428+
: repaymentPeriod.getCalculatedDueInterest();
429+
return includeCreditedInterest ? calculatedDueInterest
430+
: calculatedDueInterest.minus(repaymentPeriod.getCreditedInterest(), recalculatedScheduleModelTillDate.mc());
428431
}
429432

430433
@Override
@@ -975,6 +978,11 @@ private void calculateLastUnpaidRepaymentPeriodEMI(ProgressiveLoanInterestSchedu
975978
scheduleModel.repaymentPeriods().stream().filter(rp -> !rp.isReAgedEarlyRepaymentHolder())
976979
.forEach(rp -> rp.setInterestMoved(false));
977980

981+
if (repaymentPeriod.getReAgedInterest() == null || repaymentPeriod.getReAgedInterest().isZero()) {
982+
scheduleModel.repaymentPeriods().stream().filter(RepaymentPeriod::isReAgedEarlyRepaymentHolder)
983+
.forEach(rp -> rp.setInterestMoved(false));
984+
}
985+
978986
MathContext mc = scheduleModel.mc();
979987
Money totalDueInterest = scheduleModel.repaymentPeriods().stream().map(RepaymentPeriod::getDueInterest)
980988
.reduce(scheduleModel.zero(), (m1, m2) -> m1.plus(m2, mc)); // 1.46

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

Lines changed: 30 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -155,7 +155,9 @@ public static RepaymentPeriod copy(RepaymentPeriod previous, RepaymentPeriod rep
155155
repaymentPeriod.isReAgedEarlyRepaymentHolder(), repaymentPeriod.getReAgedInterest());
156156
newRepaymentPeriod.setCreditedPrincipalMovedDueReAge(repaymentPeriod.getCreditedPrincipalMovedDueReAge());
157157
newRepaymentPeriod.setCreditedInterestMovedDueReAge(repaymentPeriod.getCreditedInterestMovedDueReAge());
158-
newRepaymentPeriod.setInterestMoved(repaymentPeriod.isReAgedEarlyRepaymentHolder());
158+
if (newRepaymentPeriod.getReAgedInterest() != null && !repaymentPeriod.getReAgedInterest().isZero()) {
159+
newRepaymentPeriod.setInterestMoved(repaymentPeriod.isReAgedEarlyRepaymentHolder());
160+
}
159161
// There is always at least 1 interest period, by default with same from-due date as repayment period
160162
for (InterestPeriod interestPeriod : repaymentPeriod.getInterestPeriods()) {
161163
newRepaymentPeriod.getInterestPeriods().add(InterestPeriod.copy(newRepaymentPeriod, interestPeriod, mc));
@@ -216,22 +218,34 @@ public Money getCalculatedDueInterest() {
216218
return calculatedDueInterestCalculation.get();
217219
}
218220

219-
private Money calculateReAgedDueInterest() {
220-
if (getInterestPeriods() == null || getInterestPeriods().isEmpty() || getReAgedInterest() == null || getReAgedInterest().isZero()) {
221-
// return zero in case there are no interest periods
222-
// or if there is no reAgedInterest to calculate
223-
// if no interest periods are available, that means the interest to be calculated expected to be zero
224-
return getZero();
221+
public Money calculateDueInterestTillDateForReAgedPeriodWithEqualAmortization() {
222+
Money calculatedDueInterest = getZero();
223+
Money calculatedReAgedInterest = getZero();
224+
if (!isInterestMoved()) {
225+
calculatedDueInterest = Money.of(getEmi().getCurrencyData(),
226+
getInterestPeriods().stream().map(InterestPeriod::getCalculatedDueInterest).reduce(BigDecimal.ZERO, BigDecimal::add),
227+
mc);
228+
}
229+
230+
if (getReAgedInterest() != null && !getReAgedInterest().isZero()) {
231+
long length = DateUtils.getDifferenceInDays(getFromDate(), getDueDate());
232+
if (length == 0 || getInterestPeriods() == null || getInterestPeriods().isEmpty()) {
233+
// if the repayment period length is zero. return reAgedInterest.
234+
calculatedReAgedInterest = getReAgedInterest();
235+
} else {
236+
long interestCalculationLength = DateUtils.getDifferenceInDays(getInterestPeriods().getFirst().getFromDate(),
237+
getInterestPeriods().getLast().getDueDate());
238+
calculatedReAgedInterest = Money.of(getZero().getCurrencyData(), BigDecimal.valueOf(interestCalculationLength)
239+
.divide(BigDecimal.valueOf(length), getMc()).multiply(getReAgedInterest().getAmount(), getMc()));
240+
}
225241
}
226-
long length = DateUtils.getDifferenceInDays(getFromDate(), getDueDate());
227-
if (length == 0) {
228-
// if the repayment period length is zero. return reAgedInterest.
229-
return getReAgedInterest();
242+
243+
calculatedDueInterest = calculatedDueInterest.add(calculatedReAgedInterest);
244+
calculatedDueInterest = calculatedDueInterest.add(getFutureUnrecognizedInterest(), getMc());
245+
if (getPrevious().isPresent()) {
246+
calculatedDueInterest = calculatedDueInterest.add(getPrevious().get().getUnrecognizedInterest(), getMc());
230247
}
231-
long interestCalculationLength = DateUtils.getDifferenceInDays(getInterestPeriods().getFirst().getFromDate(),
232-
getInterestPeriods().getLast().getDueDate());
233-
return Money.of(getZero().getCurrencyData(), BigDecimal.valueOf(interestCalculationLength)
234-
.divide(BigDecimal.valueOf(length), getMc()).multiply(getReAgedInterest().getAmount(), getMc()));
248+
return MathUtil.negativeToZero(calculatedDueInterest, getMc());
235249
}
236250

237251
public Money calculateCalculatedDueInterest() {
@@ -241,7 +255,7 @@ public Money calculateCalculatedDueInterest() {
241255
getInterestPeriods().stream().map(InterestPeriod::getCalculatedDueInterest).reduce(BigDecimal.ZERO, BigDecimal::add),
242256
mc);
243257
}
244-
calculatedDueInterest = calculatedDueInterest.add(calculateReAgedDueInterest());
258+
calculatedDueInterest = calculatedDueInterest.add(getReAgedInterest());
245259
calculatedDueInterest = calculatedDueInterest.add(getFutureUnrecognizedInterest(), getMc());
246260
if (getPrevious().isPresent()) {
247261
calculatedDueInterest = calculatedDueInterest.add(getPrevious().get().getUnrecognizedInterest(), getMc());

0 commit comments

Comments
 (0)