FINERACT-2354: [BE] chargeback handling with backdated re-age for last adjustment strategy with equal amortization re-aging behaviour
This commit is contained in:
+4
-2
@@ -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) {
|
||||
|
||||
+34
-32
@@ -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));
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
+24
-41
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
+21
-6
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
+17
-5
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
+193
-8
@@ -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()) {
|
||||
|
||||
Reference in New Issue
Block a user