You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@fineract.apache.org by ad...@apache.org on 2025/12/10 12:10:40 UTC
(fineract) 01/02: FINERACT-2354: [BE] chargeback handling with backdated re-age for last adjustment strategy with equal amortization re-aging behaviour
This is an automated email from the ASF dual-hosted git repository.
adamsaghy pushed a commit to branch develop
in repository https://gitbox.apache.org/repos/asf/fineract.git
commit fa5be3f5a53a6f8180099f68f52545de26ce5daa
Author: Soma Sörös <so...@dpc.hu>
AuthorDate: Wed Nov 26 13:46:58 2025 +0100
FINERACT-2354: [BE] chargeback handling with backdated re-age for last adjustment strategy with equal amortization re-aging behaviour
---
.../domain/LoanRepaymentScheduleInstallment.java | 6 +-
...dvancedPaymentScheduleTransactionProcessor.java | 66 +++----
.../loanproduct/calc/ProgressiveEMICalculator.java | 65 +++----
.../calc/data/EqualAmortizationValues.java | 27 ++-
.../loanproduct/calc/data/RepaymentPeriod.java | 22 ++-
.../calc/ProgressiveEMICalculatorTest.java | 201 ++++++++++++++++++++-
6 files changed, 293 insertions(+), 94 deletions(-)
diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanRepaymentScheduleInstallment.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanRepaymentScheduleInstallment.java
index 97999777f6..40590d9cdc 100644
--- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanRepaymentScheduleInstallment.java
+++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanRepaymentScheduleInstallment.java
@@ -28,6 +28,7 @@ import jakarta.persistence.OneToMany;
import jakarta.persistence.Table;
import java.math.BigDecimal;
import java.time.LocalDate;
+import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
@@ -518,8 +519,9 @@ public class LoanRepaymentScheduleInstallment extends AbstractAuditableWithUTCDa
return this.installmentNumber.compareTo(o.installmentNumber);
}
- public int compareToByDueDate(LoanRepaymentScheduleInstallment o) {
- return this.dueDate.compareTo(o.dueDate);
+ public int compareToByFromDueDate(LoanRepaymentScheduleInstallment o) {
+ return Comparator.comparing(LoanRepaymentScheduleInstallment::getDueDate)
+ .thenComparing(LoanRepaymentScheduleInstallment::getFromDate).compare(this, o);
}
public boolean isPrincipalNotCompleted(final MonetaryCurrency currency) {
diff --git a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/AdvancedPaymentScheduleTransactionProcessor.java b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/AdvancedPaymentScheduleTransactionProcessor.java
index 6362eeea8b..accaa6da30 100644
--- a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/AdvancedPaymentScheduleTransactionProcessor.java
+++ b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/AdvancedPaymentScheduleTransactionProcessor.java
@@ -3171,7 +3171,7 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
private void reprocessInstallments(final List<LoanRepaymentScheduleInstallment> installments) {
final AtomicInteger counter = new AtomicInteger(1);
final AtomicReference<LocalDate> previousDueDate = new AtomicReference<>(null);
- installments.stream().sorted(LoanRepaymentScheduleInstallment::compareToByDueDate).forEachOrdered(i -> {
+ installments.stream().sorted(LoanRepaymentScheduleInstallment::compareToByFromDueDate).forEachOrdered(i -> {
i.updateInstallmentNumber(counter.getAndIncrement());
final LocalDate prev = previousDueDate.get();
if (prev != null && (i.isAdditional() || i.isReAged())) {
@@ -3179,6 +3179,7 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
}
previousDueDate.set(i.getDueDate());
});
+ installments.sort(LoanRepaymentScheduleInstallment::compareToByFromDueDate);
}
private LocalDate calculateReAgedInstallmentDueDate(final LoanReAgeParameter reAgeParameter, final LocalDate dueDate) {
@@ -3479,12 +3480,8 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
BalancesWithPaidInAdvance paidInAdvanceBalances = liftEarlyRepaidBalances(installments, transactionDate, currency,
ctx.getAlreadyProcessedTransactions());
- // TODO add as Parameter here: paidInAdvanceBalances.getAggregatedFeeChargesPortion().isGreaterThanZero() ||
- // paidInAdvanceBalances.getAggregatedPenaltyChargesPortion().isGreaterThanZero()
emiCalculator.reAgeEqualAmortization(model, transactionDate, loanReAgeParameter,
- outstandingBalances.fees.add(outstandingBalances.penalties),
- new EqualAmortizationValues(calculatedFees.value().add(calculatedPenalties.value()),
- calculatedFees.adjustment().add(calculatedPenalties.adjustment())));
+ outstandingBalances.fees.add(outstandingBalances.penalties), calculatedFees.add(calculatedPenalties));
installments.removeIf(i -> (i.getInstallmentNumber() != null && !i.isDownPayment() && !i.getDueDate().isBefore(transactionDate)
&& !i.isAdditional()) || (!i.getDueDate().isAfter(model.getMaturityDate()) && i.isAdditional()));
@@ -3494,6 +3491,7 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
i.setInstallmentNumber(model.repaymentPeriods().size());
});
+ int reAgedInstallmentIndex = 0;
for (int index = 0; index < model.repaymentPeriods().size(); index++) {
RepaymentPeriod rp = model.repaymentPeriods().get(index);
if (rp.getDueDate().isBefore(transactionDate)) {
@@ -3504,9 +3502,9 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
installment.setInterestCharged(installment.getInterestPaid());
installment.setPrincipal(installment.getPrincipalCompleted(currency).getAmount());
installment.setInstallmentNumber(index + 1);
+ installment.setCreditedPrincipal(rp.getCreditedPrincipal().getAmount());
installment.updateObligationsMet(currency, transactionDate);
- // TODO add remaining components
} else {
LoanRepaymentScheduleInstallment created = LoanRepaymentScheduleInstallment.newReAgedInstallment(loanTransaction.getLoan(),
index + 1, rp.getFromDate(), rp.getDueDate(), rp.getDuePrincipal().getAmount(), rp.getDueInterest().getAmount(),
@@ -3525,16 +3523,17 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
paidInAdvanceBalances.loanTransactionToRepaymentScheduleMappings.forEach(m -> m.setInstallment(created));
} else {
- boolean isLastRepaymentPeriod = model.isLastRepaymentPeriod(rp);
- created.setFeeChargesCharged(calculatedFees.calculateValueBigDecimal(isLastRepaymentPeriod));
- created.setPenaltyCharges(calculatedPenalties.calculateValueBigDecimal(isLastRepaymentPeriod));
+ created.setFeeChargesCharged(calculatedFees.calculateValueBigDecimal(reAgedInstallmentIndex));
+ created.setPenaltyCharges(calculatedPenalties.calculateValueBigDecimal(reAgedInstallmentIndex));
- created.setInterestAccrued(calculatedInterestAccrued.calculateValueBigDecimal(isLastRepaymentPeriod));
- created.setFeeAccrued(calculatedFeeAccrued.calculateValueBigDecimal(isLastRepaymentPeriod));
- created.setPenaltyAccrued(calculatedPenaltyAccrued.calculateValueBigDecimal(isLastRepaymentPeriod));
+ created.setInterestAccrued(calculatedInterestAccrued.calculateValueBigDecimal(reAgedInstallmentIndex));
+ created.setFeeAccrued(calculatedFeeAccrued.calculateValueBigDecimal(reAgedInstallmentIndex));
+ created.setPenaltyAccrued(calculatedPenaltyAccrued.calculateValueBigDecimal(reAgedInstallmentIndex));
- createChargeMappingsForInstallment(created, calculatedCharges, isLastRepaymentPeriod);
+ createChargeMappingsForInstallment(created, calculatedCharges, reAgedInstallmentIndex);
+ reAgedInstallmentIndex++;
}
+ created.setCreditedPrincipal(rp.getCreditedPrincipal().getAmount());
created.updateObligationsMet(currency, transactionDate);
installments.add(created);
}
@@ -3549,6 +3548,9 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
List<LoanRepaymentScheduleInstallment> installments = ctx.getInstallments();
LoanReAgeParameter loanReAgeParameter = loanTransaction.getLoanReAgeParameter();
LocalDate transactionDate = loanTransaction.getTransactionDate();
+ LocalDate originalMaturityDate = installments.stream()
+ .filter(i -> !i.isDownPayment() && !i.isAdditional() && i.getDueDate() != null)
+ .map(LoanRepaymentScheduleInstallment::getDueDate).max(LocalDate::compareTo).orElseThrow();
Integer numberOfReAgeInstallments = loanReAgeParameter.getNumberOfInstallments();
Integer installmentAmountInMultiplesOf = loanTransaction.getLoan().getLoanProductRelatedDetail()
@@ -3613,8 +3615,7 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
return res;
}).reduce(new BalancesWithPaidInAdvance(currency), BalancesWithPaidInAdvance::summarizerAccumulator);
- if (!balances.getPrincipal().isZero() || !balances.getInterest().isZero() || !balances.getFee().isZero()
- || !balances.getPenalty().isZero()) {
+ if (!transactionDate.isAfter(originalMaturityDate)) {
final LoanRepaymentScheduleInstallment earlyRepaidInstallment = LoanRepaymentScheduleInstallment.newReAgedInstallment(loan,
firstReAgeInstallmentProps.reAgedInstallmentNumber(), firstReAgeInstallmentProps.fromDate(), transactionDate,
@@ -3634,9 +3635,11 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
InstallmentProcessingHelper.addOneToInstallmentNumberFromInstallment(installments,
earlyRepaidInstallment.getInstallmentNumber());
- loan.getRepaymentScheduleInstallments().add(earlyRepaidInstallment);
+ installments.add(earlyRepaidInstallment);
}
+ // installment index which excludes earlyRepaidInstallment intallment index.
+ Integer reAgedInstallmentIndex = 0;
LoanRepaymentScheduleInstallment reAgedInstallment = LoanRepaymentScheduleInstallment.newReAgedInstallment(loan,
firstReAgeInstallmentProps.reAgedInstallmentNumber, firstReAgeInstallmentProps.fromDate, loanReAgeParameter.getStartDate(),
calculatedPrincipal.value().getAmount(), calculatedInterest.value().getAmount(), calculatedFees.value().getAmount(),
@@ -3644,27 +3647,27 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
calculatedFeeAccrued.value().getAmount(), calculatedPenaltyAccrued.value().getAmount());
reAgedInstallment = insertOrReplaceRelatedInstallment(installments, reAgedInstallment, currency, transactionDate);
- createChargeMappingsForInstallment(reAgedInstallment, calculatedCharges, false);
-
+ createChargeMappingsForInstallment(reAgedInstallment, calculatedCharges, reAgedInstallmentIndex);
+ reAgedInstallmentIndex++;
for (int i = 1; i < numberOfReAgeInstallments; i++) {
LocalDate calculatedDueDate = scheduledDateGenerator.getRepaymentPeriodDate(loanReAgeParameter.getFrequencyType(),
loanReAgeParameter.getFrequencyNumber(), reAgedInstallment.getDueDate());
calculateReAgedInstallmentDueDate(loanReAgeParameter, reAgedInstallment.getDueDate());
int nextReAgedInstallmentNumber = firstReAgeInstallmentProps.reAgedInstallmentNumber + i;
- boolean isLastInstallment = i + 1 == numberOfReAgeInstallments;
reAgedInstallment = LoanRepaymentScheduleInstallment.newReAgedInstallment(reAgedInstallment.getLoan(),
nextReAgedInstallmentNumber, reAgedInstallment.getDueDate(), calculatedDueDate,
- calculatedPrincipal.calculateValueBigDecimal(isLastInstallment),
- calculatedInterest.calculateValueBigDecimal(isLastInstallment),
- calculatedFees.calculateValueBigDecimal(isLastInstallment),
- calculatedPenalties.calculateValueBigDecimal(isLastInstallment),
- calculatedInterestAccrued.calculateValueBigDecimal(isLastInstallment),
- calculatedFeeAccrued.calculateValueBigDecimal(isLastInstallment),
- calculatedPenaltyAccrued.calculateValueBigDecimal(isLastInstallment));
+ calculatedPrincipal.calculateValueBigDecimal(reAgedInstallmentIndex),
+ calculatedInterest.calculateValueBigDecimal(reAgedInstallmentIndex),
+ calculatedFees.calculateValueBigDecimal(reAgedInstallmentIndex),
+ calculatedPenalties.calculateValueBigDecimal(reAgedInstallmentIndex),
+ calculatedInterestAccrued.calculateValueBigDecimal(reAgedInstallmentIndex),
+ calculatedFeeAccrued.calculateValueBigDecimal(reAgedInstallmentIndex),
+ calculatedPenaltyAccrued.calculateValueBigDecimal(reAgedInstallmentIndex));
reAgedInstallment = insertOrReplaceRelatedInstallment(installments, reAgedInstallment, currency, transactionDate);
- createChargeMappingsForInstallment(reAgedInstallment, calculatedCharges, isLastInstallment);
+ createChargeMappingsForInstallment(reAgedInstallment, calculatedCharges, reAgedInstallmentIndex);
+ reAgedInstallmentIndex++;
}
int lastReAgedInstallmentNumber = reAgedInstallment.getInstallmentNumber();
List<LoanRepaymentScheduleInstallment> toRemove = installments.stream()
@@ -3676,11 +3679,10 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
}
private void createChargeMappingsForInstallment(final LoanRepaymentScheduleInstallment installment,
- List<ReAgedChargeEqualAmortizationValues> reAgedChargeEqualAmortizationValues, boolean isLastInstallment) {
+ List<ReAgedChargeEqualAmortizationValues> reAgedChargeEqualAmortizationValues, Integer index) {
reAgedChargeEqualAmortizationValues.forEach(amortizationValue -> {
- installment.getInstallmentCharges()
- .add(new LoanInstallmentCharge(amortizationValue.equalAmortizationValues.calculateValueBigDecimal(isLastInstallment),
- amortizationValue.charge, installment));
+ installment.getInstallmentCharges().add(new LoanInstallmentCharge(
+ amortizationValue.equalAmortizationValues.calculateValueBigDecimal(index), amortizationValue.charge, installment));
});
}
diff --git a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/calc/ProgressiveEMICalculator.java b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/calc/ProgressiveEMICalculator.java
index 21a9af44c6..581198918a 100644
--- a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/calc/ProgressiveEMICalculator.java
+++ b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/calc/ProgressiveEMICalculator.java
@@ -857,7 +857,7 @@ public final class ProgressiveEMICalculator implements EMICalculator {
lastInterestPeriod.addBalanceCorrectionAmount(rp.getOutstandingPrincipal().negated());
}
rp.setEmi(rp.getTotalPaidAmount());
- rp.setOutstandingMoved(true);
+ rp.moveOutstandingDueToReAging();
});
}
@@ -1697,15 +1697,14 @@ public final class ProgressiveEMICalculator implements EMICalculator {
EqualAmortizationValues principalEAV = calculateAdjustedEqualAmortizationValues(principal,
principal.add(interest).add(feesPenaltiesOutstanding),
interestEAV.value().add(feesPenaltiesEqualAmortizationValues.value()), repaymentPeriods.size(), null, currency);
- RepaymentPeriod last = repaymentPeriods.getLast();
EqualAmortizationValues emiAEV = principalEAV.add(interestEAV);
- repaymentPeriods.forEach(rp -> {
- boolean isLast = last.equals(rp);
- rp.setReAgedInterest(interestEAV.calculateValue(isLast));
- Money emi = emiAEV.calculateValue(isLast);
+ for (int i = 0; i < repaymentPeriods.size(); i++) {
+ RepaymentPeriod rp = repaymentPeriods.get(i);
+ rp.setReAgedInterest(interestEAV.calculateValue(i));
+ Money emi = emiAEV.calculateValue(i);
rp.setEmi(emi);
rp.setOriginalEmi(emi);
- });
+ }
}
@Override
@@ -1722,40 +1721,46 @@ public final class ProgressiveEMICalculator implements EMICalculator {
LoanReAgeParameter reageParameter, Money feesPenaltiesOutstanding,
EqualAmortizationValues feesPenaltiesEqualAmortizationValues) {
LocalDate originalMaturityDate = interestSchedule.getMaturityDate();
- boolean isAfterOriginalMaturityDate = transactionDate.isAfter(originalMaturityDate);
List<RepaymentPeriod> reAgedRepaymentPeriods = new ArrayList<>(reageParameter.getNumberOfInstallments());
OutstandingDetails reAgeingAmounts = precalculateReAgeEqualAmortizationAmount(interestSchedule, transactionDate, reageParameter);
+ Money zero = interestSchedule.zero();
// calculate already paid balances from transaction date
OutstandingDetails paidBalancesFromTransactionDate = calculatePaidBalancesAfterDate(interestSchedule, transactionDate);
+ // find future chargebacks.
+ // find future balance corrections
+ Money futureCreditedPrincipals = interestSchedule.repaymentPeriods().stream()
+ .filter(rp -> !rp.getFromDate().isBefore(transactionDate)).filter(rp -> rp.getDueDate().isAfter(transactionDate))
+ .map(RepaymentPeriod::getCreditedPrincipal).reduce(zero, Money::add);
+
// set maturity date to transaction date and remove all repayment periods after it.
accelerateMaturityDateTo(interestSchedule, transactionDate);
+ addCredit(interestSchedule, transactionDate, futureCreditedPrincipals, zero);
+
// close all open repayment period while keep paid amounts
interestSchedule.repaymentPeriods().forEach(rp -> {
rp.getInterestPeriods().getLast()
.addCreditedInterestAmount(MathUtil.min(rp.getOutstandingInterest(), rp.getCreditedInterest(), false).negated());
rp.setEmi(rp.getTotalPaidAmount());
- rp.setOutstandingMoved(true);
+ rp.moveOutstandingDueToReAging();
});
// stop calculate unrecognised interest at this point because all
interestSchedule.getLastRepaymentPeriod().setNoUnrecognisedInterest(true);
- if (!paidBalancesFromTransactionDate.getOutstandingInterest().isZero()
- || !paidBalancesFromTransactionDate.getOutstandingPrincipal().isZero()) {
+ if (!originalMaturityDate.isBefore(transactionDate)) {
createRepaymentPeriodForEarlyRepaidAmountsDuringReAgeing(interestSchedule,
paidBalancesFromTransactionDate.getOutstandingPrincipal(), paidBalancesFromTransactionDate.getOutstandingInterest(),
true);
- addFirstReAgedPeriod(interestSchedule, interestSchedule.getLastRepaymentPeriod());
}
- updateModelForReageEqualAmortization(interestSchedule, reageParameter, reAgedRepaymentPeriods, isAfterOriginalMaturityDate);
+ updateModelForReageEqualAmortization(interestSchedule, reageParameter, reAgedRepaymentPeriods);
updateEMIForReAgeEqualAmortization(reAgedRepaymentPeriods, reAgeingAmounts.getOutstandingPrincipal(),
reAgeingAmounts.getOutstandingInterest(), feesPenaltiesOutstanding, feesPenaltiesEqualAmortizationValues,
- interestSchedule.zero().getCurrency());
+ zero.getCurrency());
calculateOutstandingBalance(interestSchedule);
@@ -1767,31 +1772,17 @@ public final class ProgressiveEMICalculator implements EMICalculator {
}
private void updateModelForReageEqualAmortization(ProgressiveLoanInterestScheduleModel interestSchedule,
- LoanReAgeParameter reageParameter, List<RepaymentPeriod> reAgedRepaymentPeriods, boolean isAfterOriginalMaturityDate) {
+ LoanReAgeParameter reageParameter, List<RepaymentPeriod> reAgedRepaymentPeriods) {
int numberOfInstallmentsToAdd = reageParameter.getNumberOfInstallments();
LocalDate toDate = reageParameter.getStartDate();
RepaymentPeriod previous = interestSchedule.getLastRepaymentPeriod();
int frequency = reageParameter.getFrequencyNumber();
PeriodFrequencyType frequencyType = reageParameter.getFrequencyType();
- if (!isAfterOriginalMaturityDate) {
- // merge first reaged period
- RepaymentPeriod firstReAgedPeriod = interestSchedule.getLastRepaymentPeriod();
- firstReAgedPeriod.setDueDate(toDate);
- firstReAgedPeriod.getLastInterestPeriod().setDueDate(toDate);
- firstReAgedPeriod.setReAged(true);
- firstReAgedPeriod.getPrevious().ifPresent(prev -> prev.setNoUnrecognisedInterest(true));
- reAgedRepaymentPeriods.add(firstReAgedPeriod);
-
- // update params for next reage repayment period calculation
- numberOfInstallmentsToAdd--;
- toDate = scheduledDateGenerator.getRepaymentPeriodDate(frequencyType, frequency, toDate);
- }
-
// insert new reaged repayment periods
for (int i = 0; i < numberOfInstallmentsToAdd; i++) {
RepaymentPeriod repaymentPeriod = RepaymentPeriod.create(previous, previous.getDueDate(), toDate, interestSchedule.zero(),
- previous.getMc(), previous.getLoanProductRelatedDetail());
+ interestSchedule.mc(), previous.getLoanProductRelatedDetail());
repaymentPeriod.setTotalCapitalizedIncomeAmount(previous.getTotalCapitalizedIncomeAmount());
repaymentPeriod.setTotalDisbursedAmount(previous.getTotalDisbursedAmount());
repaymentPeriod.setReAged(true);
@@ -1818,14 +1809,6 @@ public final class ProgressiveEMICalculator implements EMICalculator {
calculateRateFactorForRepaymentPeriod(targetPeriod, interestSchedule);
}
- private static void addFirstReAgedPeriod(ProgressiveLoanInterestScheduleModel interestSchedule, RepaymentPeriod targetPeriod) {
- RepaymentPeriod repaymentPeriodToInsert = RepaymentPeriod.create(targetPeriod, targetPeriod.getDueDate(),
- interestSchedule.getMaturityDate(), interestSchedule.zero(), interestSchedule.mc(),
- interestSchedule.loanProductRelatedDetail());
- repaymentPeriodToInsert.setReAged(true);
- interestSchedule.repaymentPeriods().add(repaymentPeriodToInsert);
- }
-
private OutstandingDetails calculatePaidBalancesAfterDate(ProgressiveLoanInterestScheduleModel interestSchedule,
LocalDate transactionDate) {
Money principal = interestSchedule.repaymentPeriods().stream().filter(rp -> !rp.getDueDate().isBefore(transactionDate))
@@ -1844,9 +1827,9 @@ public final class ProgressiveEMICalculator implements EMICalculator {
equalMonthlyValue = Money.roundToMultiplesOf(equalMonthlyValue, installmentAmountInMultiplesOf);
}
Money adjustmentForLastInstallment = totalOutstanding.minus(equalMonthlyValue.multipliedBy(numberOfInstallments));
- return new EqualAmortizationValues(equalMonthlyValue, adjustmentForLastInstallment);
+ return new EqualAmortizationValues(totalOutstanding, numberOfInstallments, equalMonthlyValue, adjustmentForLastInstallment);
}
- return new EqualAmortizationValues(Money.zero(currency), Money.zero(currency));
+ return new EqualAmortizationValues(totalOutstanding, numberOfInstallments, Money.zero(currency), Money.zero(currency));
}
@Override
@@ -1857,6 +1840,6 @@ public final class ProgressiveEMICalculator implements EMICalculator {
installmentAmountInMultiplesOf, currency);
Money value = calculatedEMI.value().minus(sumOfOtherEqualAmortizationValues);
Money adjust = outstanding.minus(value.multipliedBy(numberOfInstallments));
- return new EqualAmortizationValues(value, adjust);
+ return new EqualAmortizationValues(outstanding, numberOfInstallments, value, adjust);
}
}
diff --git a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/calc/data/EqualAmortizationValues.java b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/calc/data/EqualAmortizationValues.java
index b97b99e20b..1d4331969c 100644
--- a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/calc/data/EqualAmortizationValues.java
+++ b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/calc/data/EqualAmortizationValues.java
@@ -19,23 +19,38 @@
package org.apache.fineract.portfolio.loanproduct.calc.data;
import java.math.BigDecimal;
+import java.util.Objects;
import org.apache.fineract.organisation.monetary.domain.Money;
-public record EqualAmortizationValues(Money value, Money adjustment) {
+public record EqualAmortizationValues(Money totalOutstanding, Integer numberOfInstallments, Money value, Money adjustment) {
public Money getAdjustedValue() {
return value.add(adjustment);
}
- public Money calculateValue(boolean isLast) {
- return (isLast ? getAdjustedValue() : value);
+ /**
+ * calculates value according to the index of the installments
+ *
+ * @param index
+ * index accepted 0 to number of (installments - 1)
+ * @return calculated value for the given index
+ */
+ public Money calculateValue(Integer index) {
+ if (getAdjustedValue().isLessThanZero()) {
+ return totalOutstanding.minus(value.multipliedBy(index + 1)).isLessThanZero() ? value.zero() : value;
+ }
+ return (index == numberOfInstallments - 1 ? getAdjustedValue() : value);
}
- public BigDecimal calculateValueBigDecimal(boolean isLast) {
- return calculateValue(isLast).getAmount();
+ public BigDecimal calculateValueBigDecimal(Integer index) {
+ return calculateValue(index).getAmount();
}
public EqualAmortizationValues add(EqualAmortizationValues other) {
- return new EqualAmortizationValues(value.add(other.value), adjustment.add(other.adjustment));
+ if (!Objects.equals(numberOfInstallments, other.numberOfInstallments)) {
+ throw new RuntimeException("Incompatible EqualAmortizationValues. numberOfInstallments parameter should match.");
+ }
+ return new EqualAmortizationValues(totalOutstanding.add(other.totalOutstanding), numberOfInstallments, value.add(other.value),
+ adjustment.add(other.adjustment));
}
}
diff --git a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/calc/data/RepaymentPeriod.java b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/calc/data/RepaymentPeriod.java
index 61b9bb7d6d..755f47a74d 100644
--- a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/calc/data/RepaymentPeriod.java
+++ b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/calc/data/RepaymentPeriod.java
@@ -91,8 +91,10 @@ public class RepaymentPeriod {
@Getter
@Setter
- private boolean isOutstandingMoved = false;
-
+ private Money creditedPrincipalMovedDueReAge;
+ @Getter
+ @Setter
+ private Money creditedInterestMovedDueReAge;
@Setter
@Getter
private boolean noUnrecognisedInterest;
@@ -125,6 +127,8 @@ public class RepaymentPeriod {
this.reAged = reAged;
this.reAgedEarlyRepaymentHolder = reAgedEarlyRepaymentHolder;
this.reAgedInterest = reAgedInterest;
+ this.creditedInterestMovedDueReAge = Money.zero(loanProductRelatedDetail.getCurrencyData(), mc);
+ this.creditedInterestMovedDueReAge = Money.zero(loanProductRelatedDetail.getCurrencyData(), mc);
}
public static RepaymentPeriod empty(RepaymentPeriod previous, MathContext mc, ILoanConfigurationDetails loanProductRelatedDetail) {
@@ -148,7 +152,8 @@ public class RepaymentPeriod {
repaymentPeriod.getPaidPrincipal(), repaymentPeriod.getPaidInterest(), repaymentPeriod.getFutureUnrecognizedInterest(), mc,
repaymentPeriod.getLoanProductRelatedDetail(), repaymentPeriod.isNoUnrecognisedInterest(), repaymentPeriod.isReAged(),
repaymentPeriod.isReAgedEarlyRepaymentHolder(), repaymentPeriod.getReAgedInterest());
- newRepaymentPeriod.setOutstandingMoved(repaymentPeriod.isOutstandingMoved());
+ newRepaymentPeriod.setCreditedPrincipalMovedDueReAge(repaymentPeriod.getCreditedPrincipalMovedDueReAge());
+ newRepaymentPeriod.setCreditedInterestMovedDueReAge(repaymentPeriod.getCreditedInterestMovedDueReAge());
// 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 +167,8 @@ 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.setOutstandingMoved(repaymentPeriod.isOutstandingMoved());
+ newRepaymentPeriod.setCreditedPrincipalMovedDueReAge(repaymentPeriod.getCreditedPrincipalMovedDueReAge());
+ newRepaymentPeriod.setCreditedInterestMovedDueReAge(repaymentPeriod.getCreditedInterestMovedDueReAge());
// 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,7 +317,8 @@ public class RepaymentPeriod {
* @return
*/
public Money getTotalCreditedAmount() {
- return isOutstandingMoved ? Money.zero(getCurrency(), getMc()) : getCreditedPrincipal().plus(getCreditedInterest(), getMc());
+ return getCreditedPrincipal().plus(getCreditedInterest(), getMc()).minus(getCreditedInterestMovedDueReAge(), getMc())
+ .minus(getCreditedPrincipalMovedDueReAge(), getMc());
}
/**
@@ -480,4 +487,9 @@ public class RepaymentPeriod {
public Money getTotalCapitalizedIncomeAmount() {
return MathUtil.nullToZero(totalCapitalizedIncomeAmount, getCurrency(), getMc());
}
+
+ public void moveOutstandingDueToReAging() {
+ setCreditedPrincipalMovedDueReAge(getCreditedPrincipal());
+ setCreditedInterestMovedDueReAge(getCreditedInterest());
+ }
}
diff --git a/fineract-progressive-loan/src/test/java/org/apache/fineract/portfolio/loanproduct/calc/ProgressiveEMICalculatorTest.java b/fineract-progressive-loan/src/test/java/org/apache/fineract/portfolio/loanproduct/calc/ProgressiveEMICalculatorTest.java
index eef9f3d00b..819eb52b1f 100644
--- a/fineract-progressive-loan/src/test/java/org/apache/fineract/portfolio/loanproduct/calc/ProgressiveEMICalculatorTest.java
+++ b/fineract-progressive-loan/src/test/java/org/apache/fineract/portfolio/loanproduct/calc/ProgressiveEMICalculatorTest.java
@@ -31,6 +31,7 @@ import lombok.extern.slf4j.Slf4j;
import org.apache.fineract.infrastructure.core.service.DateUtils;
import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil;
import org.apache.fineract.organisation.monetary.data.CurrencyData;
+import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
import org.apache.fineract.organisation.monetary.domain.Money;
import org.apache.fineract.organisation.monetary.domain.MoneyHelper;
import org.apache.fineract.portfolio.common.domain.DaysInMonthType;
@@ -4239,6 +4240,147 @@ class ProgressiveEMICalculatorTest {
@Nested
public class ReAgeEqualAmortization {
+ private ProgressiveLoanInterestScheduleModel generateSchedule() {
+ final List<LoanScheduleModelRepaymentPeriod> expectedRepaymentPeriods = new ArrayList<>();
+
+ expectedRepaymentPeriods.add(repayment(1, LocalDate.of(2024, 1, 1), LocalDate.of(2024, 2, 1)));
+ expectedRepaymentPeriods.add(repayment(2, LocalDate.of(2024, 2, 1), LocalDate.of(2024, 3, 1)));
+ expectedRepaymentPeriods.add(repayment(3, LocalDate.of(2024, 3, 1), LocalDate.of(2024, 4, 1)));
+ expectedRepaymentPeriods.add(repayment(4, LocalDate.of(2024, 4, 1), LocalDate.of(2024, 5, 1)));
+ expectedRepaymentPeriods.add(repayment(5, LocalDate.of(2024, 5, 1), LocalDate.of(2024, 6, 1)));
+ expectedRepaymentPeriods.add(repayment(6, LocalDate.of(2024, 6, 1), LocalDate.of(2024, 7, 1)));
+
+ final BigDecimal interestRate = BigDecimal.valueOf(15.678);
+ final Integer installmentAmountInMultiplesOf = null;
+
+ Mockito.when(loanProductRelatedDetail.getAnnualNominalInterestRate()).thenReturn(interestRate);
+ Mockito.when(loanProductRelatedDetail.getDaysInYearType()).thenReturn(DaysInYearType.DAYS_365.getValue());
+ Mockito.when(loanProductRelatedDetail.getDaysInMonthType()).thenReturn(DaysInMonthType.ACTUAL.getValue());
+ Mockito.when(loanProductRelatedDetail.getRepaymentPeriodFrequencyType()).thenReturn(PeriodFrequencyType.MONTHS);
+ Mockito.when(loanProductRelatedDetail.getRepayEvery()).thenReturn(1);
+ Mockito.when(loanProductRelatedDetail.getCurrencyData()).thenReturn(currency);
+
+ final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generatePeriodInterestScheduleModel(
+ expectedRepaymentPeriods, loanProductRelatedDetail, installmentAmountInMultiplesOf, mc);
+
+ final Money disbursedAmount = toMoney(100.0);
+ emiCalculator.addDisbursement(interestSchedule, LocalDate.of(2024, 1, 1), disbursedAmount);
+
+ checkPeriod(interestSchedule, 0, 0, 17.43, 0.0, 0.0, 1.33, 16.1, 83.9);
+ checkPeriod(interestSchedule, 0, 1, 17.43, 0.013315561644, 1.3315561644, 1.33, 16.1, 83.9);
+ checkPeriod(interestSchedule, 1, 0, 17.43, 0.012456493151, 1.04509977537, 1.05, 16.38, 67.52);
+ checkPeriod(interestSchedule, 2, 0, 17.43, 0.013315561644, 0.899066722202, 0.90, 16.53, 50.99);
+ checkPeriod(interestSchedule, 3, 0, 17.43, 0.012886027397, 0.657058536972, 0.66, 16.77, 34.22);
+ checkPeriod(interestSchedule, 4, 0, 17.43, 0.013315561644, 0.455658519458, 0.46, 16.97, 17.25);
+ checkPeriod(interestSchedule, 5, 0, 17.47, 0.012886027397, 0.222283972598, 0.22, 17.25, 0.0);
+ return interestSchedule;
+ }
+
+ @Test
+ public void test_chargeBackOn2ndRP_ReAgeingOn1stRPsDueDate_EQUAL_AMORTIZATION_FULL_INTEREST() {
+ ProgressiveLoanInterestScheduleModel interestSchedule = generateSchedule();
+
+ emiCalculator.payPrincipal(interestSchedule, LocalDate.of(2024, 1, 1), LocalDate.of(2024, 2, 1), LocalDate.of(2024, 2, 1),
+ Money.of(currency, BigDecimal.valueOf(16.1)));
+ emiCalculator.payInterest(interestSchedule, LocalDate.of(2024, 1, 1), LocalDate.of(2024, 2, 1), LocalDate.of(2024, 2, 1),
+ Money.of(currency, BigDecimal.valueOf(1.33)));
+
+ emiCalculator.creditPrincipal(interestSchedule, LocalDate.of(2024, 2, 1), Money.of(currency, BigDecimal.valueOf(17.43)));
+
+ // No repayment no interest recalculation
+ LocalDate reAgingStartDate = LocalDate.of(2024, 2, 1);
+ LocalDate transactionDate = LocalDate.of(2024, 2, 1);
+
+ OutstandingDetails outstandingAmountsTillDate = emiCalculator.getOutstandingAmountsTillDate(interestSchedule,
+ interestSchedule.getMaturityDate());
+
+ LoanReAgeParameter reageParameter = new LoanReAgeParameter(null, PeriodFrequencyType.MONTHS, 1, reAgingStartDate, 6,
+ LoanReAgeInterestHandlingType.EQUAL_AMORTIZATION_FULL_INTEREST, null);
+
+ // Update the existing model with re-aged periods
+ emiCalculator.reAgeEqualAmortization(interestSchedule, transactionDate, reageParameter, Money.zero(currency),
+ new EqualAmortizationValues(Money.zero(currency), 6, Money.zero(currency), Money.zero(currency)));
+
+ OutstandingDetails outstandingAmountsTillDateAfterReage = emiCalculator.getOutstandingAmountsTillDate(interestSchedule,
+ interestSchedule.getMaturityDate());
+
+ Assertions.assertEquals(outstandingAmountsTillDate.getOutstandingInterest().getAmount(),
+ outstandingAmountsTillDateAfterReage.getOutstandingInterest().getAmount());
+ Assertions.assertEquals(outstandingAmountsTillDate.getOutstandingPrincipal().getAmount(),
+ outstandingAmountsTillDateAfterReage.getOutstandingPrincipal().getAmount());
+
+ }
+
+ @Test
+ public void test_chargeBackOn2ndRP_ReAgeingOn2stRPsDueDate_EQUAL_AMORTIZATION_FULL_INTEREST() {
+ ProgressiveLoanInterestScheduleModel interestSchedule = generateSchedule();
+
+ emiCalculator.payPrincipal(interestSchedule, LocalDate.of(2024, 1, 1), LocalDate.of(2024, 2, 1), LocalDate.of(2024, 2, 1),
+ Money.of(currency, BigDecimal.valueOf(16.1)));
+ emiCalculator.payInterest(interestSchedule, LocalDate.of(2024, 1, 1), LocalDate.of(2024, 2, 1), LocalDate.of(2024, 2, 1),
+ Money.of(currency, BigDecimal.valueOf(1.33)));
+
+ emiCalculator.creditPrincipal(interestSchedule, LocalDate.of(2024, 2, 1), Money.of(currency, BigDecimal.valueOf(17.43)));
+
+ // No repayment no interest recalculation
+ LocalDate reAgingStartDate = LocalDate.of(2024, 3, 1);
+ LocalDate transactionDate = LocalDate.of(2024, 3, 1);
+
+ OutstandingDetails outstandingAmountsTillDate = emiCalculator.getOutstandingAmountsTillDate(interestSchedule,
+ interestSchedule.getMaturityDate());
+
+ LoanReAgeParameter reageParameter = new LoanReAgeParameter(null, PeriodFrequencyType.MONTHS, 1, reAgingStartDate, 6,
+ LoanReAgeInterestHandlingType.EQUAL_AMORTIZATION_FULL_INTEREST, null);
+
+ // Update the existing model with re-aged periods
+ emiCalculator.reAgeEqualAmortization(interestSchedule, transactionDate, reageParameter, Money.zero(currency),
+ new EqualAmortizationValues(Money.zero(currency), 6, Money.zero(currency), Money.zero(currency)));
+
+ OutstandingDetails outstandingAmountsTillDateAfterReage = emiCalculator.getOutstandingAmountsTillDate(interestSchedule,
+ interestSchedule.getMaturityDate());
+
+ Assertions.assertEquals(outstandingAmountsTillDate.getOutstandingInterest().getAmount(),
+ outstandingAmountsTillDateAfterReage.getOutstandingInterest().getAmount());
+ Assertions.assertEquals(outstandingAmountsTillDate.getOutstandingPrincipal().getAmount(),
+ outstandingAmountsTillDateAfterReage.getOutstandingPrincipal().getAmount());
+
+ }
+
+ @Test
+ public void test_chargeBackOn2ndRP_ReAgeingDuring2stRP_EQUAL_AMORTIZATION_FULL_INTEREST() {
+ ProgressiveLoanInterestScheduleModel interestSchedule = generateSchedule();
+
+ emiCalculator.payPrincipal(interestSchedule, LocalDate.of(2024, 1, 1), LocalDate.of(2024, 2, 1), LocalDate.of(2024, 2, 1),
+ Money.of(currency, BigDecimal.valueOf(16.1)));
+ emiCalculator.payInterest(interestSchedule, LocalDate.of(2024, 1, 1), LocalDate.of(2024, 2, 1), LocalDate.of(2024, 2, 1),
+ Money.of(currency, BigDecimal.valueOf(1.33)));
+
+ emiCalculator.creditPrincipal(interestSchedule, LocalDate.of(2024, 2, 1), Money.of(currency, BigDecimal.valueOf(17.43)));
+
+ // No repayment no interest recalculation
+ LocalDate reAgingStartDate = LocalDate.of(2024, 2, 10);
+ LocalDate transactionDate = LocalDate.of(2024, 2, 10);
+
+ OutstandingDetails outstandingAmountsTillDate = emiCalculator.getOutstandingAmountsTillDate(interestSchedule,
+ interestSchedule.getMaturityDate());
+
+ LoanReAgeParameter reageParameter = new LoanReAgeParameter(null, PeriodFrequencyType.MONTHS, 1, reAgingStartDate, 6,
+ LoanReAgeInterestHandlingType.EQUAL_AMORTIZATION_FULL_INTEREST, null);
+
+ // Update the existing model with re-aged periods
+ emiCalculator.reAgeEqualAmortization(interestSchedule, transactionDate, reageParameter, Money.zero(currency),
+ new EqualAmortizationValues(Money.zero(currency), 6, Money.zero(currency), Money.zero(currency)));
+
+ OutstandingDetails outstandingAmountsTillDateAfterReage = emiCalculator.getOutstandingAmountsTillDate(interestSchedule,
+ interestSchedule.getMaturityDate());
+
+ Assertions.assertEquals(outstandingAmountsTillDate.getOutstandingInterest().getAmount(),
+ outstandingAmountsTillDateAfterReage.getOutstandingInterest().getAmount());
+ Assertions.assertEquals(outstandingAmountsTillDate.getOutstandingPrincipal().getAmount(),
+ outstandingAmountsTillDateAfterReage.getOutstandingPrincipal().getAmount());
+
+ }
+
@Test
public void test_transactionInMiddleOfPeriod_EQUAL_AMORTIZATION_FULL_INTEREST_noTransactionTilDate_noInterestRecalc() {
final List<LoanScheduleModelRepaymentPeriod> expectedRepaymentPeriods = new ArrayList<>();
@@ -4291,7 +4433,7 @@ class ProgressiveEMICalculatorTest {
// Update the existing model with re-aged periods
emiCalculator.reAgeEqualAmortization(interestSchedule, loanTransaction.getTransactionDate(), reageParameter,
- Money.zero(currency), new EqualAmortizationValues(Money.zero(currency), Money.zero(currency)));
+ Money.zero(currency), new EqualAmortizationValues(Money.zero(currency), 6, Money.zero(currency), Money.zero(currency)));
OutstandingDetails outstandingAmountsTillDateAfterReage = emiCalculator.getOutstandingAmountsTillDate(interestSchedule,
interestSchedule.getMaturityDate());
@@ -4380,7 +4522,7 @@ class ProgressiveEMICalculatorTest {
// Update the existing model with re-aged periods
emiCalculator.reAgeEqualAmortization(interestSchedule, loanTransaction.getTransactionDate(), reageParameter,
- Money.zero(currency), new EqualAmortizationValues(Money.zero(currency), Money.zero(currency)));
+ Money.zero(currency), new EqualAmortizationValues(Money.zero(currency), 6, Money.zero(currency), Money.zero(currency)));
OutstandingDetails outstandingAmountsTillDateAfterReage = emiCalculator.getOutstandingAmountsTillDate(interestSchedule,
interestSchedule.getMaturityDate());
@@ -4439,7 +4581,7 @@ class ProgressiveEMICalculatorTest {
// Update the existing model with re-aged periods
emiCalculator.reAgeEqualAmortization(interestSchedule, transactionDate, reageParameter, Money.zero(currency),
- new EqualAmortizationValues(Money.zero(currency), Money.zero(currency)));
+ new EqualAmortizationValues(Money.zero(currency), 6, Money.zero(currency), Money.zero(currency)));
OutstandingDetails outstandingAmountsTillDateAfterReage = emiCalculator.getOutstandingAmountsTillDate(interestSchedule,
interestSchedule.getMaturityDate());
@@ -4499,7 +4641,7 @@ class ProgressiveEMICalculatorTest {
// Update the existing model with re-aged periods
emiCalculator.reAgeEqualAmortization(interestSchedule, transactionDate, reageParameter, Money.zero(currency),
- new EqualAmortizationValues(Money.zero(currency), Money.zero(currency)));
+ new EqualAmortizationValues(Money.zero(currency), 6, Money.zero(currency), Money.zero(currency)));
OutstandingDetails outstandingAmountsTillDateAfterReage = emiCalculator.getOutstandingAmountsTillDate(interestSchedule,
interestSchedule.getMaturityDate());
@@ -4582,7 +4724,7 @@ class ProgressiveEMICalculatorTest {
// Update the existing model with re-aged periods
emiCalculator.reAgeEqualAmortization(interestSchedule, transactionDate, reageParameter, Money.zero(currency),
- new EqualAmortizationValues(Money.zero(currency), Money.zero(currency)));
+ new EqualAmortizationValues(Money.zero(currency), 6, Money.zero(currency), Money.zero(currency)));
OutstandingDetails outstandingAmountsTillDateAfterReage = emiCalculator.getOutstandingAmountsTillDate(interestSchedule,
interestSchedule.getMaturityDate());
@@ -4665,7 +4807,7 @@ class ProgressiveEMICalculatorTest {
// Update the existing model with re-aged periods
emiCalculator.reAgeEqualAmortization(interestSchedule, transactionDate, reageParameter, Money.zero(currency),
- new EqualAmortizationValues(Money.zero(currency), Money.zero(currency)));
+ new EqualAmortizationValues(Money.zero(currency), 6, Money.zero(currency), Money.zero(currency)));
OutstandingDetails outstandingAmountsTillDateAfterReage = emiCalculator.getOutstandingAmountsTillDate(interestSchedule,
interestSchedule.getMaturityDate());
@@ -4738,7 +4880,7 @@ class ProgressiveEMICalculatorTest {
// Update the existing model with re-aged periods
emiCalculator.reAgeEqualAmortization(interestSchedule, transactionDate, reageParameter, Money.zero(currency),
- new EqualAmortizationValues(Money.zero(currency), Money.zero(currency)));
+ new EqualAmortizationValues(Money.zero(currency), 6, Money.zero(currency), Money.zero(currency)));
OutstandingDetails outstandingAmountsTillDateAfterReage = emiCalculator.getOutstandingAmountsTillDate(interestSchedule,
interestSchedule.getMaturityDate());
@@ -4811,7 +4953,7 @@ class ProgressiveEMICalculatorTest {
// Update the existing model with re-aged periods
emiCalculator.reAgeEqualAmortization(interestSchedule, transactionDate, reageParameter, Money.zero(currency),
- new EqualAmortizationValues(Money.zero(currency), Money.zero(currency)));
+ new EqualAmortizationValues(Money.zero(currency), 6, Money.zero(currency), Money.zero(currency)));
OutstandingDetails outstandingAmountsTillDateAfterReage = emiCalculator.getOutstandingAmountsTillDate(interestSchedule,
interestSchedule.getMaturityDate());
@@ -4824,6 +4966,49 @@ class ProgressiveEMICalculatorTest {
}
}
+ @Nested
+ public class EqualAmortizationValue {
+
+ @Test
+ public void test_AmortizationTotalIsLessThanInstallmentNumber() {
+ EqualAmortizationValues actual = emiCalculator.calculateEqualAmortizationValues(Money.of(currency, BigDecimal.valueOf(0.04)), 6,
+ null, MonetaryCurrency.fromCurrencyData(currency));
+ Assertions.assertEquals(0.01, actual.calculateValueBigDecimal(0).doubleValue());
+ Assertions.assertEquals(0.01, actual.calculateValueBigDecimal(1).doubleValue());
+ Assertions.assertEquals(0.01, actual.calculateValueBigDecimal(2).doubleValue());
+ Assertions.assertEquals(0.01, actual.calculateValueBigDecimal(3).doubleValue());
+ Assertions.assertEquals(0.0, actual.calculateValueBigDecimal(4).doubleValue());
+ Assertions.assertEquals(0.0, actual.calculateValueBigDecimal(5).doubleValue());
+
+ }
+
+ @Test
+ public void test_AmortizationIsJustBiggerThanInstallmentNumber() {
+ EqualAmortizationValues actual = emiCalculator.calculateEqualAmortizationValues(Money.of(currency, BigDecimal.valueOf(0.07)), 6,
+ null, MonetaryCurrency.fromCurrencyData(currency));
+ Assertions.assertEquals(0.01, actual.calculateValueBigDecimal(0).doubleValue());
+ Assertions.assertEquals(0.01, actual.calculateValueBigDecimal(1).doubleValue());
+ Assertions.assertEquals(0.01, actual.calculateValueBigDecimal(2).doubleValue());
+ Assertions.assertEquals(0.01, actual.calculateValueBigDecimal(3).doubleValue());
+ Assertions.assertEquals(0.01, actual.calculateValueBigDecimal(4).doubleValue());
+ Assertions.assertEquals(0.02, actual.calculateValueBigDecimal(5).doubleValue());
+
+ }
+
+ @Test
+ public void test_AmortizationNonEdgeCase() {
+ EqualAmortizationValues actual = emiCalculator.calculateEqualAmortizationValues(Money.of(currency, BigDecimal.valueOf(0.59)), 6,
+ null, MonetaryCurrency.fromCurrencyData(currency));
+ Assertions.assertEquals(0.1, actual.calculateValueBigDecimal(0).doubleValue());
+ Assertions.assertEquals(0.1, actual.calculateValueBigDecimal(1).doubleValue());
+ Assertions.assertEquals(0.1, actual.calculateValueBigDecimal(2).doubleValue());
+ Assertions.assertEquals(0.1, actual.calculateValueBigDecimal(3).doubleValue());
+ Assertions.assertEquals(0.1, actual.calculateValueBigDecimal(4).doubleValue());
+ Assertions.assertEquals(0.09, actual.calculateValueBigDecimal(5).doubleValue());
+
+ }
+ }
+
// utilities
private List<LoanScheduleModelRepaymentPeriod> generateExpectedRepaymentPeriods(LocalDate disbursementDate) {
return switch (loanProductRelatedDetail.getRepaymentPeriodFrequencyType()) {