FINERACT-2114: EMI Calculator outstanding balance correction
This commit is contained in:
committed by
Adam Saghy
parent
a6e35170a7
commit
803a575b04
+11
-2
@@ -35,10 +35,11 @@ public class ProgressiveLoanInterestRepaymentInterestPeriod implements Comparabl
|
||||
private BigDecimal rateFactorMinus1;
|
||||
|
||||
private Money disbursedAmount;
|
||||
private Money correctionAmount;
|
||||
private Money interestDue;
|
||||
|
||||
public ProgressiveLoanInterestRepaymentInterestPeriod(final ProgressiveLoanInterestRepaymentInterestPeriod period) {
|
||||
this(period.fromDate, period.dueDate, period.rateFactorMinus1, period.disbursedAmount, period.interestDue);
|
||||
this(period.fromDate, period.dueDate, period.rateFactorMinus1, period.disbursedAmount, period.correctionAmount, period.interestDue);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -47,6 +48,14 @@ public class ProgressiveLoanInterestRepaymentInterestPeriod implements Comparabl
|
||||
}
|
||||
|
||||
public void addDisbursedAmount(final Money outstandingBalance) {
|
||||
this.disbursedAmount = this.disbursedAmount.add(outstandingBalance);
|
||||
if (outstandingBalance != null && !outstandingBalance.isZero()) {
|
||||
this.disbursedAmount = this.disbursedAmount.add(outstandingBalance);
|
||||
}
|
||||
}
|
||||
|
||||
public void addCorrectionAmount(final Money correctionAmount) {
|
||||
if (correctionAmount != null && !correctionAmount.isZero()) {
|
||||
this.correctionAmount = this.correctionAmount.add(correctionAmount);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+16
-4
@@ -32,27 +32,34 @@ public class ProgressiveLoanInterestRepaymentModel {
|
||||
|
||||
private LinkedList<ProgressiveLoanInterestRepaymentInterestPeriod> interestPeriods;
|
||||
|
||||
private boolean isLastPeriod;
|
||||
|
||||
private Money equalMonthlyInstallment;
|
||||
private Money principalDue;
|
||||
private Money remainingBalance;
|
||||
private Money outstandingBalance;
|
||||
|
||||
public ProgressiveLoanInterestRepaymentModel(final LocalDate fromDate, final LocalDate dueDate, final Money equalMonthlyInstallment) {
|
||||
this.fromDate = fromDate;
|
||||
this.dueDate = dueDate;
|
||||
this.equalMonthlyInstallment = equalMonthlyInstallment;
|
||||
this.isLastPeriod = false;
|
||||
|
||||
final Money zeroAmount = Money.zero(equalMonthlyInstallment.getCurrency());
|
||||
this.outstandingBalance = zeroAmount;
|
||||
this.remainingBalance = zeroAmount;
|
||||
this.principalDue = zeroAmount;
|
||||
this.interestPeriods = new LinkedList<>();
|
||||
this.interestPeriods
|
||||
.add(new ProgressiveLoanInterestRepaymentInterestPeriod(fromDate, dueDate, BigDecimal.ZERO, zeroAmount, zeroAmount));
|
||||
this.interestPeriods.add(
|
||||
new ProgressiveLoanInterestRepaymentInterestPeriod(fromDate, dueDate, BigDecimal.ZERO, zeroAmount, zeroAmount, zeroAmount));
|
||||
}
|
||||
|
||||
public ProgressiveLoanInterestRepaymentModel(ProgressiveLoanInterestRepaymentModel repaymentModel) {
|
||||
this.fromDate = repaymentModel.fromDate;
|
||||
this.dueDate = repaymentModel.dueDate;
|
||||
this.isLastPeriod = repaymentModel.isLastPeriod;
|
||||
this.equalMonthlyInstallment = repaymentModel.equalMonthlyInstallment;
|
||||
this.outstandingBalance = repaymentModel.outstandingBalance;
|
||||
this.remainingBalance = repaymentModel.remainingBalance;
|
||||
this.principalDue = repaymentModel.principalDue;
|
||||
this.interestPeriods = new LinkedList<>();
|
||||
@@ -76,7 +83,12 @@ public class ProgressiveLoanInterestRepaymentModel {
|
||||
.reduce(Money.zero(equalMonthlyInstallment.getCurrency()), Money::plus);
|
||||
}
|
||||
|
||||
public Money getOutstandingBalance() {
|
||||
return remainingBalance.plus(principalDue);
|
||||
public Money getCorrectionAmount() {
|
||||
return interestPeriods.stream().map(ProgressiveLoanInterestRepaymentInterestPeriod::getCorrectionAmount)
|
||||
.reduce(Money.zero(equalMonthlyInstallment.getCurrency()), Money::plus);
|
||||
}
|
||||
|
||||
public Money getCorrectedOutstandingBalance() {
|
||||
return outstandingBalance.plus(getCorrectionAmount());
|
||||
}
|
||||
}
|
||||
|
||||
+5
@@ -46,6 +46,11 @@ public interface EMICalculator {
|
||||
void changeInterestRate(ProgressiveLoanInterestScheduleModel scheduleModel, LocalDate newInterestEffectiveDate,
|
||||
BigDecimal newInterestRate);
|
||||
|
||||
void addBalanceCorrection(ProgressiveLoanInterestScheduleModel scheduleModel, LocalDate balanceCorrectionDate,
|
||||
Money balanceCorrectionAmount);
|
||||
|
||||
Optional<ProgressiveLoanInterestRepaymentModel> getPayableDetails(ProgressiveLoanInterestScheduleModel scheduleModel, LocalDate date);
|
||||
|
||||
ProgressiveLoanInterestScheduleModel makeScheduleModelDeepCopy(ProgressiveLoanInterestScheduleModel scheduleModel);
|
||||
|
||||
ProgressiveLoanInterestScheduleModel makeScheduleModelDeepCopy(ProgressiveLoanInterestScheduleModel scheduleModel,
|
||||
|
||||
+109
-51
@@ -27,6 +27,7 @@ import java.util.Collections;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Predicate;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.apache.fineract.infrastructure.core.service.DateUtils;
|
||||
import org.apache.fineract.infrastructure.core.service.MathUtil;
|
||||
@@ -41,6 +42,7 @@ import org.apache.fineract.portfolio.loanaccount.loanschedule.data.ProgressiveLo
|
||||
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleModelRepaymentPeriod;
|
||||
import org.apache.fineract.portfolio.loanproduct.domain.LoanProductRelatedDetail;
|
||||
import org.apache.fineract.portfolio.loanproduct.mapper.ProgressiveLoanInterestRepaymentModelMapper;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Component
|
||||
@@ -61,6 +63,9 @@ public final class ProgressiveEMICalculator implements EMICalculator {
|
||||
interestRepaymentModelList
|
||||
.add(new ProgressiveLoanInterestRepaymentModel(period.periodFromDate(), period.periodDueDate(), zeroAmount));
|
||||
}
|
||||
if (!interestRepaymentModelList.isEmpty()) {
|
||||
interestRepaymentModelList.get(interestRepaymentModelList.size() - 1).setLastPeriod(true);
|
||||
}
|
||||
return new ProgressiveLoanInterestScheduleModel(interestRepaymentModelList, loanProductRelatedDetail,
|
||||
installmentAmountInMultiplesOf, mc);
|
||||
}
|
||||
@@ -76,24 +81,24 @@ public final class ProgressiveEMICalculator implements EMICalculator {
|
||||
.findFirst();
|
||||
}
|
||||
|
||||
Optional<ProgressiveLoanInterestRepaymentModel> findInterestRepaymentPeriodForDisbursement(
|
||||
final ProgressiveLoanInterestScheduleModel scheduleModel, final LocalDate disbursementDueDate) {
|
||||
if (scheduleModel == null || disbursementDueDate == null) {
|
||||
Optional<ProgressiveLoanInterestRepaymentModel> findInterestRepaymentPeriodForBalanceChange(
|
||||
final ProgressiveLoanInterestScheduleModel scheduleModel, final LocalDate balanceChangeDate) {
|
||||
if (scheduleModel == null || balanceChangeDate == null) {
|
||||
return Optional.empty();
|
||||
}
|
||||
return scheduleModel.repayments().stream()//
|
||||
.filter(repaymentPeriod -> !disbursementDueDate.isBefore(repaymentPeriod.getFromDate())
|
||||
&& disbursementDueDate.isBefore(repaymentPeriod.getDueDate()))//
|
||||
.filter(repaymentPeriod -> !balanceChangeDate.isBefore(repaymentPeriod.getFromDate())
|
||||
&& (repaymentPeriod.isLastPeriod() || balanceChangeDate.isBefore(repaymentPeriod.getDueDate())))//
|
||||
.findFirst();
|
||||
}
|
||||
|
||||
Optional<ProgressiveLoanInterestRepaymentInterestPeriod> findInterestPeriodForDisbursement(
|
||||
final ProgressiveLoanInterestRepaymentModel repaymentPeriod, final LocalDate disbursementDueDate) {
|
||||
if (repaymentPeriod == null || disbursementDueDate == null) {
|
||||
Optional<ProgressiveLoanInterestRepaymentInterestPeriod> findInterestPeriodForBalanceChange(
|
||||
final ProgressiveLoanInterestRepaymentModel repaymentPeriod, final LocalDate balanceChangeDate) {
|
||||
if (repaymentPeriod == null || balanceChangeDate == null) {
|
||||
return Optional.empty();
|
||||
}
|
||||
return repaymentPeriod.getInterestPeriods().stream()//
|
||||
.filter(interestPeriod -> disbursementDueDate.isEqual(interestPeriod.getFromDate()))//
|
||||
.filter(interestPeriod -> balanceChangeDate.isEqual(interestPeriod.getFromDate()))//
|
||||
.findFirst();
|
||||
}
|
||||
|
||||
@@ -124,41 +129,54 @@ public final class ProgressiveEMICalculator implements EMICalculator {
|
||||
@Override
|
||||
public void addDisbursement(final ProgressiveLoanInterestScheduleModel scheduleModel, final LocalDate disbursementDueDate,
|
||||
final Money disbursedAmount) {
|
||||
final ProgressiveLoanInterestRepaymentModel repaymentPeriod = findInterestRepaymentPeriodForDisbursement(scheduleModel,
|
||||
disbursementDueDate).orElse(null);
|
||||
if (repaymentPeriod == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
var interestPeriodOptional = findInterestPeriodForDisbursement(repaymentPeriod, disbursementDueDate);
|
||||
if (interestPeriodOptional.isPresent()) {
|
||||
interestPeriodOptional.get().addDisbursedAmount(disbursedAmount);
|
||||
} else {
|
||||
insertInterestPeriod(repaymentPeriod, disbursementDueDate, disbursedAmount);
|
||||
}
|
||||
|
||||
calculateEMIValueAndRateFactors(repaymentPeriod.getDueDate(), scheduleModel);
|
||||
changeOutstandingBalanceAndUpdateInterestPeriods(scheduleModel, disbursementDueDate, disbursedAmount,
|
||||
Money.zero(disbursedAmount.getCurrency()))
|
||||
.ifPresent((repaymentPeriod) -> calculateEMIValueAndRateFactors(repaymentPeriod.getDueDate(), scheduleModel));
|
||||
}
|
||||
|
||||
void insertInterestPeriod(final ProgressiveLoanInterestRepaymentModel repaymentPeriod, final LocalDate disbursementDueDate,
|
||||
final Money disbursedAmount) {
|
||||
// disbursementDueDate is after disb.date because this case when disbursement date is different than interest
|
||||
Optional<ProgressiveLoanInterestRepaymentModel> changeOutstandingBalanceAndUpdateInterestPeriods(
|
||||
final ProgressiveLoanInterestScheduleModel scheduleModel, final LocalDate balanceChangeDate, final Money disbursedAmount,
|
||||
final Money correctionAmount) {
|
||||
return findInterestRepaymentPeriodForBalanceChange(scheduleModel, balanceChangeDate).stream().peek(repaymentPeriod -> {
|
||||
var interestPeriodOptional = findInterestPeriodForBalanceChange(repaymentPeriod, balanceChangeDate);
|
||||
if (interestPeriodOptional.isPresent()) {
|
||||
interestPeriodOptional.get().addDisbursedAmount(disbursedAmount);
|
||||
interestPeriodOptional.get().addCorrectionAmount(correctionAmount);
|
||||
} else {
|
||||
insertInterestPeriod(repaymentPeriod, balanceChangeDate, disbursedAmount, correctionAmount);
|
||||
}
|
||||
}).findFirst();
|
||||
}
|
||||
|
||||
void insertInterestPeriod(final ProgressiveLoanInterestRepaymentModel repaymentPeriod, final LocalDate balanceChangeDate,
|
||||
final Money disbursedAmount, final Money correctionAmount) {
|
||||
// balanceChangeDate is after disb.date because this case when disbursement date is different then interest
|
||||
// period start date
|
||||
final ProgressiveLoanInterestRepaymentInterestPeriod previousInterestPeriod = repaymentPeriod.getInterestPeriods().stream()
|
||||
.filter(interestPeriod -> disbursementDueDate.isAfter(interestPeriod.getFromDate())
|
||||
&& disbursementDueDate.isBefore(interestPeriod.getDueDate()))//
|
||||
.filter(balanceChangeRelatedPreviousInterestPeriod(repaymentPeriod, balanceChangeDate))//
|
||||
.findFirst()//
|
||||
.get();//
|
||||
|
||||
final var interestPeriod = new ProgressiveLoanInterestRepaymentInterestPeriod(disbursementDueDate,
|
||||
previousInterestPeriod.getDueDate(), BigDecimal.ZERO, disbursedAmount, Money.zero(disbursedAmount.getCurrency()));
|
||||
final boolean changeAfterLastRepaymentPeriod = repaymentPeriod.isLastPeriod()
|
||||
&& previousInterestPeriod.getDueDate().isEqual(repaymentPeriod.getDueDate());
|
||||
final LocalDate interestPeriodDueDate = changeAfterLastRepaymentPeriod ? balanceChangeDate.plusDays(1)
|
||||
: previousInterestPeriod.getDueDate();
|
||||
final var interestPeriod = new ProgressiveLoanInterestRepaymentInterestPeriod(balanceChangeDate, interestPeriodDueDate,
|
||||
BigDecimal.ZERO, disbursedAmount, correctionAmount, Money.zero(disbursedAmount.getCurrency()));
|
||||
|
||||
previousInterestPeriod.setDueDate(disbursementDueDate);
|
||||
previousInterestPeriod.setDueDate(balanceChangeDate);
|
||||
|
||||
repaymentPeriod.getInterestPeriods().add(interestPeriod);
|
||||
Collections.sort(repaymentPeriod.getInterestPeriods());
|
||||
}
|
||||
|
||||
private static @NotNull Predicate<ProgressiveLoanInterestRepaymentInterestPeriod> balanceChangeRelatedPreviousInterestPeriod(
|
||||
ProgressiveLoanInterestRepaymentModel repaymentPeriod, LocalDate balanceChangeDate) {
|
||||
return interestPeriod -> balanceChangeDate.isAfter(interestPeriod.getFromDate())
|
||||
&& (balanceChangeDate.isBefore(interestPeriod.getDueDate())
|
||||
|| (repaymentPeriod.isLastPeriod() && !balanceChangeDate.isBefore(repaymentPeriod.getDueDate())));
|
||||
}
|
||||
|
||||
@Override
|
||||
public ProgressiveLoanInterestScheduleModel makeScheduleModelDeepCopy(final ProgressiveLoanInterestScheduleModel scheduleModel) {
|
||||
return makeScheduleModelDeepCopy(scheduleModel, scheduleModel.loanProductRelatedDetail(),
|
||||
@@ -203,7 +221,7 @@ public final class ProgressiveEMICalculator implements EMICalculator {
|
||||
|
||||
final Money zeroAmount = Money.zero(scheduleModel.loanProductRelatedDetail().getCurrency());
|
||||
final var interestPeriod = new ProgressiveLoanInterestRepaymentInterestPeriod(interestChangeDueDate,
|
||||
previousInterestPeriod.getDueDate(), BigDecimal.ZERO, zeroAmount, zeroAmount);
|
||||
previousInterestPeriod.getDueDate(), BigDecimal.ZERO, zeroAmount, zeroAmount, zeroAmount);
|
||||
|
||||
previousInterestPeriod.setDueDate(interestChangeDueDate);
|
||||
|
||||
@@ -211,6 +229,30 @@ public final class ProgressiveEMICalculator implements EMICalculator {
|
||||
Collections.sort(repaymentPeriod.getInterestPeriods());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addBalanceCorrection(ProgressiveLoanInterestScheduleModel scheduleModel, LocalDate balanceCorrectionDate,
|
||||
Money balanceCorrectionAmount) {
|
||||
final Money zeroAmount = Money.zero(balanceCorrectionAmount.getCurrency());
|
||||
changeOutstandingBalanceAndUpdateInterestPeriods(scheduleModel, balanceCorrectionDate, zeroAmount, balanceCorrectionAmount)
|
||||
.ifPresent(repaymentPeriod -> {
|
||||
calculateRateFactorMinus1ForRepaymentPeriod(repaymentPeriod, scheduleModel);
|
||||
calculatePrincipalInterestComponentsForPeriods(scheduleModel);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<ProgressiveLoanInterestRepaymentModel> getPayableDetails(ProgressiveLoanInterestScheduleModel scheduleModel,
|
||||
LocalDate date) {
|
||||
final var newScheduleModel = makeScheduleModelDeepCopy(scheduleModel);
|
||||
final var zeroAmount = Money.zero(scheduleModel.loanProductRelatedDetail().getCurrency());
|
||||
|
||||
return changeOutstandingBalanceAndUpdateInterestPeriods(newScheduleModel, date, zeroAmount, zeroAmount).stream()
|
||||
.peek(repaymentPeriod -> {
|
||||
calculateRateFactorMinus1ForRepaymentPeriod(repaymentPeriod, scheduleModel);
|
||||
calculatePrincipalInterestComponentsForPeriod(repaymentPeriod, date);
|
||||
}).findFirst();
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate Equal Monthly Installment value and Rate Factor -1 values for calculate Interest
|
||||
*/
|
||||
@@ -302,11 +344,13 @@ public final class ProgressiveEMICalculator implements EMICalculator {
|
||||
*/
|
||||
void calculateRateFactorMinus1ForPeriods(final List<ProgressiveLoanInterestRepaymentModel> repaymentPeriods,
|
||||
final ProgressiveLoanInterestScheduleModel scheduleModel) {
|
||||
for (var repaymentPeriod : repaymentPeriods) {
|
||||
for (var interestPeriod : repaymentPeriod.getInterestPeriods()) {
|
||||
interestPeriod.setRateFactorMinus1(calculateRateFactorMinus1PerPeriod(repaymentPeriod, interestPeriod, scheduleModel));
|
||||
}
|
||||
}
|
||||
repaymentPeriods.forEach(repaymentPeriod -> calculateRateFactorMinus1ForRepaymentPeriod(repaymentPeriod, scheduleModel));
|
||||
}
|
||||
|
||||
void calculateRateFactorMinus1ForRepaymentPeriod(final ProgressiveLoanInterestRepaymentModel repaymentPeriod,
|
||||
final ProgressiveLoanInterestScheduleModel scheduleModel) {
|
||||
repaymentPeriod.getInterestPeriods().forEach(interestPeriod -> interestPeriod
|
||||
.setRateFactorMinus1(calculateRateFactorMinus1PerPeriod(repaymentPeriod, interestPeriod, scheduleModel)));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -636,35 +680,45 @@ public final class ProgressiveEMICalculator implements EMICalculator {
|
||||
|
||||
void calculatePrincipalInterestComponentsForPeriods(final ProgressiveLoanInterestScheduleModel scheduleModel) {
|
||||
Money outstandingBalance = Money.zero(scheduleModel.loanProductRelatedDetail().getCurrency());
|
||||
int numberOfPeriods = scheduleModel.repayments().size();
|
||||
int period = 0;
|
||||
for (var repaymentPeriod : scheduleModel.repayments()) {
|
||||
final boolean isLastPeriod = ++period == numberOfPeriods;
|
||||
outstandingBalance = calculatePrincipalInterestComponentsForPeriod(repaymentPeriod, outstandingBalance, isLastPeriod);
|
||||
repaymentPeriod.setOutstandingBalance(outstandingBalance);
|
||||
calculatePrincipalInterestComponentsForPeriod(repaymentPeriod, null);
|
||||
outstandingBalance = repaymentPeriod.getRemainingBalance();
|
||||
}
|
||||
}
|
||||
|
||||
Money calculatePrincipalInterestComponentsForPeriod(final ProgressiveLoanInterestRepaymentModel repaymentPeriod,
|
||||
final Money initBalance, final boolean isLastPeriod) {
|
||||
Money outstandingBalance = initBalance;
|
||||
void calculatePrincipalInterestComponentsForPeriod(final ProgressiveLoanInterestRepaymentModel repaymentPeriod,
|
||||
final LocalDate calculateTill) {
|
||||
final Money zeroAmount = Money.zero(repaymentPeriod.getOutstandingBalance().getCurrency());
|
||||
Money outstandingBalance = repaymentPeriod.getOutstandingBalance();
|
||||
Money balanceCorrection = zeroAmount;
|
||||
Money cumulatedInterest = zeroAmount;
|
||||
|
||||
for (ProgressiveLoanInterestRepaymentInterestPeriod interestPeriod : repaymentPeriod.getInterestPeriods()) {
|
||||
final boolean shouldInvalidateInterestPeriod = calculateTill != null && interestPeriod.getDueDate().isAfter(calculateTill);
|
||||
if (shouldInvalidateInterestPeriod) {
|
||||
interestPeriod.setInterestDue(zeroAmount);
|
||||
interestPeriod.setDisbursedAmount(zeroAmount);
|
||||
interestPeriod.setCorrectionAmount(zeroAmount);
|
||||
continue;
|
||||
}
|
||||
outstandingBalance = outstandingBalance.plus(interestPeriod.getDisbursedAmount());
|
||||
final Money calculatedInterest = outstandingBalance.multipliedBy(interestPeriod.getRateFactorMinus1());
|
||||
balanceCorrection = balanceCorrection.plus(interestPeriod.getCorrectionAmount());
|
||||
final Money calculatedInterest = outstandingBalance.plus(balanceCorrection).multipliedBy(interestPeriod.getRateFactorMinus1());
|
||||
interestPeriod.setInterestDue(calculatedInterest);
|
||||
cumulatedInterest = cumulatedInterest.plus(calculatedInterest);
|
||||
}
|
||||
|
||||
final Money cumulatedInterest = repaymentPeriod.getInterestDue();
|
||||
final Money calculatedPrincipal = isLastPeriod ? outstandingBalance
|
||||
final Money calculatedPrincipal = repaymentPeriod.isLastPeriod() ? outstandingBalance
|
||||
: repaymentPeriod.getEqualMonthlyInstallment().minus(cumulatedInterest);
|
||||
|
||||
if (isLastPeriod) {
|
||||
if (repaymentPeriod.isLastPeriod()) {
|
||||
repaymentPeriod.setEqualMonthlyInstallment(calculatedPrincipal.add(cumulatedInterest));
|
||||
}
|
||||
|
||||
final Money remainingBalance = outstandingBalance.minus(calculatedPrincipal);
|
||||
repaymentPeriod.setPrincipalDue(calculatedPrincipal);
|
||||
repaymentPeriod.setRemainingBalance(outstandingBalance.minus(calculatedPrincipal));
|
||||
|
||||
return repaymentPeriod.getRemainingBalance();
|
||||
repaymentPeriod.setRemainingBalance(remainingBalance);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -674,6 +728,10 @@ public final class ProgressiveEMICalculator implements EMICalculator {
|
||||
.filter(period -> !period.isDownPayment() && !period.isAdditional()).toList();
|
||||
List<ProgressiveLoanInterestRepaymentModel> repaymentModels = progressiveLoanInterestRepaymentModelMapper
|
||||
.map(repaymentModelsWithoutDownPayment);
|
||||
|
||||
if (!repaymentModels.isEmpty()) {
|
||||
repaymentModels.get(repaymentModels.size() - 1).setLastPeriod(true);
|
||||
}
|
||||
return new ProgressiveLoanInterestScheduleModel(repaymentModels, loanProductRelatedDetail, installmentAmountInMultiplesOf, mc);
|
||||
}
|
||||
}
|
||||
|
||||
+92
@@ -34,6 +34,7 @@ import org.apache.fineract.portfolio.common.domain.DaysInMonthType;
|
||||
import org.apache.fineract.portfolio.common.domain.DaysInYearType;
|
||||
import org.apache.fineract.portfolio.common.domain.PeriodFrequencyType;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment;
|
||||
import org.apache.fineract.portfolio.loanaccount.loanschedule.data.ProgressiveLoanInterestRepaymentModel;
|
||||
import org.apache.fineract.portfolio.loanaccount.loanschedule.data.ProgressiveLoanInterestScheduleModel;
|
||||
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleModelRepaymentPeriod;
|
||||
import org.apache.fineract.portfolio.loanproduct.domain.LoanProductRelatedDetail;
|
||||
@@ -332,6 +333,97 @@ class ProgressiveEMICalculatorTest {
|
||||
checkPeriod(interestSchedule, 5, 0, 16.90, 0.003333333333, 0.06, 16.84, 0.0);
|
||||
}
|
||||
|
||||
/**
|
||||
* This test case tests a period early and late repayment with balance correction
|
||||
*/
|
||||
@Test
|
||||
public void testEMICalculation_disbursedAmt100_dayInYears360_daysInMonth30_repayEvery1Month_add_balance_on0215() {
|
||||
final MathContext mc = MoneyHelper.getMathContext();
|
||||
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 = new BigDecimal("7");
|
||||
final Integer installmentAmountInMultiplesOf = null;
|
||||
|
||||
Mockito.when(loanProductRelatedDetail.getNominalInterestRatePerPeriod()).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);
|
||||
|
||||
threadLocalContextUtil.when(ThreadLocalContextUtil::getBusinessDate).thenReturn(LocalDate.of(2024, 2, 15));
|
||||
|
||||
final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generateInterestScheduleModel(expectedRepaymentPeriods,
|
||||
loanProductRelatedDetail, installmentAmountInMultiplesOf, mc);
|
||||
|
||||
final Money disbursedAmount = Money.of(monetaryCurrency, BigDecimal.valueOf(100));
|
||||
emiCalculator.addDisbursement(interestSchedule, LocalDate.of(2024, 1, 1), disbursedAmount);
|
||||
|
||||
// partially pay off a period with balance correction
|
||||
final LocalDate op1stCorrectionDate = LocalDate.of(2024, 2, 15);
|
||||
final Money op1stCorrectionAmount = Money.of(monetaryCurrency, BigDecimal.valueOf(-83.57));
|
||||
|
||||
// get remaining balance and dues for a date
|
||||
final ProgressiveLoanInterestRepaymentModel repaymentDetails1st = emiCalculator
|
||||
.getPayableDetails(interestSchedule, op1stCorrectionDate).get();
|
||||
Assertions.assertEquals(83.57, toDouble(repaymentDetails1st.getOutstandingBalance().getAmount()));
|
||||
Assertions.assertEquals(16.77, toDouble(repaymentDetails1st.getPrincipalDue().getAmount()));
|
||||
Assertions.assertEquals(0.24, toDouble(repaymentDetails1st.getInterestDue().getAmount()));
|
||||
|
||||
emiCalculator.addBalanceCorrection(interestSchedule, op1stCorrectionDate, op1stCorrectionAmount);
|
||||
|
||||
checkDisbursementOnPeriod(interestSchedule, 0, disbursedAmount);
|
||||
checkPeriod(interestSchedule, 0, 0, 17.01, 0.005833333333, 0.58, 16.43, 83.57);
|
||||
checkPeriod(interestSchedule, 1, 0, 17.01, 0.002816091954, 0.24, 0.24, 16.77, 66.80);
|
||||
checkPeriod(interestSchedule, 1, 1, 17.01, 0.003017241379, 0.0, 0.24, 16.77, 66.80);
|
||||
checkPeriod(interestSchedule, 2, 0, 17.01, 0.005833333333, 0.39, 16.62, 50.18);
|
||||
checkPeriod(interestSchedule, 3, 0, 17.01, 0.005833333333, 0.29, 16.72, 33.46);
|
||||
checkPeriod(interestSchedule, 4, 0, 17.01, 0.005833333333, 0.20, 16.81, 16.65);
|
||||
checkPeriod(interestSchedule, 5, 0, 16.75, 0.005833333333, 0.10, 16.65, 0.0);
|
||||
|
||||
// totally pay off another period with balance correction
|
||||
final LocalDate op2ndCorrectionDate = LocalDate.of(2024, 3, 1);
|
||||
final Money op2ndCorrectionAmount = Money.of(monetaryCurrency, BigDecimal.valueOf(-66.80));
|
||||
|
||||
// get remaining balance and dues for a date
|
||||
final ProgressiveLoanInterestRepaymentModel repaymentDetails2st = emiCalculator
|
||||
.getPayableDetails(interestSchedule, op2ndCorrectionDate).get();
|
||||
Assertions.assertEquals(66.80, toDouble(repaymentDetails2st.getOutstandingBalance().getAmount()));
|
||||
Assertions.assertEquals(17.01, toDouble(repaymentDetails2st.getPrincipalDue().getAmount()));
|
||||
Assertions.assertEquals(0.0, toDouble(repaymentDetails2st.getInterestDue().getAmount()));
|
||||
|
||||
emiCalculator.addBalanceCorrection(interestSchedule, op2ndCorrectionDate, op2ndCorrectionAmount);
|
||||
|
||||
checkPeriod(interestSchedule, 0, 0, 17.01, 0.005833333333, 0.58, 16.43, 83.57);
|
||||
checkPeriod(interestSchedule, 1, 0, 17.01, 0.002816091954, 0.24, 0.24, 16.77, 66.80);
|
||||
checkPeriod(interestSchedule, 1, 1, 17.01, 0.003017241379, 0.0, 0.24, 16.77, 66.80);
|
||||
checkPeriod(interestSchedule, 2, 0, 17.01, 0.005833333333, 0, 17.01, 49.79);
|
||||
checkPeriod(interestSchedule, 3, 0, 17.01, 0.005833333333, 0.29, 16.72, 33.07);
|
||||
checkPeriod(interestSchedule, 4, 0, 17.01, 0.005833333333, 0.19, 16.82, 16.25);
|
||||
checkPeriod(interestSchedule, 5, 0, 16.34, 0.005833333333, 0.09, 16.25, 0.0);
|
||||
|
||||
// check numbers on last period due date
|
||||
final ProgressiveLoanInterestRepaymentModel repaymentDetails3rd = emiCalculator
|
||||
.getPayableDetails(interestSchedule, LocalDate.of(2024, 7, 1)).get();
|
||||
Assertions.assertEquals(16.25, toDouble(repaymentDetails3rd.getOutstandingBalance().getAmount()));
|
||||
Assertions.assertEquals(16.25, toDouble(repaymentDetails3rd.getPrincipalDue().getAmount()));
|
||||
Assertions.assertEquals(0.09, toDouble(repaymentDetails3rd.getInterestDue().getAmount()));
|
||||
|
||||
// check numbers after the last period due date
|
||||
final ProgressiveLoanInterestRepaymentModel repaymentDetails4th = emiCalculator
|
||||
.getPayableDetails(interestSchedule, LocalDate.of(2024, 7, 15)).get();
|
||||
Assertions.assertEquals(16.25, toDouble(repaymentDetails4th.getOutstandingBalance().getAmount()));
|
||||
Assertions.assertEquals(16.25, toDouble(repaymentDetails4th.getPrincipalDue().getAmount()));
|
||||
Assertions.assertEquals(0.14, toDouble(repaymentDetails4th.getInterestDue().getAmount()));
|
||||
}
|
||||
|
||||
// @Test
|
||||
// public void testEMICalculation_disbursedAmt100_dayInYearsActual_daysInMonthActual_repayEvery1Month_reschedule() {
|
||||
// final MathContext mc = MoneyHelper.getMathContext();
|
||||
|
||||
Reference in New Issue
Block a user