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 jakarta.persistence.Table;
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
import java.time.LocalDate;
|
import java.time.LocalDate;
|
||||||
|
import java.util.Comparator;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
@@ -518,8 +519,9 @@ public class LoanRepaymentScheduleInstallment extends AbstractAuditableWithUTCDa
|
|||||||
return this.installmentNumber.compareTo(o.installmentNumber);
|
return this.installmentNumber.compareTo(o.installmentNumber);
|
||||||
}
|
}
|
||||||
|
|
||||||
public int compareToByDueDate(LoanRepaymentScheduleInstallment o) {
|
public int compareToByFromDueDate(LoanRepaymentScheduleInstallment o) {
|
||||||
return this.dueDate.compareTo(o.dueDate);
|
return Comparator.comparing(LoanRepaymentScheduleInstallment::getDueDate)
|
||||||
|
.thenComparing(LoanRepaymentScheduleInstallment::getFromDate).compare(this, o);
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isPrincipalNotCompleted(final MonetaryCurrency currency) {
|
public boolean isPrincipalNotCompleted(final MonetaryCurrency currency) {
|
||||||
|
|||||||
+34
-32
@@ -3171,7 +3171,7 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
|
|||||||
private void reprocessInstallments(final List<LoanRepaymentScheduleInstallment> installments) {
|
private void reprocessInstallments(final List<LoanRepaymentScheduleInstallment> installments) {
|
||||||
final AtomicInteger counter = new AtomicInteger(1);
|
final AtomicInteger counter = new AtomicInteger(1);
|
||||||
final AtomicReference<LocalDate> previousDueDate = new AtomicReference<>(null);
|
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());
|
i.updateInstallmentNumber(counter.getAndIncrement());
|
||||||
final LocalDate prev = previousDueDate.get();
|
final LocalDate prev = previousDueDate.get();
|
||||||
if (prev != null && (i.isAdditional() || i.isReAged())) {
|
if (prev != null && (i.isAdditional() || i.isReAged())) {
|
||||||
@@ -3179,6 +3179,7 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
|
|||||||
}
|
}
|
||||||
previousDueDate.set(i.getDueDate());
|
previousDueDate.set(i.getDueDate());
|
||||||
});
|
});
|
||||||
|
installments.sort(LoanRepaymentScheduleInstallment::compareToByFromDueDate);
|
||||||
}
|
}
|
||||||
|
|
||||||
private LocalDate calculateReAgedInstallmentDueDate(final LoanReAgeParameter reAgeParameter, final LocalDate dueDate) {
|
private LocalDate calculateReAgedInstallmentDueDate(final LoanReAgeParameter reAgeParameter, final LocalDate dueDate) {
|
||||||
@@ -3479,12 +3480,8 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
|
|||||||
BalancesWithPaidInAdvance paidInAdvanceBalances = liftEarlyRepaidBalances(installments, transactionDate, currency,
|
BalancesWithPaidInAdvance paidInAdvanceBalances = liftEarlyRepaidBalances(installments, transactionDate, currency,
|
||||||
ctx.getAlreadyProcessedTransactions());
|
ctx.getAlreadyProcessedTransactions());
|
||||||
|
|
||||||
// TODO add as Parameter here: paidInAdvanceBalances.getAggregatedFeeChargesPortion().isGreaterThanZero() ||
|
|
||||||
// paidInAdvanceBalances.getAggregatedPenaltyChargesPortion().isGreaterThanZero()
|
|
||||||
emiCalculator.reAgeEqualAmortization(model, transactionDate, loanReAgeParameter,
|
emiCalculator.reAgeEqualAmortization(model, transactionDate, loanReAgeParameter,
|
||||||
outstandingBalances.fees.add(outstandingBalances.penalties),
|
outstandingBalances.fees.add(outstandingBalances.penalties), calculatedFees.add(calculatedPenalties));
|
||||||
new EqualAmortizationValues(calculatedFees.value().add(calculatedPenalties.value()),
|
|
||||||
calculatedFees.adjustment().add(calculatedPenalties.adjustment())));
|
|
||||||
|
|
||||||
installments.removeIf(i -> (i.getInstallmentNumber() != null && !i.isDownPayment() && !i.getDueDate().isBefore(transactionDate)
|
installments.removeIf(i -> (i.getInstallmentNumber() != null && !i.isDownPayment() && !i.getDueDate().isBefore(transactionDate)
|
||||||
&& !i.isAdditional()) || (!i.getDueDate().isAfter(model.getMaturityDate()) && i.isAdditional()));
|
&& !i.isAdditional()) || (!i.getDueDate().isAfter(model.getMaturityDate()) && i.isAdditional()));
|
||||||
@@ -3494,6 +3491,7 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
|
|||||||
i.setInstallmentNumber(model.repaymentPeriods().size());
|
i.setInstallmentNumber(model.repaymentPeriods().size());
|
||||||
});
|
});
|
||||||
|
|
||||||
|
int reAgedInstallmentIndex = 0;
|
||||||
for (int index = 0; index < model.repaymentPeriods().size(); index++) {
|
for (int index = 0; index < model.repaymentPeriods().size(); index++) {
|
||||||
RepaymentPeriod rp = model.repaymentPeriods().get(index);
|
RepaymentPeriod rp = model.repaymentPeriods().get(index);
|
||||||
if (rp.getDueDate().isBefore(transactionDate)) {
|
if (rp.getDueDate().isBefore(transactionDate)) {
|
||||||
@@ -3504,9 +3502,9 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
|
|||||||
installment.setInterestCharged(installment.getInterestPaid());
|
installment.setInterestCharged(installment.getInterestPaid());
|
||||||
installment.setPrincipal(installment.getPrincipalCompleted(currency).getAmount());
|
installment.setPrincipal(installment.getPrincipalCompleted(currency).getAmount());
|
||||||
installment.setInstallmentNumber(index + 1);
|
installment.setInstallmentNumber(index + 1);
|
||||||
|
installment.setCreditedPrincipal(rp.getCreditedPrincipal().getAmount());
|
||||||
|
|
||||||
installment.updateObligationsMet(currency, transactionDate);
|
installment.updateObligationsMet(currency, transactionDate);
|
||||||
// TODO add remaining components
|
|
||||||
} else {
|
} else {
|
||||||
LoanRepaymentScheduleInstallment created = LoanRepaymentScheduleInstallment.newReAgedInstallment(loanTransaction.getLoan(),
|
LoanRepaymentScheduleInstallment created = LoanRepaymentScheduleInstallment.newReAgedInstallment(loanTransaction.getLoan(),
|
||||||
index + 1, rp.getFromDate(), rp.getDueDate(), rp.getDuePrincipal().getAmount(), rp.getDueInterest().getAmount(),
|
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));
|
paidInAdvanceBalances.loanTransactionToRepaymentScheduleMappings.forEach(m -> m.setInstallment(created));
|
||||||
} else {
|
} else {
|
||||||
boolean isLastRepaymentPeriod = model.isLastRepaymentPeriod(rp);
|
created.setFeeChargesCharged(calculatedFees.calculateValueBigDecimal(reAgedInstallmentIndex));
|
||||||
created.setFeeChargesCharged(calculatedFees.calculateValueBigDecimal(isLastRepaymentPeriod));
|
created.setPenaltyCharges(calculatedPenalties.calculateValueBigDecimal(reAgedInstallmentIndex));
|
||||||
created.setPenaltyCharges(calculatedPenalties.calculateValueBigDecimal(isLastRepaymentPeriod));
|
|
||||||
|
|
||||||
created.setInterestAccrued(calculatedInterestAccrued.calculateValueBigDecimal(isLastRepaymentPeriod));
|
created.setInterestAccrued(calculatedInterestAccrued.calculateValueBigDecimal(reAgedInstallmentIndex));
|
||||||
created.setFeeAccrued(calculatedFeeAccrued.calculateValueBigDecimal(isLastRepaymentPeriod));
|
created.setFeeAccrued(calculatedFeeAccrued.calculateValueBigDecimal(reAgedInstallmentIndex));
|
||||||
created.setPenaltyAccrued(calculatedPenaltyAccrued.calculateValueBigDecimal(isLastRepaymentPeriod));
|
created.setPenaltyAccrued(calculatedPenaltyAccrued.calculateValueBigDecimal(reAgedInstallmentIndex));
|
||||||
|
|
||||||
createChargeMappingsForInstallment(created, calculatedCharges, isLastRepaymentPeriod);
|
createChargeMappingsForInstallment(created, calculatedCharges, reAgedInstallmentIndex);
|
||||||
|
reAgedInstallmentIndex++;
|
||||||
}
|
}
|
||||||
|
created.setCreditedPrincipal(rp.getCreditedPrincipal().getAmount());
|
||||||
created.updateObligationsMet(currency, transactionDate);
|
created.updateObligationsMet(currency, transactionDate);
|
||||||
installments.add(created);
|
installments.add(created);
|
||||||
}
|
}
|
||||||
@@ -3549,6 +3548,9 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
|
|||||||
List<LoanRepaymentScheduleInstallment> installments = ctx.getInstallments();
|
List<LoanRepaymentScheduleInstallment> installments = ctx.getInstallments();
|
||||||
LoanReAgeParameter loanReAgeParameter = loanTransaction.getLoanReAgeParameter();
|
LoanReAgeParameter loanReAgeParameter = loanTransaction.getLoanReAgeParameter();
|
||||||
LocalDate transactionDate = loanTransaction.getTransactionDate();
|
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 numberOfReAgeInstallments = loanReAgeParameter.getNumberOfInstallments();
|
||||||
Integer installmentAmountInMultiplesOf = loanTransaction.getLoan().getLoanProductRelatedDetail()
|
Integer installmentAmountInMultiplesOf = loanTransaction.getLoan().getLoanProductRelatedDetail()
|
||||||
@@ -3613,8 +3615,7 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
|
|||||||
return res;
|
return res;
|
||||||
}).reduce(new BalancesWithPaidInAdvance(currency), BalancesWithPaidInAdvance::summarizerAccumulator);
|
}).reduce(new BalancesWithPaidInAdvance(currency), BalancesWithPaidInAdvance::summarizerAccumulator);
|
||||||
|
|
||||||
if (!balances.getPrincipal().isZero() || !balances.getInterest().isZero() || !balances.getFee().isZero()
|
if (!transactionDate.isAfter(originalMaturityDate)) {
|
||||||
|| !balances.getPenalty().isZero()) {
|
|
||||||
|
|
||||||
final LoanRepaymentScheduleInstallment earlyRepaidInstallment = LoanRepaymentScheduleInstallment.newReAgedInstallment(loan,
|
final LoanRepaymentScheduleInstallment earlyRepaidInstallment = LoanRepaymentScheduleInstallment.newReAgedInstallment(loan,
|
||||||
firstReAgeInstallmentProps.reAgedInstallmentNumber(), firstReAgeInstallmentProps.fromDate(), transactionDate,
|
firstReAgeInstallmentProps.reAgedInstallmentNumber(), firstReAgeInstallmentProps.fromDate(), transactionDate,
|
||||||
@@ -3634,9 +3635,11 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
|
|||||||
|
|
||||||
InstallmentProcessingHelper.addOneToInstallmentNumberFromInstallment(installments,
|
InstallmentProcessingHelper.addOneToInstallmentNumberFromInstallment(installments,
|
||||||
earlyRepaidInstallment.getInstallmentNumber());
|
earlyRepaidInstallment.getInstallmentNumber());
|
||||||
loan.getRepaymentScheduleInstallments().add(earlyRepaidInstallment);
|
installments.add(earlyRepaidInstallment);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// installment index which excludes earlyRepaidInstallment intallment index.
|
||||||
|
Integer reAgedInstallmentIndex = 0;
|
||||||
LoanRepaymentScheduleInstallment reAgedInstallment = LoanRepaymentScheduleInstallment.newReAgedInstallment(loan,
|
LoanRepaymentScheduleInstallment reAgedInstallment = LoanRepaymentScheduleInstallment.newReAgedInstallment(loan,
|
||||||
firstReAgeInstallmentProps.reAgedInstallmentNumber, firstReAgeInstallmentProps.fromDate, loanReAgeParameter.getStartDate(),
|
firstReAgeInstallmentProps.reAgedInstallmentNumber, firstReAgeInstallmentProps.fromDate, loanReAgeParameter.getStartDate(),
|
||||||
calculatedPrincipal.value().getAmount(), calculatedInterest.value().getAmount(), calculatedFees.value().getAmount(),
|
calculatedPrincipal.value().getAmount(), calculatedInterest.value().getAmount(), calculatedFees.value().getAmount(),
|
||||||
@@ -3644,27 +3647,27 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
|
|||||||
calculatedFeeAccrued.value().getAmount(), calculatedPenaltyAccrued.value().getAmount());
|
calculatedFeeAccrued.value().getAmount(), calculatedPenaltyAccrued.value().getAmount());
|
||||||
|
|
||||||
reAgedInstallment = insertOrReplaceRelatedInstallment(installments, reAgedInstallment, currency, transactionDate);
|
reAgedInstallment = insertOrReplaceRelatedInstallment(installments, reAgedInstallment, currency, transactionDate);
|
||||||
createChargeMappingsForInstallment(reAgedInstallment, calculatedCharges, false);
|
createChargeMappingsForInstallment(reAgedInstallment, calculatedCharges, reAgedInstallmentIndex);
|
||||||
|
reAgedInstallmentIndex++;
|
||||||
for (int i = 1; i < numberOfReAgeInstallments; i++) {
|
for (int i = 1; i < numberOfReAgeInstallments; i++) {
|
||||||
LocalDate calculatedDueDate = scheduledDateGenerator.getRepaymentPeriodDate(loanReAgeParameter.getFrequencyType(),
|
LocalDate calculatedDueDate = scheduledDateGenerator.getRepaymentPeriodDate(loanReAgeParameter.getFrequencyType(),
|
||||||
loanReAgeParameter.getFrequencyNumber(), reAgedInstallment.getDueDate());
|
loanReAgeParameter.getFrequencyNumber(), reAgedInstallment.getDueDate());
|
||||||
calculateReAgedInstallmentDueDate(loanReAgeParameter, reAgedInstallment.getDueDate());
|
calculateReAgedInstallmentDueDate(loanReAgeParameter, reAgedInstallment.getDueDate());
|
||||||
int nextReAgedInstallmentNumber = firstReAgeInstallmentProps.reAgedInstallmentNumber + i;
|
int nextReAgedInstallmentNumber = firstReAgeInstallmentProps.reAgedInstallmentNumber + i;
|
||||||
boolean isLastInstallment = i + 1 == numberOfReAgeInstallments;
|
|
||||||
|
|
||||||
reAgedInstallment = LoanRepaymentScheduleInstallment.newReAgedInstallment(reAgedInstallment.getLoan(),
|
reAgedInstallment = LoanRepaymentScheduleInstallment.newReAgedInstallment(reAgedInstallment.getLoan(),
|
||||||
nextReAgedInstallmentNumber, reAgedInstallment.getDueDate(), calculatedDueDate,
|
nextReAgedInstallmentNumber, reAgedInstallment.getDueDate(), calculatedDueDate,
|
||||||
calculatedPrincipal.calculateValueBigDecimal(isLastInstallment),
|
calculatedPrincipal.calculateValueBigDecimal(reAgedInstallmentIndex),
|
||||||
calculatedInterest.calculateValueBigDecimal(isLastInstallment),
|
calculatedInterest.calculateValueBigDecimal(reAgedInstallmentIndex),
|
||||||
calculatedFees.calculateValueBigDecimal(isLastInstallment),
|
calculatedFees.calculateValueBigDecimal(reAgedInstallmentIndex),
|
||||||
calculatedPenalties.calculateValueBigDecimal(isLastInstallment),
|
calculatedPenalties.calculateValueBigDecimal(reAgedInstallmentIndex),
|
||||||
calculatedInterestAccrued.calculateValueBigDecimal(isLastInstallment),
|
calculatedInterestAccrued.calculateValueBigDecimal(reAgedInstallmentIndex),
|
||||||
calculatedFeeAccrued.calculateValueBigDecimal(isLastInstallment),
|
calculatedFeeAccrued.calculateValueBigDecimal(reAgedInstallmentIndex),
|
||||||
calculatedPenaltyAccrued.calculateValueBigDecimal(isLastInstallment));
|
calculatedPenaltyAccrued.calculateValueBigDecimal(reAgedInstallmentIndex));
|
||||||
|
|
||||||
reAgedInstallment = insertOrReplaceRelatedInstallment(installments, reAgedInstallment, currency, transactionDate);
|
reAgedInstallment = insertOrReplaceRelatedInstallment(installments, reAgedInstallment, currency, transactionDate);
|
||||||
createChargeMappingsForInstallment(reAgedInstallment, calculatedCharges, isLastInstallment);
|
createChargeMappingsForInstallment(reAgedInstallment, calculatedCharges, reAgedInstallmentIndex);
|
||||||
|
reAgedInstallmentIndex++;
|
||||||
}
|
}
|
||||||
int lastReAgedInstallmentNumber = reAgedInstallment.getInstallmentNumber();
|
int lastReAgedInstallmentNumber = reAgedInstallment.getInstallmentNumber();
|
||||||
List<LoanRepaymentScheduleInstallment> toRemove = installments.stream()
|
List<LoanRepaymentScheduleInstallment> toRemove = installments.stream()
|
||||||
@@ -3676,11 +3679,10 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void createChargeMappingsForInstallment(final LoanRepaymentScheduleInstallment installment,
|
private void createChargeMappingsForInstallment(final LoanRepaymentScheduleInstallment installment,
|
||||||
List<ReAgedChargeEqualAmortizationValues> reAgedChargeEqualAmortizationValues, boolean isLastInstallment) {
|
List<ReAgedChargeEqualAmortizationValues> reAgedChargeEqualAmortizationValues, Integer index) {
|
||||||
reAgedChargeEqualAmortizationValues.forEach(amortizationValue -> {
|
reAgedChargeEqualAmortizationValues.forEach(amortizationValue -> {
|
||||||
installment.getInstallmentCharges()
|
installment.getInstallmentCharges().add(new LoanInstallmentCharge(
|
||||||
.add(new LoanInstallmentCharge(amortizationValue.equalAmortizationValues.calculateValueBigDecimal(isLastInstallment),
|
amortizationValue.equalAmortizationValues.calculateValueBigDecimal(index), amortizationValue.charge, installment));
|
||||||
amortizationValue.charge, installment));
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+24
-41
@@ -857,7 +857,7 @@ public final class ProgressiveEMICalculator implements EMICalculator {
|
|||||||
lastInterestPeriod.addBalanceCorrectionAmount(rp.getOutstandingPrincipal().negated());
|
lastInterestPeriod.addBalanceCorrectionAmount(rp.getOutstandingPrincipal().negated());
|
||||||
}
|
}
|
||||||
rp.setEmi(rp.getTotalPaidAmount());
|
rp.setEmi(rp.getTotalPaidAmount());
|
||||||
rp.setOutstandingMoved(true);
|
rp.moveOutstandingDueToReAging();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1697,15 +1697,14 @@ public final class ProgressiveEMICalculator implements EMICalculator {
|
|||||||
EqualAmortizationValues principalEAV = calculateAdjustedEqualAmortizationValues(principal,
|
EqualAmortizationValues principalEAV = calculateAdjustedEqualAmortizationValues(principal,
|
||||||
principal.add(interest).add(feesPenaltiesOutstanding),
|
principal.add(interest).add(feesPenaltiesOutstanding),
|
||||||
interestEAV.value().add(feesPenaltiesEqualAmortizationValues.value()), repaymentPeriods.size(), null, currency);
|
interestEAV.value().add(feesPenaltiesEqualAmortizationValues.value()), repaymentPeriods.size(), null, currency);
|
||||||
RepaymentPeriod last = repaymentPeriods.getLast();
|
|
||||||
EqualAmortizationValues emiAEV = principalEAV.add(interestEAV);
|
EqualAmortizationValues emiAEV = principalEAV.add(interestEAV);
|
||||||
repaymentPeriods.forEach(rp -> {
|
for (int i = 0; i < repaymentPeriods.size(); i++) {
|
||||||
boolean isLast = last.equals(rp);
|
RepaymentPeriod rp = repaymentPeriods.get(i);
|
||||||
rp.setReAgedInterest(interestEAV.calculateValue(isLast));
|
rp.setReAgedInterest(interestEAV.calculateValue(i));
|
||||||
Money emi = emiAEV.calculateValue(isLast);
|
Money emi = emiAEV.calculateValue(i);
|
||||||
rp.setEmi(emi);
|
rp.setEmi(emi);
|
||||||
rp.setOriginalEmi(emi);
|
rp.setOriginalEmi(emi);
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -1722,40 +1721,46 @@ public final class ProgressiveEMICalculator implements EMICalculator {
|
|||||||
LoanReAgeParameter reageParameter, Money feesPenaltiesOutstanding,
|
LoanReAgeParameter reageParameter, Money feesPenaltiesOutstanding,
|
||||||
EqualAmortizationValues feesPenaltiesEqualAmortizationValues) {
|
EqualAmortizationValues feesPenaltiesEqualAmortizationValues) {
|
||||||
LocalDate originalMaturityDate = interestSchedule.getMaturityDate();
|
LocalDate originalMaturityDate = interestSchedule.getMaturityDate();
|
||||||
boolean isAfterOriginalMaturityDate = transactionDate.isAfter(originalMaturityDate);
|
|
||||||
List<RepaymentPeriod> reAgedRepaymentPeriods = new ArrayList<>(reageParameter.getNumberOfInstallments());
|
List<RepaymentPeriod> reAgedRepaymentPeriods = new ArrayList<>(reageParameter.getNumberOfInstallments());
|
||||||
OutstandingDetails reAgeingAmounts = precalculateReAgeEqualAmortizationAmount(interestSchedule, transactionDate, reageParameter);
|
OutstandingDetails reAgeingAmounts = precalculateReAgeEqualAmortizationAmount(interestSchedule, transactionDate, reageParameter);
|
||||||
|
Money zero = interestSchedule.zero();
|
||||||
|
|
||||||
// calculate already paid balances from transaction date
|
// calculate already paid balances from transaction date
|
||||||
OutstandingDetails paidBalancesFromTransactionDate = calculatePaidBalancesAfterDate(interestSchedule, transactionDate);
|
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.
|
// set maturity date to transaction date and remove all repayment periods after it.
|
||||||
accelerateMaturityDateTo(interestSchedule, transactionDate);
|
accelerateMaturityDateTo(interestSchedule, transactionDate);
|
||||||
|
|
||||||
|
addCredit(interestSchedule, transactionDate, futureCreditedPrincipals, zero);
|
||||||
|
|
||||||
// close all open repayment period while keep paid amounts
|
// close all open repayment period while keep paid amounts
|
||||||
interestSchedule.repaymentPeriods().forEach(rp -> {
|
interestSchedule.repaymentPeriods().forEach(rp -> {
|
||||||
rp.getInterestPeriods().getLast()
|
rp.getInterestPeriods().getLast()
|
||||||
.addCreditedInterestAmount(MathUtil.min(rp.getOutstandingInterest(), rp.getCreditedInterest(), false).negated());
|
.addCreditedInterestAmount(MathUtil.min(rp.getOutstandingInterest(), rp.getCreditedInterest(), false).negated());
|
||||||
rp.setEmi(rp.getTotalPaidAmount());
|
rp.setEmi(rp.getTotalPaidAmount());
|
||||||
rp.setOutstandingMoved(true);
|
rp.moveOutstandingDueToReAging();
|
||||||
});
|
});
|
||||||
|
|
||||||
// stop calculate unrecognised interest at this point because all
|
// stop calculate unrecognised interest at this point because all
|
||||||
interestSchedule.getLastRepaymentPeriod().setNoUnrecognisedInterest(true);
|
interestSchedule.getLastRepaymentPeriod().setNoUnrecognisedInterest(true);
|
||||||
|
|
||||||
if (!paidBalancesFromTransactionDate.getOutstandingInterest().isZero()
|
if (!originalMaturityDate.isBefore(transactionDate)) {
|
||||||
|| !paidBalancesFromTransactionDate.getOutstandingPrincipal().isZero()) {
|
|
||||||
createRepaymentPeriodForEarlyRepaidAmountsDuringReAgeing(interestSchedule,
|
createRepaymentPeriodForEarlyRepaidAmountsDuringReAgeing(interestSchedule,
|
||||||
paidBalancesFromTransactionDate.getOutstandingPrincipal(), paidBalancesFromTransactionDate.getOutstandingInterest(),
|
paidBalancesFromTransactionDate.getOutstandingPrincipal(), paidBalancesFromTransactionDate.getOutstandingInterest(),
|
||||||
true);
|
true);
|
||||||
addFirstReAgedPeriod(interestSchedule, interestSchedule.getLastRepaymentPeriod());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
updateModelForReageEqualAmortization(interestSchedule, reageParameter, reAgedRepaymentPeriods, isAfterOriginalMaturityDate);
|
updateModelForReageEqualAmortization(interestSchedule, reageParameter, reAgedRepaymentPeriods);
|
||||||
|
|
||||||
updateEMIForReAgeEqualAmortization(reAgedRepaymentPeriods, reAgeingAmounts.getOutstandingPrincipal(),
|
updateEMIForReAgeEqualAmortization(reAgedRepaymentPeriods, reAgeingAmounts.getOutstandingPrincipal(),
|
||||||
reAgeingAmounts.getOutstandingInterest(), feesPenaltiesOutstanding, feesPenaltiesEqualAmortizationValues,
|
reAgeingAmounts.getOutstandingInterest(), feesPenaltiesOutstanding, feesPenaltiesEqualAmortizationValues,
|
||||||
interestSchedule.zero().getCurrency());
|
zero.getCurrency());
|
||||||
|
|
||||||
calculateOutstandingBalance(interestSchedule);
|
calculateOutstandingBalance(interestSchedule);
|
||||||
|
|
||||||
@@ -1767,31 +1772,17 @@ public final class ProgressiveEMICalculator implements EMICalculator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void updateModelForReageEqualAmortization(ProgressiveLoanInterestScheduleModel interestSchedule,
|
private void updateModelForReageEqualAmortization(ProgressiveLoanInterestScheduleModel interestSchedule,
|
||||||
LoanReAgeParameter reageParameter, List<RepaymentPeriod> reAgedRepaymentPeriods, boolean isAfterOriginalMaturityDate) {
|
LoanReAgeParameter reageParameter, List<RepaymentPeriod> reAgedRepaymentPeriods) {
|
||||||
int numberOfInstallmentsToAdd = reageParameter.getNumberOfInstallments();
|
int numberOfInstallmentsToAdd = reageParameter.getNumberOfInstallments();
|
||||||
LocalDate toDate = reageParameter.getStartDate();
|
LocalDate toDate = reageParameter.getStartDate();
|
||||||
RepaymentPeriod previous = interestSchedule.getLastRepaymentPeriod();
|
RepaymentPeriod previous = interestSchedule.getLastRepaymentPeriod();
|
||||||
int frequency = reageParameter.getFrequencyNumber();
|
int frequency = reageParameter.getFrequencyNumber();
|
||||||
PeriodFrequencyType frequencyType = reageParameter.getFrequencyType();
|
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
|
// insert new reaged repayment periods
|
||||||
for (int i = 0; i < numberOfInstallmentsToAdd; i++) {
|
for (int i = 0; i < numberOfInstallmentsToAdd; i++) {
|
||||||
RepaymentPeriod repaymentPeriod = RepaymentPeriod.create(previous, previous.getDueDate(), toDate, interestSchedule.zero(),
|
RepaymentPeriod repaymentPeriod = RepaymentPeriod.create(previous, previous.getDueDate(), toDate, interestSchedule.zero(),
|
||||||
previous.getMc(), previous.getLoanProductRelatedDetail());
|
interestSchedule.mc(), previous.getLoanProductRelatedDetail());
|
||||||
repaymentPeriod.setTotalCapitalizedIncomeAmount(previous.getTotalCapitalizedIncomeAmount());
|
repaymentPeriod.setTotalCapitalizedIncomeAmount(previous.getTotalCapitalizedIncomeAmount());
|
||||||
repaymentPeriod.setTotalDisbursedAmount(previous.getTotalDisbursedAmount());
|
repaymentPeriod.setTotalDisbursedAmount(previous.getTotalDisbursedAmount());
|
||||||
repaymentPeriod.setReAged(true);
|
repaymentPeriod.setReAged(true);
|
||||||
@@ -1818,14 +1809,6 @@ public final class ProgressiveEMICalculator implements EMICalculator {
|
|||||||
calculateRateFactorForRepaymentPeriod(targetPeriod, interestSchedule);
|
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,
|
private OutstandingDetails calculatePaidBalancesAfterDate(ProgressiveLoanInterestScheduleModel interestSchedule,
|
||||||
LocalDate transactionDate) {
|
LocalDate transactionDate) {
|
||||||
Money principal = interestSchedule.repaymentPeriods().stream().filter(rp -> !rp.getDueDate().isBefore(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);
|
equalMonthlyValue = Money.roundToMultiplesOf(equalMonthlyValue, installmentAmountInMultiplesOf);
|
||||||
}
|
}
|
||||||
Money adjustmentForLastInstallment = totalOutstanding.minus(equalMonthlyValue.multipliedBy(numberOfInstallments));
|
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
|
@Override
|
||||||
@@ -1857,6 +1840,6 @@ public final class ProgressiveEMICalculator implements EMICalculator {
|
|||||||
installmentAmountInMultiplesOf, currency);
|
installmentAmountInMultiplesOf, currency);
|
||||||
Money value = calculatedEMI.value().minus(sumOfOtherEqualAmortizationValues);
|
Money value = calculatedEMI.value().minus(sumOfOtherEqualAmortizationValues);
|
||||||
Money adjust = outstanding.minus(value.multipliedBy(numberOfInstallments));
|
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;
|
package org.apache.fineract.portfolio.loanproduct.calc.data;
|
||||||
|
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
|
import java.util.Objects;
|
||||||
import org.apache.fineract.organisation.monetary.domain.Money;
|
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() {
|
public Money getAdjustedValue() {
|
||||||
return value.add(adjustment);
|
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) {
|
public BigDecimal calculateValueBigDecimal(Integer index) {
|
||||||
return calculateValue(isLast).getAmount();
|
return calculateValue(index).getAmount();
|
||||||
}
|
}
|
||||||
|
|
||||||
public EqualAmortizationValues add(EqualAmortizationValues other) {
|
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
|
@Getter
|
||||||
@Setter
|
@Setter
|
||||||
private boolean isOutstandingMoved = false;
|
private Money creditedPrincipalMovedDueReAge;
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
private Money creditedInterestMovedDueReAge;
|
||||||
@Setter
|
@Setter
|
||||||
@Getter
|
@Getter
|
||||||
private boolean noUnrecognisedInterest;
|
private boolean noUnrecognisedInterest;
|
||||||
@@ -125,6 +127,8 @@ public class RepaymentPeriod {
|
|||||||
this.reAged = reAged;
|
this.reAged = reAged;
|
||||||
this.reAgedEarlyRepaymentHolder = reAgedEarlyRepaymentHolder;
|
this.reAgedEarlyRepaymentHolder = reAgedEarlyRepaymentHolder;
|
||||||
this.reAgedInterest = reAgedInterest;
|
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) {
|
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.getPaidPrincipal(), repaymentPeriod.getPaidInterest(), repaymentPeriod.getFutureUnrecognizedInterest(), mc,
|
||||||
repaymentPeriod.getLoanProductRelatedDetail(), repaymentPeriod.isNoUnrecognisedInterest(), repaymentPeriod.isReAged(),
|
repaymentPeriod.getLoanProductRelatedDetail(), repaymentPeriod.isNoUnrecognisedInterest(), repaymentPeriod.isReAged(),
|
||||||
repaymentPeriod.isReAgedEarlyRepaymentHolder(), repaymentPeriod.getReAgedInterest());
|
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
|
// There is always at least 1 interest period, by default with same from-due date as repayment period
|
||||||
for (InterestPeriod interestPeriod : repaymentPeriod.getInterestPeriods()) {
|
for (InterestPeriod interestPeriod : repaymentPeriod.getInterestPeriods()) {
|
||||||
newRepaymentPeriod.getInterestPeriods().add(InterestPeriod.copy(newRepaymentPeriod, interestPeriod, mc));
|
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,
|
repaymentPeriod.getDueDate(), new ArrayList<>(), repaymentPeriod.getEmi(), repaymentPeriod.getOriginalEmi(), zero, zero,
|
||||||
zero, mc, repaymentPeriod.getLoanProductRelatedDetail(), repaymentPeriod.isNoUnrecognisedInterest(),
|
zero, mc, repaymentPeriod.getLoanProductRelatedDetail(), repaymentPeriod.isNoUnrecognisedInterest(),
|
||||||
repaymentPeriod.isReAged(), repaymentPeriod.isReAgedEarlyRepaymentHolder(), repaymentPeriod.getReAgedInterest());
|
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
|
// There is always at least 1 interest period, by default with same from-due date as repayment period
|
||||||
for (InterestPeriod interestPeriod : repaymentPeriod.getInterestPeriods()) {
|
for (InterestPeriod interestPeriod : repaymentPeriod.getInterestPeriods()) {
|
||||||
var interestPeriodCopy = InterestPeriod.copy(newRepaymentPeriod, interestPeriod);
|
var interestPeriodCopy = InterestPeriod.copy(newRepaymentPeriod, interestPeriod);
|
||||||
@@ -311,7 +317,8 @@ public class RepaymentPeriod {
|
|||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
public Money getTotalCreditedAmount() {
|
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() {
|
public Money getTotalCapitalizedIncomeAmount() {
|
||||||
return MathUtil.nullToZero(totalCapitalizedIncomeAmount, getCurrency(), getMc());
|
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.DateUtils;
|
||||||
import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil;
|
import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil;
|
||||||
import org.apache.fineract.organisation.monetary.data.CurrencyData;
|
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.Money;
|
||||||
import org.apache.fineract.organisation.monetary.domain.MoneyHelper;
|
import org.apache.fineract.organisation.monetary.domain.MoneyHelper;
|
||||||
import org.apache.fineract.portfolio.common.domain.DaysInMonthType;
|
import org.apache.fineract.portfolio.common.domain.DaysInMonthType;
|
||||||
@@ -4239,6 +4240,147 @@ class ProgressiveEMICalculatorTest {
|
|||||||
@Nested
|
@Nested
|
||||||
public class ReAgeEqualAmortization {
|
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
|
@Test
|
||||||
public void test_transactionInMiddleOfPeriod_EQUAL_AMORTIZATION_FULL_INTEREST_noTransactionTilDate_noInterestRecalc() {
|
public void test_transactionInMiddleOfPeriod_EQUAL_AMORTIZATION_FULL_INTEREST_noTransactionTilDate_noInterestRecalc() {
|
||||||
final List<LoanScheduleModelRepaymentPeriod> expectedRepaymentPeriods = new ArrayList<>();
|
final List<LoanScheduleModelRepaymentPeriod> expectedRepaymentPeriods = new ArrayList<>();
|
||||||
@@ -4291,7 +4433,7 @@ class ProgressiveEMICalculatorTest {
|
|||||||
|
|
||||||
// Update the existing model with re-aged periods
|
// Update the existing model with re-aged periods
|
||||||
emiCalculator.reAgeEqualAmortization(interestSchedule, loanTransaction.getTransactionDate(), reageParameter,
|
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,
|
OutstandingDetails outstandingAmountsTillDateAfterReage = emiCalculator.getOutstandingAmountsTillDate(interestSchedule,
|
||||||
interestSchedule.getMaturityDate());
|
interestSchedule.getMaturityDate());
|
||||||
@@ -4380,7 +4522,7 @@ class ProgressiveEMICalculatorTest {
|
|||||||
|
|
||||||
// Update the existing model with re-aged periods
|
// Update the existing model with re-aged periods
|
||||||
emiCalculator.reAgeEqualAmortization(interestSchedule, loanTransaction.getTransactionDate(), reageParameter,
|
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,
|
OutstandingDetails outstandingAmountsTillDateAfterReage = emiCalculator.getOutstandingAmountsTillDate(interestSchedule,
|
||||||
interestSchedule.getMaturityDate());
|
interestSchedule.getMaturityDate());
|
||||||
@@ -4439,7 +4581,7 @@ class ProgressiveEMICalculatorTest {
|
|||||||
|
|
||||||
// Update the existing model with re-aged periods
|
// Update the existing model with re-aged periods
|
||||||
emiCalculator.reAgeEqualAmortization(interestSchedule, transactionDate, reageParameter, Money.zero(currency),
|
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,
|
OutstandingDetails outstandingAmountsTillDateAfterReage = emiCalculator.getOutstandingAmountsTillDate(interestSchedule,
|
||||||
interestSchedule.getMaturityDate());
|
interestSchedule.getMaturityDate());
|
||||||
@@ -4499,7 +4641,7 @@ class ProgressiveEMICalculatorTest {
|
|||||||
|
|
||||||
// Update the existing model with re-aged periods
|
// Update the existing model with re-aged periods
|
||||||
emiCalculator.reAgeEqualAmortization(interestSchedule, transactionDate, reageParameter, Money.zero(currency),
|
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,
|
OutstandingDetails outstandingAmountsTillDateAfterReage = emiCalculator.getOutstandingAmountsTillDate(interestSchedule,
|
||||||
interestSchedule.getMaturityDate());
|
interestSchedule.getMaturityDate());
|
||||||
@@ -4582,7 +4724,7 @@ class ProgressiveEMICalculatorTest {
|
|||||||
|
|
||||||
// Update the existing model with re-aged periods
|
// Update the existing model with re-aged periods
|
||||||
emiCalculator.reAgeEqualAmortization(interestSchedule, transactionDate, reageParameter, Money.zero(currency),
|
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,
|
OutstandingDetails outstandingAmountsTillDateAfterReage = emiCalculator.getOutstandingAmountsTillDate(interestSchedule,
|
||||||
interestSchedule.getMaturityDate());
|
interestSchedule.getMaturityDate());
|
||||||
@@ -4665,7 +4807,7 @@ class ProgressiveEMICalculatorTest {
|
|||||||
|
|
||||||
// Update the existing model with re-aged periods
|
// Update the existing model with re-aged periods
|
||||||
emiCalculator.reAgeEqualAmortization(interestSchedule, transactionDate, reageParameter, Money.zero(currency),
|
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,
|
OutstandingDetails outstandingAmountsTillDateAfterReage = emiCalculator.getOutstandingAmountsTillDate(interestSchedule,
|
||||||
interestSchedule.getMaturityDate());
|
interestSchedule.getMaturityDate());
|
||||||
@@ -4738,7 +4880,7 @@ class ProgressiveEMICalculatorTest {
|
|||||||
|
|
||||||
// Update the existing model with re-aged periods
|
// Update the existing model with re-aged periods
|
||||||
emiCalculator.reAgeEqualAmortization(interestSchedule, transactionDate, reageParameter, Money.zero(currency),
|
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,
|
OutstandingDetails outstandingAmountsTillDateAfterReage = emiCalculator.getOutstandingAmountsTillDate(interestSchedule,
|
||||||
interestSchedule.getMaturityDate());
|
interestSchedule.getMaturityDate());
|
||||||
@@ -4811,7 +4953,7 @@ class ProgressiveEMICalculatorTest {
|
|||||||
|
|
||||||
// Update the existing model with re-aged periods
|
// Update the existing model with re-aged periods
|
||||||
emiCalculator.reAgeEqualAmortization(interestSchedule, transactionDate, reageParameter, Money.zero(currency),
|
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,
|
OutstandingDetails outstandingAmountsTillDateAfterReage = emiCalculator.getOutstandingAmountsTillDate(interestSchedule,
|
||||||
interestSchedule.getMaturityDate());
|
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
|
// utilities
|
||||||
private List<LoanScheduleModelRepaymentPeriod> generateExpectedRepaymentPeriods(LocalDate disbursementDate) {
|
private List<LoanScheduleModelRepaymentPeriod> generateExpectedRepaymentPeriods(LocalDate disbursementDate) {
|
||||||
return switch (loanProductRelatedDetail.getRepaymentPeriodFrequencyType()) {
|
return switch (loanProductRelatedDetail.getRepaymentPeriodFrequencyType()) {
|
||||||
|
|||||||
Reference in New Issue
Block a user