FINERACT-1981: Enhance EMI Calculator Daily Interest calculation

This commit is contained in:
Janos Meszaros
2024-11-06 09:46:36 +01:00
committed by Adam Saghy
parent 0bc14ab7f3
commit 51b6dfa08e
7 changed files with 214 additions and 49 deletions
@@ -27,6 +27,7 @@ import lombok.EqualsAndHashCode;
import lombok.Getter; import lombok.Getter;
import lombok.Setter; import lombok.Setter;
import lombok.ToString; import lombok.ToString;
import org.apache.fineract.infrastructure.core.service.DateUtils;
import org.apache.fineract.infrastructure.core.service.MathUtil; import org.apache.fineract.infrastructure.core.service.MathUtil;
import org.apache.fineract.organisation.monetary.domain.Money; import org.apache.fineract.organisation.monetary.domain.Money;
@@ -42,27 +43,31 @@ public class InterestPeriod implements Comparable<InterestPeriod> {
private LocalDate dueDate; private LocalDate dueDate;
@Setter @Setter
private BigDecimal rateFactor; private BigDecimal rateFactor;
@Setter
private BigDecimal rateFactorTillPeriodDueDate;
private Money disbursementAmount; private Money disbursementAmount;
private Money balanceCorrectionAmount; private Money balanceCorrectionAmount;
private Money outstandingLoanBalance; private Money outstandingLoanBalance;
private final MathContext mc; private final MathContext mc;
public InterestPeriod(RepaymentPeriod repaymentPeriod, LocalDate fromDate, LocalDate dueDate, BigDecimal rateFactor, public InterestPeriod(RepaymentPeriod repaymentPeriod, LocalDate fromDate, LocalDate dueDate, BigDecimal rateFactor,
Money disbursementAmount, Money balanceCorrectionAmount, Money outstandingLoanBalance, MathContext mc) { BigDecimal rateFactorTillPeriodDueDate, Money disbursementAmount, Money balanceCorrectionAmount, Money outstandingLoanBalance,
MathContext mc) {
this.repaymentPeriod = repaymentPeriod; this.repaymentPeriod = repaymentPeriod;
this.fromDate = fromDate; this.fromDate = fromDate;
this.dueDate = dueDate; this.dueDate = dueDate;
this.rateFactor = rateFactor; this.rateFactor = rateFactor;
this.rateFactorTillPeriodDueDate = rateFactorTillPeriodDueDate;
this.disbursementAmount = disbursementAmount; this.disbursementAmount = disbursementAmount;
this.balanceCorrectionAmount = balanceCorrectionAmount; this.balanceCorrectionAmount = balanceCorrectionAmount;
this.outstandingLoanBalance = outstandingLoanBalance; this.outstandingLoanBalance = outstandingLoanBalance;
this.mc = mc; this.mc = mc;
} }
public InterestPeriod(RepaymentPeriod repaymentPeriod, InterestPeriod interestPeriod, MathContext mc) { public InterestPeriod(RepaymentPeriod repaymentPeriod, InterestPeriod interestPeriod) {
this(repaymentPeriod, interestPeriod.getFromDate(), interestPeriod.getDueDate(), interestPeriod.getRateFactor(), this(repaymentPeriod, interestPeriod.getFromDate(), interestPeriod.getDueDate(), interestPeriod.getRateFactor(),
interestPeriod.getDisbursementAmount(), interestPeriod.getBalanceCorrectionAmount(), interestPeriod.getRateFactorTillPeriodDueDate(), interestPeriod.getDisbursementAmount(),
interestPeriod.getOutstandingLoanBalance(), mc); interestPeriod.getBalanceCorrectionAmount(), interestPeriod.getOutstandingLoanBalance(), interestPeriod.getMc());
} }
@Override @Override
@@ -79,7 +84,19 @@ public class InterestPeriod implements Comparable<InterestPeriod> {
} }
public Money getCalculatedDueInterest() { public Money getCalculatedDueInterest() {
return getOutstandingLoanBalance().multipliedBy(getRateFactor(), mc); final BigDecimal interestDueTillRepaymentDueDate = getOutstandingLoanBalance()//
.multipliedBy(getRateFactorTillPeriodDueDate(), mc).getAmount() //
.divide(BigDecimal.valueOf(getLengthTillPeriodDueDate()), mc) //
.multiply(BigDecimal.valueOf(getLength()), mc); //
return Money.of(outstandingLoanBalance.getCurrency(), interestDueTillRepaymentDueDate, mc);
}
public long getLength() {
return DateUtils.getDifferenceInDays(fromDate, dueDate);
}
public long getLengthTillPeriodDueDate() {
return DateUtils.getDifferenceInDays(fromDate, repaymentPeriod.getDueDate());
} }
public void updateOutstandingLoanBalance() { public void updateOutstandingLoanBalance() {
@@ -189,7 +189,7 @@ public class ProgressiveLoanInterestScheduleModel {
previousInterestPeriod.addDisbursementAmount(disbursedAmount); previousInterestPeriod.addDisbursementAmount(disbursedAmount);
previousInterestPeriod.addBalanceCorrectionAmount(correctionAmount); previousInterestPeriod.addBalanceCorrectionAmount(correctionAmount);
final InterestPeriod interestPeriod = new InterestPeriod(repaymentPeriod, previousInterestPeriod.getDueDate(), originalDueDate, final InterestPeriod interestPeriod = new InterestPeriod(repaymentPeriod, previousInterestPeriod.getDueDate(), originalDueDate,
BigDecimal.ZERO, getZero(), getZero(), getZero(), mc); BigDecimal.ZERO, BigDecimal.ZERO, getZero(), getZero(), getZero(), mc);
repaymentPeriod.getInterestPeriods().add(interestPeriod); repaymentPeriod.getInterestPeriods().add(interestPeriod);
} }
@@ -69,8 +69,8 @@ public class RepaymentPeriod {
this.mc = mc; this.mc = mc;
this.interestPeriods = new ArrayList<>(); this.interestPeriods = new ArrayList<>();
// 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
getInterestPeriods() getInterestPeriods().add(new InterestPeriod(this, getFromDate(), getDueDate(), BigDecimal.ZERO, BigDecimal.ZERO, getZero(mc),
.add(new InterestPeriod(this, getFromDate(), getDueDate(), BigDecimal.ZERO, getZero(mc), getZero(mc), getZero(mc), mc)); getZero(mc), getZero(mc), mc));
this.paidInterest = getZero(mc); this.paidInterest = getZero(mc);
this.paidPrincipal = getZero(mc); this.paidPrincipal = getZero(mc);
} }
@@ -87,7 +87,7 @@ public class RepaymentPeriod {
this.mc = mc; this.mc = mc;
// 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.interestPeriods) { for (InterestPeriod interestPeriod : repaymentPeriod.interestPeriods) {
interestPeriods.add(new InterestPeriod(this, interestPeriod, mc)); interestPeriods.add(new InterestPeriod(this, interestPeriod));
} }
} }
@@ -314,38 +314,42 @@ public final class ProgressiveEMICalculator implements EMICalculator {
void calculateRateFactorForRepaymentPeriod(final RepaymentPeriod repaymentPeriod, void calculateRateFactorForRepaymentPeriod(final RepaymentPeriod repaymentPeriod,
final ProgressiveLoanInterestScheduleModel scheduleModel) { final ProgressiveLoanInterestScheduleModel scheduleModel) {
repaymentPeriod.getInterestPeriods().forEach(interestPeriod -> interestPeriod repaymentPeriod.getInterestPeriods().forEach(interestPeriod -> {
.setRateFactor(calculateRateFactorPerPeriod(repaymentPeriod, interestPeriod, scheduleModel))); interestPeriod.setRateFactor(calculateRateFactorPerPeriod(scheduleModel, repaymentPeriod, interestPeriod.getFromDate(),
interestPeriod.getDueDate()));
interestPeriod.setRateFactorTillPeriodDueDate(calculateRateFactorPerPeriod(scheduleModel, repaymentPeriod,
interestPeriod.getFromDate(), repaymentPeriod.getDueDate()));
});
} }
/** /**
* Calculate Rate Factor for an exact Period * Calculate Rate Factor for an exact Period
*/ */
BigDecimal calculateRateFactorPerPeriod(final RepaymentPeriod repaymentPeriod, final InterestPeriod interestPeriod, BigDecimal calculateRateFactorPerPeriod(final ProgressiveLoanInterestScheduleModel scheduleModel, final RepaymentPeriod repaymentPeriod,
final ProgressiveLoanInterestScheduleModel scheduleModel) { final LocalDate interestPeriodFromDate, final LocalDate interestPeriodDueDate) {
final MathContext mc = scheduleModel.mc(); final MathContext mc = scheduleModel.mc();
final LoanProductRelatedDetail loanProductRelatedDetail = scheduleModel.loanProductRelatedDetail(); final LoanProductRelatedDetail loanProductRelatedDetail = scheduleModel.loanProductRelatedDetail();
final BigDecimal interestRate = calcNominalInterestRatePercentage(scheduleModel.getInterestRate(interestPeriod.getFromDate()), final BigDecimal interestRate = calcNominalInterestRatePercentage(scheduleModel.getInterestRate(interestPeriodFromDate),
scheduleModel.mc()); scheduleModel.mc());
final DaysInYearType daysInYearType = DaysInYearType.fromInt(loanProductRelatedDetail.getDaysInYearType()); final DaysInYearType daysInYearType = DaysInYearType.fromInt(loanProductRelatedDetail.getDaysInYearType());
final DaysInMonthType daysInMonthType = DaysInMonthType.fromInt(loanProductRelatedDetail.getDaysInMonthType()); final DaysInMonthType daysInMonthType = DaysInMonthType.fromInt(loanProductRelatedDetail.getDaysInMonthType());
final PeriodFrequencyType repaymentFrequency = loanProductRelatedDetail.getRepaymentPeriodFrequencyType(); final PeriodFrequencyType repaymentFrequency = loanProductRelatedDetail.getRepaymentPeriodFrequencyType();
final BigDecimal repaymentEvery = BigDecimal.valueOf(loanProductRelatedDetail.getRepayEvery()); final BigDecimal repaymentEvery = BigDecimal.valueOf(loanProductRelatedDetail.getRepayEvery());
final BigDecimal daysInMonth = BigDecimal.valueOf(daysInMonthType.getNumberOfDays(interestPeriod.getFromDate())); final BigDecimal daysInMonth = BigDecimal.valueOf(daysInMonthType.getNumberOfDays(interestPeriodFromDate));
final BigDecimal daysInYear = BigDecimal.valueOf(daysInYearType.getNumberOfDays(interestPeriod.getFromDate())); final BigDecimal daysInYear = BigDecimal.valueOf(daysInYearType.getNumberOfDays(interestPeriodFromDate));
final BigDecimal actualDaysInPeriod = BigDecimal final BigDecimal actualDaysInPeriod = BigDecimal
.valueOf(DateUtils.getDifferenceInDays(interestPeriod.getFromDate(), interestPeriod.getDueDate())); .valueOf(DateUtils.getDifferenceInDays(interestPeriodFromDate, interestPeriodDueDate));
final BigDecimal calculatedDaysInPeriod = BigDecimal final BigDecimal calculatedDaysInPeriod = BigDecimal
.valueOf(DateUtils.getDifferenceInDays(repaymentPeriod.getFromDate(), repaymentPeriod.getDueDate())); .valueOf(DateUtils.getDifferenceInDays(repaymentPeriod.getFromDate(), repaymentPeriod.getDueDate()));
final int numberOfYearsDifferenceInPeriod = interestPeriod.getDueDate().getYear() - interestPeriod.getFromDate().getYear(); final int numberOfYearsDifferenceInPeriod = interestPeriodDueDate.getYear() - interestPeriodFromDate.getYear();
final boolean partialPeriodCalculationNeeded = daysInYearType == DaysInYearType.ACTUAL && numberOfYearsDifferenceInPeriod > 0; final boolean partialPeriodCalculationNeeded = daysInYearType == DaysInYearType.ACTUAL && numberOfYearsDifferenceInPeriod > 0;
// TODO check: loanApplicationTerms.calculatePeriodsBetweenDates(startDate, endDate); // calculate period data // TODO check: loanApplicationTerms.calculatePeriodsBetweenDates(startDate, endDate); // calculate period data
// TODO review: (repayment frequency: days, weeks, years; validation day is month fix 30) // TODO review: (repayment frequency: days, weeks, years; validation day is month fix 30)
// TODO refactor this logic to represent in interest period // TODO refactor this logic to represent in interest period
if (partialPeriodCalculationNeeded) { if (partialPeriodCalculationNeeded) {
final BigDecimal cumulatedPeriodFractions = calculatePeriodFractions(interestPeriod, mc); final BigDecimal cumulatedPeriodFractions = calculatePeriodFractions(interestPeriodFromDate, interestPeriodDueDate, mc);
return rateFactorByRepaymentPartialPeriod(interestRate, repaymentEvery, cumulatedPeriodFractions, BigDecimal.ONE, return rateFactorByRepaymentPartialPeriod(interestRate, repaymentEvery, cumulatedPeriodFractions, BigDecimal.ONE,
BigDecimal.ONE, mc); BigDecimal.ONE, mc);
} }
@@ -357,18 +361,19 @@ public final class ProgressiveEMICalculator implements EMICalculator {
/** /**
* Calculate Period fractions part based on how much year has in the period * Calculate Period fractions part based on how much year has in the period
* *
* @param interestPeriod * @param interestPeriodFromDate
* @param interestPeriodDueDate
* @return * @return
*/ */
BigDecimal calculatePeriodFractions(InterestPeriod interestPeriod, MathContext mc) { BigDecimal calculatePeriodFractions(final LocalDate interestPeriodFromDate, final LocalDate interestPeriodDueDate, MathContext mc) {
BigDecimal cumulatedRateFactor = BigDecimal.ZERO; BigDecimal cumulatedRateFactor = BigDecimal.ZERO;
int actualYear = interestPeriod.getFromDate().getYear(); int actualYear = interestPeriodFromDate.getYear();
int endYear = interestPeriod.getDueDate().getYear(); int endYear = interestPeriodDueDate.getYear();
LocalDate actualDate = interestPeriod.getFromDate(); LocalDate actualDate = interestPeriodFromDate;
LocalDate endOfActualYear; LocalDate endOfActualYear;
while (actualYear <= endYear) { while (actualYear <= endYear) {
endOfActualYear = actualYear == endYear ? interestPeriod.getDueDate() : LocalDate.of(actualYear, 12, 31); endOfActualYear = actualYear == endYear ? interestPeriodDueDate : LocalDate.of(actualYear, 12, 31);
BigDecimal numberOfDaysInYear = BigDecimal.valueOf(Year.of(actualYear).length()); BigDecimal numberOfDaysInYear = BigDecimal.valueOf(Year.of(actualYear).length());
BigDecimal calculatedDaysInActualYear = BigDecimal.valueOf(DateUtils.getDifferenceInDays(actualDate, endOfActualYear)); BigDecimal calculatedDaysInActualYear = BigDecimal.valueOf(DateUtils.getDifferenceInDays(actualDate, endOfActualYear));
cumulatedRateFactor = cumulatedRateFactor.add(calculatedDaysInActualYear.divide(numberOfDaysInYear, mc), mc); cumulatedRateFactor = cumulatedRateFactor.add(calculatedDaysInActualYear.divide(numberOfDaysInYear, mc), mc);
@@ -1110,6 +1110,138 @@ class ProgressiveEMICalculatorTest {
checkPeriod(interestSchedule, 5, 0, 16.89, 0.003950916667, 0.07, 16.82, 0.0); checkPeriod(interestSchedule, 5, 0, 16.89, 0.003950916667, 0.07, 16.82, 0.0);
} }
@Test
public void test_dailyInterest_disbursedAmt1000_dayInYears360_daysInMonth30_repayIn1Month() {
final List<LoanScheduleModelRepaymentPeriod> expectedRepaymentPeriods = new ArrayList<>();
expectedRepaymentPeriods.add(repayment(1, LocalDate.of(2024, 1, 1), LocalDate.of(2024, 2, 1)));
final BigDecimal interestRate = BigDecimal.valueOf(7.0);
final Integer installmentAmountInMultiplesOf = null;
Mockito.when(loanProductRelatedDetail.getAnnualNominalInterestRate()).thenReturn(interestRate);
Mockito.when(loanProductRelatedDetail.getDaysInYearType()).thenReturn(DaysInYearType.DAYS_360.getValue());
Mockito.when(loanProductRelatedDetail.getDaysInMonthType()).thenReturn(DaysInMonthType.DAYS_30.getValue());
Mockito.when(loanProductRelatedDetail.getRepaymentPeriodFrequencyType()).thenReturn(PeriodFrequencyType.MONTHS);
Mockito.when(loanProductRelatedDetail.getRepayEvery()).thenReturn(1);
Mockito.when(loanProductRelatedDetail.getCurrency()).thenReturn(monetaryCurrency);
final ProgressiveLoanInterestScheduleModel interestModel = emiCalculator.generateInterestScheduleModel(expectedRepaymentPeriods,
loanProductRelatedDetail, installmentAmountInMultiplesOf, mc);
final Money disbursedAmount = toMoney(1000.0);
emiCalculator.addDisbursement(interestModel, LocalDate.of(2024, 1, 1), disbursedAmount);
checkPeriod(interestModel, 0, 0, 1005.83, 0.0, 0.0, 5.83, 1000.0, 0.0);
checkPeriod(interestModel, 0, 1, 1005.83, 0.005833333333, 5.83, 1000.0, 0.0);
final LocalDate dueDate = LocalDate.of(2024, 2, 1);
final LocalDate startDay = LocalDate.of(2024, 1, 1);
checkDailyInterest(interestModel, dueDate, startDay, 1, 0.19, 0.19);
checkDailyInterest(interestModel, dueDate, startDay, 2, 0.19, 0.38);
checkDailyInterest(interestModel, dueDate, startDay, 3, 0.18, 0.56);
checkDailyInterest(interestModel, dueDate, startDay, 4, 0.19, 0.75);
checkDailyInterest(interestModel, dueDate, startDay, 5, 0.19, 0.94);
checkDailyInterest(interestModel, dueDate, startDay, 6, 0.19, 1.13);
checkDailyInterest(interestModel, dueDate, startDay, 7, 0.19, 1.32);
checkDailyInterest(interestModel, dueDate, startDay, 8, 0.18, 1.50);
checkDailyInterest(interestModel, dueDate, startDay, 9, 0.19, 1.69);
checkDailyInterest(interestModel, dueDate, startDay, 10, 0.19, 1.88);
checkDailyInterest(interestModel, dueDate, startDay, 11, 0.19, 2.07);
checkDailyInterest(interestModel, dueDate, startDay, 12, 0.19, 2.26);
checkDailyInterest(interestModel, dueDate, startDay, 13, 0.18, 2.44);
checkDailyInterest(interestModel, dueDate, startDay, 14, 0.19, 2.63);
checkDailyInterest(interestModel, dueDate, startDay, 15, 0.19, 2.82);
checkDailyInterest(interestModel, dueDate, startDay, 16, 0.19, 3.01);
checkDailyInterest(interestModel, dueDate, startDay, 17, 0.19, 3.20);
checkDailyInterest(interestModel, dueDate, startDay, 18, 0.19, 3.39);
checkDailyInterest(interestModel, dueDate, startDay, 19, 0.18, 3.57);
checkDailyInterest(interestModel, dueDate, startDay, 20, 0.19, 3.76);
checkDailyInterest(interestModel, dueDate, startDay, 21, 0.19, 3.95);
checkDailyInterest(interestModel, dueDate, startDay, 22, 0.19, 4.14);
checkDailyInterest(interestModel, dueDate, startDay, 23, 0.19, 4.33);
checkDailyInterest(interestModel, dueDate, startDay, 24, 0.18, 4.51);
checkDailyInterest(interestModel, dueDate, startDay, 25, 0.19, 4.70);
checkDailyInterest(interestModel, dueDate, startDay, 26, 0.19, 4.89);
checkDailyInterest(interestModel, dueDate, startDay, 27, 0.19, 5.08);
checkDailyInterest(interestModel, dueDate, startDay, 28, 0.19, 5.27);
checkDailyInterest(interestModel, dueDate, startDay, 29, 0.18, 5.45);
checkDailyInterest(interestModel, dueDate, startDay, 30, 0.19, 5.64);
checkDailyInterest(interestModel, dueDate, startDay, 31, 0.19, 5.83);
}
@Test
public void test_dailyInterest_disbursedAmt2000_dayInYears360_daysInMonth30_repayIn2Month() {
final List<LoanScheduleModelRepaymentPeriod> expectedRepaymentPeriods = new ArrayList<>();
expectedRepaymentPeriods.add(repayment(1, LocalDate.of(2024, 1, 1), LocalDate.of(2024, 2, 1)));
final BigDecimal interestRate = BigDecimal.valueOf(7.0);
final Integer installmentAmountInMultiplesOf = null;
Mockito.when(loanProductRelatedDetail.getAnnualNominalInterestRate()).thenReturn(interestRate);
Mockito.when(loanProductRelatedDetail.getDaysInYearType()).thenReturn(DaysInYearType.DAYS_360.getValue());
Mockito.when(loanProductRelatedDetail.getDaysInMonthType()).thenReturn(DaysInMonthType.DAYS_30.getValue());
Mockito.when(loanProductRelatedDetail.getRepaymentPeriodFrequencyType()).thenReturn(PeriodFrequencyType.MONTHS);
Mockito.when(loanProductRelatedDetail.getRepayEvery()).thenReturn(1);
Mockito.when(loanProductRelatedDetail.getCurrency()).thenReturn(monetaryCurrency);
final ProgressiveLoanInterestScheduleModel interestModel = emiCalculator.generateInterestScheduleModel(expectedRepaymentPeriods,
loanProductRelatedDetail, installmentAmountInMultiplesOf, mc);
final Money disbursedAmount1st = toMoney(1000.0);
final Money disbursedAmount2nd = toMoney(1000.0);
emiCalculator.addDisbursement(interestModel, LocalDate.of(2024, 1, 1), disbursedAmount1st);
emiCalculator.addDisbursement(interestModel, LocalDate.of(2024, 1, 15), disbursedAmount2nd);
checkPeriod(interestModel, 0, 0, 2009.03, 0.0, 0.0, 9.03, 2000.0, 0.0);
checkPeriod(interestModel, 0, 1, 2009.03, 0.002634408602, 2.63, 9.03, 2000.0, 0.0);
checkPeriod(interestModel, 0, 2, 2009.03, 0.003198924731, 6.40, 9.03, 2000.0, 0.0);
final LocalDate dueDate = LocalDate.of(2024, 2, 1);
final LocalDate startDay = LocalDate.of(2024, 1, 1);
// 1st 1000 disbursement accruals
// Total Interest: 5.83 (31 days), 2.63 (14 days)
checkDailyInterest(interestModel, dueDate, startDay, 1, 0.19, 0.19);
checkDailyInterest(interestModel, dueDate, startDay, 2, 0.19, 0.38);
checkDailyInterest(interestModel, dueDate, startDay, 3, 0.18, 0.56);
checkDailyInterest(interestModel, dueDate, startDay, 4, 0.19, 0.75);
checkDailyInterest(interestModel, dueDate, startDay, 5, 0.19, 0.94);
checkDailyInterest(interestModel, dueDate, startDay, 6, 0.19, 1.13);
checkDailyInterest(interestModel, dueDate, startDay, 7, 0.19, 1.32);
checkDailyInterest(interestModel, dueDate, startDay, 8, 0.18, 1.50);
checkDailyInterest(interestModel, dueDate, startDay, 9, 0.19, 1.69);
checkDailyInterest(interestModel, dueDate, startDay, 10, 0.19, 1.88);
checkDailyInterest(interestModel, dueDate, startDay, 11, 0.19, 2.07);
checkDailyInterest(interestModel, dueDate, startDay, 12, 0.19, 2.26);
checkDailyInterest(interestModel, dueDate, startDay, 13, 0.18, 2.44);
checkDailyInterest(interestModel, dueDate, startDay, 14, 0.19, 2.63);
// 2nd 1000 disbursement accruals
// Total Interest: 6.40 (17 days)
checkDailyInterest(interestModel, dueDate, startDay, 15, 0.38, 3.01);
checkDailyInterest(interestModel, dueDate, startDay, 16, 0.37, 3.38);
checkDailyInterest(interestModel, dueDate, startDay, 17, 0.38, 3.76);
checkDailyInterest(interestModel, dueDate, startDay, 18, 0.38, 4.14);
checkDailyInterest(interestModel, dueDate, startDay, 19, 0.37, 4.51);
checkDailyInterest(interestModel, dueDate, startDay, 20, 0.38, 4.89);
checkDailyInterest(interestModel, dueDate, startDay, 21, 0.38, 5.27);
checkDailyInterest(interestModel, dueDate, startDay, 22, 0.37, 5.64);
checkDailyInterest(interestModel, dueDate, startDay, 23, 0.38, 6.02);
checkDailyInterest(interestModel, dueDate, startDay, 24, 0.37, 6.39);
checkDailyInterest(interestModel, dueDate, startDay, 25, 0.38, 6.77);
checkDailyInterest(interestModel, dueDate, startDay, 26, 0.38, 7.15);
checkDailyInterest(interestModel, dueDate, startDay, 27, 0.37, 7.52);
checkDailyInterest(interestModel, dueDate, startDay, 28, 0.38, 7.90);
checkDailyInterest(interestModel, dueDate, startDay, 29, 0.38, 8.28);
checkDailyInterest(interestModel, dueDate, startDay, 30, 0.37, 8.65);
checkDailyInterest(interestModel, dueDate, startDay, 31, 0.38, 9.03);
}
private static LoanScheduleModelRepaymentPeriod repayment(int periodNumber, LocalDate fromDate, LocalDate dueDate) { private static LoanScheduleModelRepaymentPeriod repayment(int periodNumber, LocalDate fromDate, LocalDate dueDate) {
final Money zeroAmount = Money.zero(monetaryCurrency); final Money zeroAmount = Money.zero(monetaryCurrency);
return LoanScheduleModelRepaymentPeriod.repayment(periodNumber, fromDate, dueDate, zeroAmount, zeroAmount, zeroAmount, zeroAmount, return LoanScheduleModelRepaymentPeriod.repayment(periodNumber, fromDate, dueDate, zeroAmount, zeroAmount, zeroAmount, zeroAmount,
@@ -1126,6 +1258,17 @@ class ProgressiveEMICalculatorTest {
return period; return period;
} }
private static void checkDailyInterest(final ProgressiveLoanInterestScheduleModel interestModel, final LocalDate repaymentPeriodDueDate,
final LocalDate interestStartDay, final int dayOffset, final double dailyInterest, final double interest) {
Money previousInterest = emiCalculator
.getPayableDetails(interestModel, repaymentPeriodDueDate, interestStartDay.plusDays(dayOffset - 1)).getPayableInterest();
Money currentInterest = emiCalculator.getPayableDetails(interestModel, repaymentPeriodDueDate, interestStartDay.plusDays(dayOffset))
.getPayableInterest();
Assertions.assertEquals(dailyInterest, toDouble(currentInterest.minus(previousInterest)));
Assertions.assertEquals(interest, toDouble(currentInterest));
}
private static void checkPeriod(final ProgressiveLoanInterestScheduleModel interestScheduleModel, final int repaymentIdx, private static void checkPeriod(final ProgressiveLoanInterestScheduleModel interestScheduleModel, final int repaymentIdx,
final int interestIdx, final double emiValue, final double rateFactor, final double interestDue, final double principalDue, final int interestIdx, final double emiValue, final double rateFactor, final double interestDue, final double principalDue,
final double remaingBalance) { final double remaingBalance) {
@@ -182,8 +182,8 @@ public class ExternalBusinessEventTest extends BaseLoanIntegrationTest {
Assertions.assertNotNull(postLoansLoanIdTransactionsResponse); Assertions.assertNotNull(postLoansLoanIdTransactionsResponse);
Assertions.assertNotNull(postLoansLoanIdTransactionsResponse.getResourceId()); Assertions.assertNotNull(postLoansLoanIdTransactionsResponse.getResourceId());
verifyBusinessEvents(new LoanTransactionBusinessEvent("LoanTransactionInterestRefundPostBusinessEvent", "22 January 2021", 5.75, verifyBusinessEvents(new LoanTransactionBusinessEvent("LoanTransactionInterestRefundPostBusinessEvent", "22 January 2021", 5.74,
0.0, 5.75, 0.0, 0.0, 0.0)); 0.0, 5.74, 0.0, 0.0, 0.0));
}); });
enableLoanInterestRefundPstBusinessEvent(false); enableLoanInterestRefundPstBusinessEvent(false);
} }
@@ -148,8 +148,8 @@ public class LoanInterestRefundTest extends BaseLoanIntegrationTest {
verifyTransactions(loanId, // verifyTransactions(loanId, //
transaction(1000.0, "Disbursement", "01 January 2021"), // transaction(1000.0, "Disbursement", "01 January 2021"), //
transaction(1000.0, "Payout Refund", "22 January 2021"), // transaction(1000.0, "Payout Refund", "22 January 2021"), //
transaction(5.75, "Interest Refund", "22 January 2021"), // transaction(5.74, "Interest Refund", "22 January 2021"), //
transaction(5.75, "Accrual", "22 January 2021")); // transaction(5.74, "Accrual", "22 January 2021")); //
checkTransactionWasNotReverseReplayed(postLoansLoanIdTransactionsResponse.getLoanId(), checkTransactionWasNotReverseReplayed(postLoansLoanIdTransactionsResponse.getLoanId(),
postLoansLoanIdTransactionsResponse.getResourceId()); postLoansLoanIdTransactionsResponse.getResourceId());
@@ -158,12 +158,12 @@ public class LoanInterestRefundTest extends BaseLoanIntegrationTest {
verifyTRJournalEntries(postLoansLoanIdTransactionsResponse.getResourceId(), // verifyTRJournalEntries(postLoansLoanIdTransactionsResponse.getResourceId(), //
journalEntry(1000, fundSource, "DEBIT"), // journalEntry(1000, fundSource, "DEBIT"), //
journalEntry(5.75, interestReceivableAccount, "CREDIT"), // journalEntry(5.74, interestReceivableAccount, "CREDIT"), //
journalEntry(994.25, loansReceivableAccount, "CREDIT")); journalEntry(994.26, loansReceivableAccount, "CREDIT"));
verifyTRJournalEntries(postLoansLoanIdTransactionsResponse.getSubResourceId(), verifyTRJournalEntries(postLoansLoanIdTransactionsResponse.getSubResourceId(),
journalEntry(5.75, interestIncomeAccount, "DEBIT"), // journalEntry(5.74, interestIncomeAccount, "DEBIT"), //
journalEntry(5.75, loansReceivableAccount, "CREDIT")); // journalEntry(5.74, loansReceivableAccount, "CREDIT")); //
}); });
} }
@@ -204,8 +204,8 @@ public class LoanInterestRefundTest extends BaseLoanIntegrationTest {
logLoanTransactions(loanId); logLoanTransactions(loanId);
verifyTransactions(loanId, transaction(1000.0, "Disbursement", "01 January 2021"), verifyTransactions(loanId, transaction(1000.0, "Disbursement", "01 January 2021"),
transaction(1000.0, "Merchant Issued Refund", "22 January 2021"), transaction(5.75, "Accrual", "22 January 2021"), transaction(1000.0, "Merchant Issued Refund", "22 January 2021"), transaction(5.74, "Accrual", "22 January 2021"),
transaction(5.75, "Interest Refund", "22 January 2021")); transaction(5.74, "Interest Refund", "22 January 2021"));
}); });
} }
@@ -235,8 +235,8 @@ public class LoanInterestRefundTest extends BaseLoanIntegrationTest {
logLoanTransactions(loanId); logLoanTransactions(loanId);
verifyTransactions(loanId, transaction(1000.0, "Disbursement", "01 January 2021"), verifyTransactions(loanId, transaction(1000.0, "Disbursement", "01 January 2021"),
transaction(1000.0, "Payout Refund", "22 January 2021"), transaction(5.75, "Accrual", "22 January 2021"), transaction(1000.0, "Payout Refund", "22 January 2021"), transaction(5.74, "Accrual", "22 January 2021"),
transaction(5.75, "Interest Refund", "22 January 2021")); transaction(5.74, "Interest Refund", "22 January 2021"));
}); });
} }
@@ -310,7 +310,7 @@ public class LoanInterestRefundTest extends BaseLoanIntegrationTest {
logLoanTransactions(loanId); logLoanTransactions(loanId);
verifyTransactions(loanId, transaction(1000.0, "Disbursement", "01 January 2021"), verifyTransactions(loanId, transaction(1000.0, "Disbursement", "01 January 2021"),
transaction(1000.0, "Payout Refund", "09 February 2021"), transaction(87.89, "Repayment", "01 February 2021"), transaction(1000.0, "Payout Refund", "09 February 2021"), transaction(87.89, "Repayment", "01 February 2021"),
transaction(10.50, "Interest Refund", "09 February 2021")); transaction(10.49, "Interest Refund", "09 February 2021"));
}); });
} }
@@ -345,7 +345,7 @@ public class LoanInterestRefundTest extends BaseLoanIntegrationTest {
logLoanTransactions(loanId); logLoanTransactions(loanId);
verifyTransactions(loanId, transaction(750.0, "Disbursement", "01 January 2021"), verifyTransactions(loanId, transaction(750.0, "Disbursement", "01 January 2021"),
transaction(250.0, "Disbursement", "01 January 2021"), transaction(1000.0, "Payout Refund", "22 January 2021"), transaction(250.0, "Disbursement", "01 January 2021"), transaction(1000.0, "Payout Refund", "22 January 2021"),
transaction(5.75, "Accrual", "22 January 2021"), transaction(5.75, "Interest Refund", "22 January 2021")); transaction(5.74, "Accrual", "22 January 2021"), transaction(5.74, "Interest Refund", "22 January 2021"));
}); });
} }
@@ -381,7 +381,7 @@ public class LoanInterestRefundTest extends BaseLoanIntegrationTest {
logLoanTransactions(loanId); logLoanTransactions(loanId);
verifyTransactions(loanId, transaction(750.0, "Disbursement", "04 January 2021"), verifyTransactions(loanId, transaction(750.0, "Disbursement", "04 January 2021"),
transaction(250.0, "Disbursement", "01 January 2021"), transaction(1000.0, "Payout Refund", "22 January 2021"), transaction(250.0, "Disbursement", "01 January 2021"), transaction(1000.0, "Payout Refund", "22 January 2021"),
transaction(5.14, "Accrual", "22 January 2021"), transaction(5.14, "Interest Refund", "22 January 2021")); transaction(5.13, "Accrual", "22 January 2021"), transaction(5.13, "Interest Refund", "22 January 2021"));
}); });
} }
@@ -508,7 +508,7 @@ public class LoanInterestRefundTest extends BaseLoanIntegrationTest {
verifyTransactions(loanId, transaction(1000.0, "Disbursement", "01 January 2021"), // verifyTransactions(loanId, transaction(1000.0, "Disbursement", "01 January 2021"), //
transaction(87.89, "Repayment", "01 February 2021"), // transaction(87.89, "Repayment", "01 February 2021"), //
transaction(500.0, "Payout Refund", "09 February 2021"), // transaction(500.0, "Payout Refund", "09 February 2021"), //
transaction(5.35, "Interest Refund", "09 February 2021")); transaction(5.34, "Interest Refund", "09 February 2021"));
}); });
} }
@@ -543,7 +543,7 @@ public class LoanInterestRefundTest extends BaseLoanIntegrationTest {
verifyTransactions(loanId, transaction(250.0, "Disbursement", "01 January 2021"), // verifyTransactions(loanId, transaction(250.0, "Disbursement", "01 January 2021"), //
transaction(750.0, "Disbursement", "01 January 2021"), // transaction(750.0, "Disbursement", "01 January 2021"), //
transaction(500.0, "Payout Refund", "22 January 2021"), // transaction(500.0, "Payout Refund", "22 January 2021"), //
transaction(2.88, "Interest Refund", "22 January 2021")); transaction(2.87, "Interest Refund", "22 January 2021"));
}); });
} }
@@ -580,7 +580,7 @@ public class LoanInterestRefundTest extends BaseLoanIntegrationTest {
verifyTransactions(loanId, transaction(250.0, "Disbursement", "01 January 2021"), // verifyTransactions(loanId, transaction(250.0, "Disbursement", "01 January 2021"), //
transaction(750.0, "Disbursement", "07 January 2021"), // transaction(750.0, "Disbursement", "07 January 2021"), //
transaction(500.0, "Payout Refund", "22 January 2021"), // transaction(500.0, "Payout Refund", "22 January 2021"), //
transaction(2.47, "Interest Refund", "22 January 2021")); transaction(2.46, "Interest Refund", "22 January 2021"));
}); });
} }
@@ -766,7 +766,7 @@ public class LoanInterestRefundTest extends BaseLoanIntegrationTest {
verifyTransactions(loanId, transaction(250.0, "Disbursement", "01 January 2021"), // verifyTransactions(loanId, transaction(250.0, "Disbursement", "01 January 2021"), //
transaction(750.0, "Disbursement", "01 January 2021"), // transaction(750.0, "Disbursement", "01 January 2021"), //
transaction(500.0, "Merchant Issued Refund", "22 January 2021"), // transaction(500.0, "Merchant Issued Refund", "22 January 2021"), //
transaction(2.88, "Interest Refund", "22 January 2021") // transaction(2.87, "Interest Refund", "22 January 2021") //
); );
}); });
runAt("26 January 2021", () -> { runAt("26 January 2021", () -> {
@@ -776,14 +776,14 @@ public class LoanInterestRefundTest extends BaseLoanIntegrationTest {
verifyTransactions(loanId, transaction(250.0, "Disbursement", "01 January 2021"), // verifyTransactions(loanId, transaction(250.0, "Disbursement", "01 January 2021"), //
transaction(750.0, "Disbursement", "01 January 2021"), // transaction(750.0, "Disbursement", "01 January 2021"), //
transaction(500.0, "Merchant Issued Refund", "22 January 2021"), // transaction(500.0, "Merchant Issued Refund", "22 January 2021"), //
transaction(2.88, "Interest Refund", "22 January 2021"), // transaction(2.87, "Interest Refund", "22 January 2021"), //
transaction(400.0, "Payout Refund", "26 January 2021"), // transaction(400.0, "Payout Refund", "26 January 2021"), //
transaction(2.74, "Interest Refund", "26 January 2021") // transaction(2.73, "Interest Refund", "26 January 2021") //
); );
}); });
runAt("1 February 2021", () -> { runAt("1 February 2021", () -> {
Long loanId = loanIdRef.get(); Long loanId = loanIdRef.get();
loanTransactionHelper.makeLoanRepayment("Repayment", "1 February 2021", 100.85F, loanId.intValue()); loanTransactionHelper.makeLoanRepayment("Repayment", "1 February 2021", 100.86F, loanId.intValue());
GetLoansLoanIdResponse loanDetails = loanTransactionHelper.getLoanDetails(loanId); GetLoansLoanIdResponse loanDetails = loanTransactionHelper.getLoanDetails(loanId);
Assertions.assertNotNull(loanDetails); Assertions.assertNotNull(loanDetails);
Assertions.assertNotNull(loanDetails.getStatus()); Assertions.assertNotNull(loanDetails.getStatus());
@@ -844,7 +844,7 @@ public class LoanInterestRefundTest extends BaseLoanIntegrationTest {
Long loanId = loanIdRef.get(); Long loanId = loanIdRef.get();
loanTransactionHelper.makeLoanRepayment("Repayment", "1 February 2021", 171.41F, loanId.intValue()); loanTransactionHelper.makeLoanRepayment("Repayment", "1 February 2021", 171.41F, loanId.intValue());
loanTransactionHelper.makeLoanRepayment("Repayment", "1 March 2021", 171.41F, loanId.intValue()); loanTransactionHelper.makeLoanRepayment("Repayment", "1 March 2021", 171.41F, loanId.intValue());
loanTransactionHelper.makeLoanRepayment("Repayment", "1 April 2021", 11.26F, loanId.intValue()); loanTransactionHelper.makeLoanRepayment("Repayment", "1 April 2021", 11.25F, loanId.intValue());
GetLoansLoanIdResponse loanDetails = loanTransactionHelper.getLoanDetails(loanId); GetLoansLoanIdResponse loanDetails = loanTransactionHelper.getLoanDetails(loanId);
Assertions.assertNotNull(loanDetails); Assertions.assertNotNull(loanDetails);
@@ -912,7 +912,7 @@ public class LoanInterestRefundTest extends BaseLoanIntegrationTest {
runAt("1 April 2021", () -> { runAt("1 April 2021", () -> {
Long loanId = loanIdRef.get(); Long loanId = loanIdRef.get();
loanTransactionHelper.makeLoanRepayment("Repayment", "1 March 2021", 171.41F, loanId.intValue()); loanTransactionHelper.makeLoanRepayment("Repayment", "1 March 2021", 171.41F, loanId.intValue());
loanTransactionHelper.makeLoanRepayment("Repayment", "1 April 2021", 11.16F, loanId.intValue()); loanTransactionHelper.makeLoanRepayment("Repayment", "1 April 2021", 11.17F, loanId.intValue());
GetLoansLoanIdResponse loanDetails = loanTransactionHelper.getLoanDetails(loanId); GetLoansLoanIdResponse loanDetails = loanTransactionHelper.getLoanDetails(loanId);
Assertions.assertNotNull(loanDetails); Assertions.assertNotNull(loanDetails);