FINERACT-1981: Provide more general EMI calculator interface
This commit is contained in:
committed by
Adam Saghy
parent
c9d1b81cc6
commit
a4f077d675
+7
-7
@@ -119,13 +119,16 @@ public abstract class AbstractProgressiveLoanScheduleGenerator implements LoanSc
|
||||
: scheduleParams.getPeriodStartDate();
|
||||
List<PreGeneratedLoanSchedulePeriod> expectedRepaymentPeriods = getScheduledDateGenerator()
|
||||
.generateRepaymentPeriods(startDate, loanApplicationTerms, holidayDetailDTO);
|
||||
emiCalculationResult = getEMICalculator().calculateEMIValueAndRateFactors(loanApplicationTerms, scheduleParams,
|
||||
expectedRepaymentPeriods, mc);
|
||||
emiCalculationResult = getEMICalculator().calculateEMIValueAndRateFactors(scheduleParams.getOutstandingBalanceAsPerRest(),
|
||||
loanApplicationTerms.toLoanProductRelatedDetail(), expectedRepaymentPeriods, scheduleParams.getPeriodNumber(),
|
||||
loanApplicationTerms.getNumberOfRepayments(), mc);
|
||||
}
|
||||
|
||||
// 5 determine principal,interest of repayment period
|
||||
PrincipalInterest principalInterestForThisPeriod = calculatePrincipalInterestComponentsForPeriod(loanApplicationTerms,
|
||||
scheduleParams, emiCalculationResult, mc);
|
||||
PrincipalInterest principalInterestForThisPeriod = getEMICalculator().calculatePrincipalInterestComponentsForPeriod(
|
||||
emiCalculationResult, scheduleParams.getOutstandingBalanceAsPerRest(),
|
||||
loanApplicationTerms.getInstallmentAmountInMultiplesOf(), scheduleParams.getPeriodNumber(),
|
||||
loanApplicationTerms.getActualNoOfRepaymnets(), mc);
|
||||
|
||||
// update cumulative fields for principal
|
||||
currentPeriodParams.setPrincipalForThisPeriod(principalInterestForThisPeriod.principal());
|
||||
@@ -237,9 +240,6 @@ public abstract class AbstractProgressiveLoanScheduleGenerator implements LoanSc
|
||||
|
||||
public abstract PaymentPeriodsInOneYearCalculator getPaymentPeriodsInOneYearCalculator();
|
||||
|
||||
public abstract PrincipalInterest calculatePrincipalInterestComponentsForPeriod(LoanApplicationTerms loanApplicationTerms,
|
||||
LoanScheduleParams loanScheduleParams, EMICalculationResult emiCalculationResult, MathContext mc);
|
||||
|
||||
protected abstract EMICalculator getEMICalculator();
|
||||
|
||||
// Private, internal methods
|
||||
|
||||
-32
@@ -18,12 +18,7 @@
|
||||
*/
|
||||
package org.apache.fineract.portfolio.loanaccount.loanschedule.domain;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.math.MathContext;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.apache.fineract.organisation.monetary.domain.Money;
|
||||
import org.apache.fineract.portfolio.loanaccount.loanschedule.data.LoanScheduleParams;
|
||||
import org.apache.fineract.portfolio.loanproduct.calc.EMICalculationResult;
|
||||
import org.apache.fineract.portfolio.loanproduct.calc.EMICalculator;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@@ -49,31 +44,4 @@ public class ProgressiveLoanScheduleGenerator extends AbstractProgressiveLoanSch
|
||||
protected EMICalculator getEMICalculator() {
|
||||
return emiCalculator;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PrincipalInterest calculatePrincipalInterestComponentsForPeriod(final LoanApplicationTerms loanApplicationTerms,
|
||||
final LoanScheduleParams loanScheduleParams, final EMICalculationResult emiCalculationResult, final MathContext mc) {
|
||||
|
||||
final Money equalMonthlyInstallmentValue = loanApplicationTerms.getInstallmentAmountInMultiplesOf() != null
|
||||
? Money.roundToMultiplesOf(emiCalculationResult.getEqualMonthlyInstallmentValue(),
|
||||
loanApplicationTerms.getInstallmentAmountInMultiplesOf())
|
||||
: emiCalculationResult.getEqualMonthlyInstallmentValue();
|
||||
final BigDecimal rateFactorMinus1 = emiCalculationResult.getNextRepaymentPeriodRateFactorMinus1();
|
||||
final Money calculatedInterest = loanScheduleParams.getOutstandingBalanceAsPerRest().multipliedBy(rateFactorMinus1);
|
||||
final Money calculatedPrincipal = equalMonthlyInstallmentValue.minus(calculatedInterest);
|
||||
|
||||
return new PrincipalInterest(
|
||||
adjustCalculatedPrincipalWithRemainingBalanceInLastPeriod(calculatedPrincipal, loanApplicationTerms, loanScheduleParams),
|
||||
calculatedInterest, Money.zero(loanApplicationTerms.getCurrency()));
|
||||
}
|
||||
|
||||
private Money adjustCalculatedPrincipalWithRemainingBalanceInLastPeriod(final Money calculatedPrincipal,
|
||||
final LoanApplicationTerms loanApplicationTerms, final LoanScheduleParams loanScheduleParams) {
|
||||
final boolean isLastRepaymentPeriod = loanScheduleParams.getPeriodNumber() == loanApplicationTerms.getActualNoOfRepaymnets();
|
||||
if (isLastRepaymentPeriod) {
|
||||
final Money remainingAmount = loanScheduleParams.getOutstandingBalanceAsPerRest().minus(calculatedPrincipal);
|
||||
return calculatedPrincipal.plus(remainingAmount);
|
||||
}
|
||||
return calculatedPrincipal;
|
||||
}
|
||||
}
|
||||
|
||||
+10
-4
@@ -20,12 +20,18 @@ package org.apache.fineract.portfolio.loanproduct.calc;
|
||||
|
||||
import java.math.MathContext;
|
||||
import java.util.List;
|
||||
import org.apache.fineract.portfolio.loanaccount.loanschedule.data.LoanScheduleParams;
|
||||
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanApplicationTerms;
|
||||
import org.apache.fineract.organisation.monetary.domain.Money;
|
||||
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleModelPeriod;
|
||||
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.PrincipalInterest;
|
||||
import org.apache.fineract.portfolio.loanproduct.domain.LoanProductRelatedDetail;
|
||||
|
||||
public interface EMICalculator {
|
||||
|
||||
EMICalculationResult calculateEMIValueAndRateFactors(LoanApplicationTerms loanApplicationTerms, LoanScheduleParams scheduleParams,
|
||||
List<? extends LoanScheduleModelPeriod> expectedRepaymentPeriods, MathContext mc);
|
||||
EMICalculationResult calculateEMIValueAndRateFactors(Money outstandingBalanceAsPerRest,
|
||||
LoanProductRelatedDetail loanProductRelatedDetail, List<? extends LoanScheduleModelPeriod> expectedRepaymentPeriods,
|
||||
Integer actualPeriodNumber, Integer numberOfRepayments, MathContext mc);
|
||||
|
||||
PrincipalInterest calculatePrincipalInterestComponentsForPeriod(EMICalculationResult emiCalculationResult,
|
||||
Money outstandingBalanceAsPerRest, Integer installmentAmountInMultiplesOf, Integer actualPeriodNumber,
|
||||
Integer actualNoOfRepayments, MathContext mc);
|
||||
}
|
||||
|
||||
+48
-74
@@ -23,15 +23,15 @@ import java.math.MathContext;
|
||||
import java.time.LocalDate;
|
||||
import java.time.Year;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import org.apache.fineract.infrastructure.core.service.DateUtils;
|
||||
import org.apache.fineract.infrastructure.core.service.MathUtil;
|
||||
import org.apache.fineract.organisation.monetary.domain.Money;
|
||||
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.loanschedule.data.LoanScheduleParams;
|
||||
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanApplicationTerms;
|
||||
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleModelPeriod;
|
||||
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.PrincipalInterest;
|
||||
import org.apache.fineract.portfolio.loanproduct.domain.LoanProductRelatedDetail;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@@ -43,29 +43,13 @@ public final class ProgressiveEMICalculator implements EMICalculator {
|
||||
|
||||
/**
|
||||
* Calculate Equal Monthly Installment value and Rate Factor -1 values for calculate Interest
|
||||
*
|
||||
* @param loanApplicationTerms
|
||||
* LoanTermApplication
|
||||
*
|
||||
* @param scheduleParams
|
||||
* Loan Schedule Params
|
||||
*
|
||||
* @param expectedRepaymentPeriods
|
||||
* Expected Repayment Periods
|
||||
*
|
||||
* @param mc
|
||||
* MathContext for rounding
|
||||
*
|
||||
* @return EMICalculationResult Contains rate factor for each period and calculated EMI
|
||||
*/
|
||||
@Override
|
||||
public EMICalculationResult calculateEMIValueAndRateFactors(final LoanApplicationTerms loanApplicationTerms,
|
||||
final LoanScheduleParams scheduleParams, final List<? extends LoanScheduleModelPeriod> expectedRepaymentPeriods,
|
||||
final MathContext mc) {
|
||||
final LoanProductRelatedDetail loanProductRelatedDetail = loanApplicationTerms.toLoanProductRelatedDetail();
|
||||
public EMICalculationResult calculateEMIValueAndRateFactors(final Money outstandingBalanceAsPerRest,
|
||||
final LoanProductRelatedDetail loanProductRelatedDetail, final List<? extends LoanScheduleModelPeriod> expectedRepaymentPeriods,
|
||||
final Integer actualPeriodNumber, final Integer numberOfRepayments, final MathContext mc) {
|
||||
final BigDecimal nominalInterestRatePerPeriod = calcNominalInterestRatePerPeriod(
|
||||
loanProductRelatedDetail.getNominalInterestRatePerPeriod(), mc);
|
||||
final Money outstandingBalance = scheduleParams.getOutstandingBalanceAsPerRest();
|
||||
final DaysInYearType daysInYearType = DaysInYearType.fromInt(loanProductRelatedDetail.getDaysInYearType());
|
||||
final DaysInMonthType daysInMonthType = DaysInMonthType.fromInt(loanProductRelatedDetail.getDaysInMonthType());
|
||||
final PeriodFrequencyType repaymentFrequency = loanProductRelatedDetail.getRepaymentPeriodFrequencyType();
|
||||
@@ -74,7 +58,7 @@ public final class ProgressiveEMICalculator implements EMICalculator {
|
||||
final List<BigDecimal> rateFactorList = getRateFactorList(expectedRepaymentPeriods, nominalInterestRatePerPeriod, daysInYearType,
|
||||
daysInMonthType, repaymentFrequency, repaymentEvery, mc);
|
||||
|
||||
return calculateEMI(loanApplicationTerms, scheduleParams, rateFactorList, outstandingBalance, mc);
|
||||
return calculateEMI(rateFactorList, actualPeriodNumber, numberOfRepayments, outstandingBalanceAsPerRest, mc);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -203,23 +187,18 @@ public final class ProgressiveEMICalculator implements EMICalculator {
|
||||
|
||||
/**
|
||||
* Calculate EMI parts and return an EMI calculation result object with repayment installment rate factors
|
||||
*
|
||||
* @param rateFactorList
|
||||
* @param outstandingBalanceForRest
|
||||
* @param mc
|
||||
* @return
|
||||
*/
|
||||
EMICalculationResult calculateEMI(final LoanApplicationTerms loanApplicationTerms, final LoanScheduleParams loanScheduleParams,
|
||||
final List<BigDecimal> rateFactorList, final Money outstandingBalanceForRest, final MathContext mc) {
|
||||
EMICalculationResult calculateEMI(final List<BigDecimal> rateFactorList, final Integer actualPeriodNumber,
|
||||
final Integer numberOfRepayments, final Money outstandingBalanceForRest, final MathContext mc) {
|
||||
final BigDecimal rateFactorN = MathUtil.stripTrailingZeros(calculateRateFactorN(rateFactorList, mc));
|
||||
final BigDecimal fnResult = MathUtil.stripTrailingZeros(calculateFnResult(rateFactorList, mc));
|
||||
|
||||
final Money emiValue = Money.of(loanApplicationTerms.getCurrency(),
|
||||
final Money emiValue = Money.of(outstandingBalanceForRest.getCurrency(),
|
||||
calculateEMIValue(rateFactorN, outstandingBalanceForRest.getAmount(), fnResult, mc));
|
||||
final List<BigDecimal> rateFactorMinus1List = getRateFactorMinus1List(rateFactorList, mc);
|
||||
|
||||
final Money adjustedEqualMonthlyInstallmentValue = adjustEMIForMoreStreamlinedRepaymentSchedule(loanApplicationTerms,
|
||||
loanScheduleParams, emiValue, rateFactorMinus1List, mc);
|
||||
final Money adjustedEqualMonthlyInstallmentValue = adjustEMIForMoreStreamlinedRepaymentSchedule(actualPeriodNumber,
|
||||
numberOfRepayments, outstandingBalanceForRest, emiValue, rateFactorMinus1List, mc);
|
||||
|
||||
return new EMICalculationResult(adjustedEqualMonthlyInstallmentValue, rateFactorMinus1List);
|
||||
}
|
||||
@@ -228,23 +207,16 @@ public final class ProgressiveEMICalculator implements EMICalculator {
|
||||
* Due to rounding or unequal installments, the first calculated EMI might not be the best one! Reiterate with
|
||||
* adjusted EMI to get a better streamlined repayment schedule (less difference between calculated EMI and last
|
||||
* installment EMI).
|
||||
*
|
||||
* @param loanApplicationTerms
|
||||
* @param loanScheduleParams
|
||||
* @param equalMonthlyInstallmentValue
|
||||
* @param rateFactorMinus1List
|
||||
* @param mc
|
||||
* @return
|
||||
*/
|
||||
Money adjustEMIForMoreStreamlinedRepaymentSchedule(final LoanApplicationTerms loanApplicationTerms,
|
||||
final LoanScheduleParams loanScheduleParams, final Money equalMonthlyInstallmentValue, List<BigDecimal> rateFactorMinus1List,
|
||||
Money adjustEMIForMoreStreamlinedRepaymentSchedule(final Integer actualPeriodNumber, final Integer numberOfRepayments,
|
||||
final Money outstandingBalanceAsPerRest, final Money equalMonthlyInstallmentValue, List<BigDecimal> rateFactorMinus1List,
|
||||
final MathContext mc) {
|
||||
int numberOfUpcomingPeriods = loanApplicationTerms.getNumberOfRepayments() - loanScheduleParams.getPeriodNumber() + 1;
|
||||
int numberOfUpcomingPeriods = numberOfRepayments - actualPeriodNumber + 1;
|
||||
if (numberOfUpcomingPeriods < 2) {
|
||||
return equalMonthlyInstallmentValue;
|
||||
}
|
||||
|
||||
RepaymentScheduleModel repaymentScheduleModel = generateRepaymentScheduleModel(loanApplicationTerms, loanScheduleParams,
|
||||
RepaymentScheduleModel repaymentScheduleModel = generateRepaymentScheduleModel(numberOfRepayments, outstandingBalanceAsPerRest,
|
||||
equalMonthlyInstallmentValue, rateFactorMinus1List);
|
||||
Money calculatedLastEMI = repaymentScheduleModel.getScheduleList().get(repaymentScheduleModel.getScheduleList().size() - 1).emi();
|
||||
Money originalDifference = calculatedLastEMI.minus(equalMonthlyInstallmentValue);
|
||||
@@ -262,8 +234,8 @@ public final class ProgressiveEMICalculator implements EMICalculator {
|
||||
Money adjustment = originalDifference.dividedBy(numberOfUpcomingPeriods, mc.getRoundingMode());
|
||||
|
||||
Money adjustedEqualMonthlyInstallmentValue = equalMonthlyInstallmentValue.plus(adjustment);
|
||||
RepaymentScheduleModel repaymentScheduleModelWithAdjustedEMI = generateRepaymentScheduleModel(loanApplicationTerms,
|
||||
loanScheduleParams, adjustedEqualMonthlyInstallmentValue, rateFactorMinus1List);
|
||||
RepaymentScheduleModel repaymentScheduleModelWithAdjustedEMI = generateRepaymentScheduleModel(numberOfRepayments,
|
||||
outstandingBalanceAsPerRest, adjustedEqualMonthlyInstallmentValue, rateFactorMinus1List);
|
||||
Money calculatedLastEMIAfterAdjustment = repaymentScheduleModelWithAdjustedEMI.getScheduleList()
|
||||
.get(repaymentScheduleModelWithAdjustedEMI.getScheduleList().size() - 1).emi();
|
||||
Money differenceAfterEMIAdjustment = calculatedLastEMIAfterAdjustment.minus(adjustedEqualMonthlyInstallmentValue);
|
||||
@@ -275,16 +247,15 @@ public final class ProgressiveEMICalculator implements EMICalculator {
|
||||
}
|
||||
}
|
||||
|
||||
RepaymentScheduleModel generateRepaymentScheduleModel(LoanApplicationTerms loanApplicationTerms, LoanScheduleParams loanScheduleParams,
|
||||
RepaymentScheduleModel generateRepaymentScheduleModel(final Integer numberOfRepayments, final Money outstandingBalanceAsPerRest,
|
||||
Money equalMonthlyInstallmentValue, List<BigDecimal> rateFactorMinus1List) {
|
||||
RepaymentScheduleModel repaymentScheduleModel = new RepaymentScheduleModel();
|
||||
Money balanceOfLoan = loanScheduleParams.getOutstandingBalanceAsPerRest();
|
||||
for (int i = 0; i < loanApplicationTerms.getNumberOfRepayments(); i++) {
|
||||
Money balanceOfLoan = outstandingBalanceAsPerRest;
|
||||
for (int i = 0; i < numberOfRepayments; i++) {
|
||||
final Money calculatedInterest = balanceOfLoan.multipliedBy(rateFactorMinus1List.get(i));
|
||||
// WE need to calculate EMI differently for last installment (decided by number of repayments or when
|
||||
// schedule got shorter then planned)
|
||||
if (balanceOfLoan.isLessThan(equalMonthlyInstallmentValue.minus(calculatedInterest))
|
||||
|| i == loanApplicationTerms.getNumberOfRepayments() - 1) {
|
||||
if (balanceOfLoan.isLessThan(equalMonthlyInstallmentValue.minus(calculatedInterest)) || i == numberOfRepayments - 1) {
|
||||
equalMonthlyInstallmentValue = balanceOfLoan.plus(calculatedInterest);
|
||||
}
|
||||
final Money calculatedPrincipal = equalMonthlyInstallmentValue.minus(calculatedInterest);
|
||||
@@ -302,9 +273,6 @@ public final class ProgressiveEMICalculator implements EMICalculator {
|
||||
/**
|
||||
* Rate factor -1 values
|
||||
*
|
||||
* @param rateFactors
|
||||
* @param mc
|
||||
* @return
|
||||
*/
|
||||
List<BigDecimal> getRateFactorMinus1List(final List<BigDecimal> rateFactors, final MathContext mc) {
|
||||
return rateFactors.stream().map(it -> it.subtract(BigDecimal.ONE, mc)).toList();
|
||||
@@ -312,10 +280,6 @@ public final class ProgressiveEMICalculator implements EMICalculator {
|
||||
|
||||
/**
|
||||
* Calculate Rate Factor Product from rate factors
|
||||
*
|
||||
* @param rateFactors
|
||||
* @param mc
|
||||
* @return
|
||||
*/
|
||||
BigDecimal calculateRateFactorN(final List<BigDecimal> rateFactors, final MathContext mc) {
|
||||
return rateFactors.stream().reduce(BigDecimal.ONE, (BigDecimal acc, BigDecimal value) -> acc.multiply(value, mc));
|
||||
@@ -323,10 +287,6 @@ public final class ProgressiveEMICalculator implements EMICalculator {
|
||||
|
||||
/**
|
||||
* Summarize Fn values
|
||||
*
|
||||
* @param rateFactors
|
||||
* @param mc
|
||||
* @return
|
||||
*/
|
||||
BigDecimal calculateFnResult(final List<BigDecimal> rateFactors, final MathContext mc) {
|
||||
return rateFactors.stream().skip(1).reduce(BigDecimal.ONE,
|
||||
@@ -335,12 +295,6 @@ public final class ProgressiveEMICalculator implements EMICalculator {
|
||||
|
||||
/**
|
||||
* Calculate the EMI (Equal Monthly Installment) value
|
||||
*
|
||||
* @param rateFactorN
|
||||
* @param outstandingBalanceForRest
|
||||
* @param fnResult
|
||||
* @param mc
|
||||
* @return
|
||||
*/
|
||||
BigDecimal calculateEMIValue(final BigDecimal rateFactorN, final BigDecimal outstandingBalanceForRest, final BigDecimal fnResult,
|
||||
final MathContext mc) {
|
||||
@@ -484,13 +438,6 @@ public final class ProgressiveEMICalculator implements EMICalculator {
|
||||
/**
|
||||
* Calculate Rate Factor based on Partial Period
|
||||
*
|
||||
* @param interestRate
|
||||
* @param repaymentEvery
|
||||
* @param cumulatedPeriodRatio
|
||||
* @param actualDaysInPeriod
|
||||
* @param calculatedDaysInPeriod
|
||||
* @param mc
|
||||
* @return
|
||||
*/
|
||||
BigDecimal rateFactorByRepaymentPartialPeriod(final BigDecimal interestRate, final BigDecimal repaymentEvery,
|
||||
final BigDecimal cumulatedPeriodRatio, final BigDecimal actualDaysInPeriod, final BigDecimal calculatedDaysInPeriod,
|
||||
@@ -516,4 +463,31 @@ public final class ProgressiveEMICalculator implements EMICalculator {
|
||||
BigDecimal fnValue(final BigDecimal previousFnValue, final BigDecimal currentRateFactor, final MathContext mc) {
|
||||
return BigDecimal.ONE.add(previousFnValue.multiply(currentRateFactor, mc), mc);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PrincipalInterest calculatePrincipalInterestComponentsForPeriod(final EMICalculationResult emiCalculationResult,
|
||||
final Money outstandingBalanceAsPerRest, final Integer installmentAmountInMultiplesOf, final Integer actualPeriodNumber,
|
||||
final Integer actualNoOfRepayments, final MathContext mc) {
|
||||
|
||||
final Money equalMonthlyInstallmentValue = installmentAmountInMultiplesOf != null
|
||||
? Money.roundToMultiplesOf(emiCalculationResult.getEqualMonthlyInstallmentValue(), installmentAmountInMultiplesOf)
|
||||
: emiCalculationResult.getEqualMonthlyInstallmentValue();
|
||||
final BigDecimal rateFactorMinus1 = emiCalculationResult.getNextRepaymentPeriodRateFactorMinus1();
|
||||
final Money calculatedInterest = outstandingBalanceAsPerRest.multipliedBy(rateFactorMinus1);
|
||||
final Money calculatedPrincipal = equalMonthlyInstallmentValue.minus(calculatedInterest);
|
||||
return new PrincipalInterest(
|
||||
adjustCalculatedPrincipalWithRemainingBalanceInLastPeriod(calculatedPrincipal, outstandingBalanceAsPerRest,
|
||||
actualPeriodNumber, actualNoOfRepayments),
|
||||
calculatedInterest, Money.zero(equalMonthlyInstallmentValue.getCurrency()));
|
||||
}
|
||||
|
||||
Money adjustCalculatedPrincipalWithRemainingBalanceInLastPeriod(final Money calculatedPrincipal,
|
||||
final Money outstandingBalanceAsPerRest, final Integer actualPeriodNumber, final Integer actualNoOfRepayments) {
|
||||
final boolean isLastRepaymentPeriod = Objects.equals(actualPeriodNumber, actualNoOfRepayments);
|
||||
if (isLastRepaymentPeriod) {
|
||||
final Money remainingAmount = outstandingBalanceAsPerRest.minus(calculatedPrincipal);
|
||||
return calculatedPrincipal.plus(remainingAmount);
|
||||
}
|
||||
return calculatedPrincipal;
|
||||
}
|
||||
}
|
||||
|
||||
+24
-39
@@ -34,8 +34,6 @@ 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.LoanScheduleModelDownPaymentPeriod;
|
||||
import org.apache.fineract.portfolio.loanaccount.loanschedule.data.LoanScheduleParams;
|
||||
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanApplicationTerms;
|
||||
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleModelDisbursementPeriod;
|
||||
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleModelPeriod;
|
||||
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.PreGeneratedLoanSchedulePeriod;
|
||||
@@ -55,8 +53,6 @@ class ProgressiveEMICalculatorTest {
|
||||
private static final ProgressiveEMICalculator emiCalculator = new ProgressiveEMICalculator();
|
||||
|
||||
private static MockedStatic<MoneyHelper> moneyHelper = Mockito.mockStatic(MoneyHelper.class);
|
||||
private static LoanScheduleParams scheduleParams = Mockito.mock(LoanScheduleParams.class);
|
||||
private static LoanApplicationTerms loanApplicationTerms = Mockito.mock(LoanApplicationTerms.class);
|
||||
private static LoanProductRelatedDetail loanProductRelatedDetail = Mockito.mock(LoanProductRelatedDetail.class);
|
||||
|
||||
private static final MonetaryCurrency monetaryCurrency = MonetaryCurrency
|
||||
@@ -79,8 +75,6 @@ class ProgressiveEMICalculatorTest {
|
||||
// When
|
||||
moneyHelper.when(MoneyHelper::getRoundingMode).thenReturn(RoundingMode.HALF_EVEN);
|
||||
moneyHelper.when(MoneyHelper::getMathContext).thenReturn(new MathContext(12, RoundingMode.HALF_EVEN));
|
||||
Mockito.when(loanApplicationTerms.toLoanProductRelatedDetail()).thenReturn(loanProductRelatedDetail);
|
||||
Mockito.when(loanApplicationTerms.getCurrency()).thenReturn(monetaryCurrency);
|
||||
}
|
||||
|
||||
private BigDecimal getRateFactorsByMonth(final DaysInYearType daysInYearType, final DaysInMonthType daysInMonthType,
|
||||
@@ -163,15 +157,14 @@ class ProgressiveEMICalculatorTest {
|
||||
final BigDecimal principal = BigDecimal.valueOf(100);
|
||||
final Money outstandingBalance = Money.of(monetaryCurrency, principal);
|
||||
|
||||
Mockito.when(scheduleParams.getOutstandingBalanceAsPerRest()).thenReturn(outstandingBalance);
|
||||
Mockito.when(loanProductRelatedDetail.getNominalInterestRatePerPeriod()).thenReturn(interestRate);
|
||||
Mockito.when(loanProductRelatedDetail.getDaysInYearType()).thenReturn(DaysInYearType.ACTUAL.getValue());
|
||||
Mockito.when(loanProductRelatedDetail.getDaysInMonthType()).thenReturn(DaysInMonthType.ACTUAL.getValue());
|
||||
Mockito.when(loanProductRelatedDetail.getRepaymentPeriodFrequencyType()).thenReturn(PeriodFrequencyType.MONTHS);
|
||||
Mockito.when(loanProductRelatedDetail.getRepayEvery()).thenReturn(1);
|
||||
|
||||
final EMICalculationResult result = emiCalculator.calculateEMIValueAndRateFactors(loanApplicationTerms, scheduleParams,
|
||||
expectedRepaymentPeriods, mc);
|
||||
final EMICalculationResult result = emiCalculator.calculateEMIValueAndRateFactors(outstandingBalance, loanProductRelatedDetail,
|
||||
expectedRepaymentPeriods, 1, 6, mc);
|
||||
|
||||
// 17.13
|
||||
Assertions.assertEquals(BigDecimal.valueOf(17.13), result.getEqualMonthlyInstallmentValue().getAmount());
|
||||
@@ -205,15 +198,14 @@ class ProgressiveEMICalculatorTest {
|
||||
final BigDecimal principal = BigDecimal.valueOf(10_000);
|
||||
final Money outstandingBalance = Money.of(monetaryCurrency, principal);
|
||||
|
||||
Mockito.when(scheduleParams.getOutstandingBalanceAsPerRest()).thenReturn(outstandingBalance);
|
||||
Mockito.when(loanProductRelatedDetail.getNominalInterestRatePerPeriod()).thenReturn(interestRate);
|
||||
Mockito.when(loanProductRelatedDetail.getDaysInYearType()).thenReturn(DaysInYearType.ACTUAL.getValue());
|
||||
Mockito.when(loanProductRelatedDetail.getDaysInMonthType()).thenReturn(DaysInMonthType.ACTUAL.getValue());
|
||||
Mockito.when(loanProductRelatedDetail.getRepaymentPeriodFrequencyType()).thenReturn(PeriodFrequencyType.MONTHS);
|
||||
Mockito.when(loanProductRelatedDetail.getRepayEvery()).thenReturn(1);
|
||||
|
||||
final EMICalculationResult result = emiCalculator.calculateEMIValueAndRateFactors(loanApplicationTerms, scheduleParams,
|
||||
expectedRepaymentPeriods, mc);
|
||||
final EMICalculationResult result = emiCalculator.calculateEMIValueAndRateFactors(outstandingBalance, loanProductRelatedDetail,
|
||||
expectedRepaymentPeriods, 1, 6, mc);
|
||||
|
||||
// 1713.12
|
||||
Assertions.assertEquals(BigDecimal.valueOf(1713.12), result.getEqualMonthlyInstallmentValue().getAmount());
|
||||
@@ -244,15 +236,14 @@ class ProgressiveEMICalculatorTest {
|
||||
final BigDecimal principal = BigDecimal.valueOf(100);
|
||||
final Money outstandingBalance = Money.of(monetaryCurrency, principal);
|
||||
|
||||
Mockito.when(scheduleParams.getOutstandingBalanceAsPerRest()).thenReturn(outstandingBalance);
|
||||
Mockito.when(loanProductRelatedDetail.getNominalInterestRatePerPeriod()).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);
|
||||
|
||||
final EMICalculationResult result = emiCalculator.calculateEMIValueAndRateFactors(loanApplicationTerms, scheduleParams,
|
||||
expectedRepaymentPeriods, mc);
|
||||
final EMICalculationResult result = emiCalculator.calculateEMIValueAndRateFactors(outstandingBalance, loanProductRelatedDetail,
|
||||
expectedRepaymentPeriods, 1, 6, mc);
|
||||
|
||||
// 17.13
|
||||
Assertions.assertEquals(BigDecimal.valueOf(17.13), result.getEqualMonthlyInstallmentValue().getAmount());
|
||||
@@ -287,15 +278,14 @@ class ProgressiveEMICalculatorTest {
|
||||
final BigDecimal principal = BigDecimal.valueOf(100);
|
||||
final Money outstandingBalance = Money.of(monetaryCurrency, principal);
|
||||
|
||||
Mockito.when(scheduleParams.getOutstandingBalanceAsPerRest()).thenReturn(outstandingBalance);
|
||||
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);
|
||||
|
||||
final EMICalculationResult result = emiCalculator.calculateEMIValueAndRateFactors(loanApplicationTerms, scheduleParams,
|
||||
expectedRepaymentPeriods, mc);
|
||||
final EMICalculationResult result = emiCalculator.calculateEMIValueAndRateFactors(outstandingBalance, loanProductRelatedDetail,
|
||||
expectedRepaymentPeriods, 1, 6, mc);
|
||||
|
||||
// 17.13
|
||||
Assertions.assertEquals(BigDecimal.valueOf(17.13), result.getEqualMonthlyInstallmentValue().getAmount());
|
||||
@@ -330,15 +320,14 @@ class ProgressiveEMICalculatorTest {
|
||||
final BigDecimal principal = BigDecimal.valueOf(100);
|
||||
final Money outstandingBalance = Money.of(monetaryCurrency, principal);
|
||||
|
||||
Mockito.when(scheduleParams.getOutstandingBalanceAsPerRest()).thenReturn(outstandingBalance);
|
||||
Mockito.when(loanProductRelatedDetail.getNominalInterestRatePerPeriod()).thenReturn(interestRate);
|
||||
Mockito.when(loanProductRelatedDetail.getDaysInYearType()).thenReturn(DaysInYearType.DAYS_364.getValue());
|
||||
Mockito.when(loanProductRelatedDetail.getDaysInMonthType()).thenReturn(DaysInMonthType.INVALID.getValue());
|
||||
Mockito.when(loanProductRelatedDetail.getRepaymentPeriodFrequencyType()).thenReturn(PeriodFrequencyType.WEEKS);
|
||||
Mockito.when(loanProductRelatedDetail.getRepayEvery()).thenReturn(1);
|
||||
|
||||
final EMICalculationResult result = emiCalculator.calculateEMIValueAndRateFactors(loanApplicationTerms, scheduleParams,
|
||||
expectedRepaymentPeriods, mc);
|
||||
final EMICalculationResult result = emiCalculator.calculateEMIValueAndRateFactors(outstandingBalance, loanProductRelatedDetail,
|
||||
expectedRepaymentPeriods, 1, 6, mc);
|
||||
|
||||
// 16.77
|
||||
Assertions.assertEquals(BigDecimal.valueOf(16.77), result.getEqualMonthlyInstallmentValue().getAmount());
|
||||
@@ -374,15 +363,14 @@ class ProgressiveEMICalculatorTest {
|
||||
final BigDecimal principal = BigDecimal.valueOf(100);
|
||||
final Money outstandingBalance = Money.of(monetaryCurrency, principal);
|
||||
|
||||
Mockito.when(scheduleParams.getOutstandingBalanceAsPerRest()).thenReturn(outstandingBalance);
|
||||
Mockito.when(loanProductRelatedDetail.getNominalInterestRatePerPeriod()).thenReturn(interestRate);
|
||||
Mockito.when(loanProductRelatedDetail.getDaysInYearType()).thenReturn(DaysInYearType.DAYS_360.getValue());
|
||||
Mockito.when(loanProductRelatedDetail.getDaysInMonthType()).thenReturn(DaysInMonthType.INVALID.getValue());
|
||||
Mockito.when(loanProductRelatedDetail.getRepaymentPeriodFrequencyType()).thenReturn(PeriodFrequencyType.WEEKS);
|
||||
Mockito.when(loanProductRelatedDetail.getRepayEvery()).thenReturn(2);
|
||||
|
||||
final EMICalculationResult result = emiCalculator.calculateEMIValueAndRateFactors(loanApplicationTerms, scheduleParams,
|
||||
expectedRepaymentPeriods, mc);
|
||||
final EMICalculationResult result = emiCalculator.calculateEMIValueAndRateFactors(outstandingBalance, loanProductRelatedDetail,
|
||||
expectedRepaymentPeriods, 1, 6, mc);
|
||||
|
||||
// 16.88
|
||||
Assertions.assertEquals(new BigDecimal("16.88"), result.getEqualMonthlyInstallmentValue().getAmount());
|
||||
@@ -418,15 +406,14 @@ class ProgressiveEMICalculatorTest {
|
||||
final BigDecimal principal = BigDecimal.valueOf(100);
|
||||
final Money outstandingBalance = Money.of(monetaryCurrency, principal);
|
||||
|
||||
Mockito.when(scheduleParams.getOutstandingBalanceAsPerRest()).thenReturn(outstandingBalance);
|
||||
Mockito.when(loanProductRelatedDetail.getNominalInterestRatePerPeriod()).thenReturn(interestRate);
|
||||
Mockito.when(loanProductRelatedDetail.getDaysInYearType()).thenReturn(DaysInYearType.DAYS_360.getValue());
|
||||
Mockito.when(loanProductRelatedDetail.getDaysInMonthType()).thenReturn(DaysInMonthType.INVALID.getValue());
|
||||
Mockito.when(loanProductRelatedDetail.getRepaymentPeriodFrequencyType()).thenReturn(PeriodFrequencyType.DAYS);
|
||||
Mockito.when(loanProductRelatedDetail.getRepayEvery()).thenReturn(15);
|
||||
|
||||
final EMICalculationResult result = emiCalculator.calculateEMIValueAndRateFactors(loanApplicationTerms, scheduleParams,
|
||||
expectedRepaymentPeriods, mc);
|
||||
final EMICalculationResult result = emiCalculator.calculateEMIValueAndRateFactors(outstandingBalance, loanProductRelatedDetail,
|
||||
expectedRepaymentPeriods, 1, 6, mc);
|
||||
|
||||
// 16.90
|
||||
Assertions.assertEquals(new BigDecimal("16.90"), result.getEqualMonthlyInstallmentValue().getAmount());
|
||||
@@ -456,7 +443,6 @@ class ProgressiveEMICalculatorTest {
|
||||
final Money downPaymentValue = Money.of(monetaryCurrency, BigDecimal.valueOf(25));
|
||||
final Money outstandingBalance = principal.minus(downPaymentValue);
|
||||
|
||||
Mockito.when(scheduleParams.getOutstandingBalanceAsPerRest()).thenReturn(outstandingBalance);
|
||||
Mockito.when(loanProductRelatedDetail.getNominalInterestRatePerPeriod()).thenReturn(interestRate);
|
||||
Mockito.when(loanProductRelatedDetail.getDaysInYearType()).thenReturn(DaysInYearType.DAYS_360.getValue());
|
||||
Mockito.when(loanProductRelatedDetail.getDaysInMonthType()).thenReturn(DaysInMonthType.DAYS_30.getValue());
|
||||
@@ -474,8 +460,8 @@ class ProgressiveEMICalculatorTest {
|
||||
expectedRepaymentPeriods.add(new PreGeneratedLoanSchedulePeriod(5, LocalDate.of(2024, 4, 1), LocalDate.of(2024, 5, 1)));
|
||||
expectedRepaymentPeriods.add(new PreGeneratedLoanSchedulePeriod(6, LocalDate.of(2024, 5, 1), LocalDate.of(2024, 6, 1)));
|
||||
|
||||
final EMICalculationResult result = emiCalculator.calculateEMIValueAndRateFactors(loanApplicationTerms, scheduleParams,
|
||||
expectedRepaymentPeriods, mc);
|
||||
final EMICalculationResult result = emiCalculator.calculateEMIValueAndRateFactors(outstandingBalance, loanProductRelatedDetail,
|
||||
expectedRepaymentPeriods, 1, 5, mc);
|
||||
|
||||
// 15.36
|
||||
Assertions.assertEquals(new BigDecimal("15.36"), result.getEqualMonthlyInstallmentValue().getAmount());
|
||||
@@ -501,7 +487,6 @@ class ProgressiveEMICalculatorTest {
|
||||
final BigDecimal interestRate = BigDecimal.valueOf(0);
|
||||
final Money outstandingBalance = Money.of(monetaryCurrency, BigDecimal.valueOf(1000));
|
||||
|
||||
Mockito.when(scheduleParams.getOutstandingBalanceAsPerRest()).thenReturn(outstandingBalance);
|
||||
Mockito.when(loanProductRelatedDetail.getNominalInterestRatePerPeriod()).thenReturn(interestRate);
|
||||
Mockito.when(loanProductRelatedDetail.getDaysInYearType()).thenReturn(DaysInYearType.DAYS_360.getValue());
|
||||
Mockito.when(loanProductRelatedDetail.getDaysInMonthType()).thenReturn(DaysInMonthType.DAYS_30.getValue());
|
||||
@@ -514,8 +499,8 @@ class ProgressiveEMICalculatorTest {
|
||||
expectedRepaymentPeriods.add(new PreGeneratedLoanSchedulePeriod(3, LocalDate.of(2024, 3, 1), LocalDate.of(2024, 4, 1)));
|
||||
expectedRepaymentPeriods.add(new PreGeneratedLoanSchedulePeriod(4, LocalDate.of(2024, 4, 1), LocalDate.of(2024, 5, 1)));
|
||||
|
||||
final EMICalculationResult result = emiCalculator.calculateEMIValueAndRateFactors(loanApplicationTerms, scheduleParams,
|
||||
expectedRepaymentPeriods, mc);
|
||||
final EMICalculationResult result = emiCalculator.calculateEMIValueAndRateFactors(outstandingBalance, loanProductRelatedDetail,
|
||||
expectedRepaymentPeriods, 1, 4, mc);
|
||||
|
||||
// 250.00
|
||||
Assertions.assertEquals(new BigDecimal("250.00"), result.getEqualMonthlyInstallmentValue().getAmount());
|
||||
@@ -542,7 +527,6 @@ class ProgressiveEMICalculatorTest {
|
||||
final BigDecimal principal = BigDecimal.valueOf(100);
|
||||
final Money outstandingBalance = Money.of(monetaryCurrency, principal);
|
||||
|
||||
Mockito.when(scheduleParams.getOutstandingBalanceAsPerRest()).thenReturn(outstandingBalance);
|
||||
Mockito.when(loanProductRelatedDetail.getNominalInterestRatePerPeriod()).thenReturn(interestRate);
|
||||
Mockito.when(loanProductRelatedDetail.getDaysInYearType()).thenReturn(DaysInYearType.DAYS_360.getValue());
|
||||
Mockito.when(loanProductRelatedDetail.getDaysInMonthType()).thenReturn(DaysInMonthType.INVALID.getValue());
|
||||
@@ -552,7 +536,8 @@ class ProgressiveEMICalculatorTest {
|
||||
expectedRepaymentPeriods.add(new PreGeneratedLoanSchedulePeriod(1, LocalDate.of(2024, 1, 1), LocalDate.of(2024, 1, 16)));
|
||||
|
||||
try {
|
||||
emiCalculator.calculateEMIValueAndRateFactors(loanApplicationTerms, scheduleParams, expectedRepaymentPeriods, mc);
|
||||
final EMICalculationResult result = emiCalculator.calculateEMIValueAndRateFactors(outstandingBalance, loanProductRelatedDetail,
|
||||
expectedRepaymentPeriods, 1, 6, mc);
|
||||
Assertions.fail();
|
||||
} catch (Exception e) {
|
||||
Assertions.assertInstanceOf(UnsupportedOperationException.class, e);
|
||||
@@ -568,7 +553,6 @@ class ProgressiveEMICalculatorTest {
|
||||
final BigDecimal principal = BigDecimal.valueOf(100);
|
||||
final Money outstandingBalance = Money.of(monetaryCurrency, principal);
|
||||
|
||||
Mockito.when(scheduleParams.getOutstandingBalanceAsPerRest()).thenReturn(outstandingBalance);
|
||||
Mockito.when(loanProductRelatedDetail.getNominalInterestRatePerPeriod()).thenReturn(interestRate);
|
||||
Mockito.when(loanProductRelatedDetail.getDaysInYearType()).thenReturn(DaysInYearType.DAYS_360.getValue());
|
||||
Mockito.when(loanProductRelatedDetail.getDaysInMonthType()).thenReturn(DaysInMonthType.INVALID.getValue());
|
||||
@@ -578,7 +562,8 @@ class ProgressiveEMICalculatorTest {
|
||||
expectedRepaymentPeriods.add(new PreGeneratedLoanSchedulePeriod(1, LocalDate.of(2024, 1, 1), LocalDate.of(2024, 1, 16)));
|
||||
|
||||
try {
|
||||
emiCalculator.calculateEMIValueAndRateFactors(loanApplicationTerms, scheduleParams, expectedRepaymentPeriods, mc);
|
||||
final EMICalculationResult result = emiCalculator.calculateEMIValueAndRateFactors(outstandingBalance, loanProductRelatedDetail,
|
||||
expectedRepaymentPeriods, 1, 6, mc);
|
||||
Assertions.fail();
|
||||
} catch (Exception e) {
|
||||
Assertions.assertInstanceOf(UnsupportedOperationException.class, e);
|
||||
@@ -594,7 +579,6 @@ class ProgressiveEMICalculatorTest {
|
||||
final BigDecimal principal = BigDecimal.valueOf(100);
|
||||
final Money outstandingBalance = Money.of(monetaryCurrency, principal);
|
||||
|
||||
Mockito.when(scheduleParams.getOutstandingBalanceAsPerRest()).thenReturn(outstandingBalance);
|
||||
Mockito.when(loanProductRelatedDetail.getNominalInterestRatePerPeriod()).thenReturn(interestRate);
|
||||
Mockito.when(loanProductRelatedDetail.getDaysInYearType()).thenReturn(DaysInYearType.DAYS_360.getValue());
|
||||
Mockito.when(loanProductRelatedDetail.getDaysInMonthType()).thenReturn(DaysInMonthType.INVALID.getValue());
|
||||
@@ -604,7 +588,8 @@ class ProgressiveEMICalculatorTest {
|
||||
expectedRepaymentPeriods.add(new PreGeneratedLoanSchedulePeriod(1, LocalDate.of(2024, 1, 1), LocalDate.of(2024, 1, 16)));
|
||||
|
||||
try {
|
||||
emiCalculator.calculateEMIValueAndRateFactors(loanApplicationTerms, scheduleParams, expectedRepaymentPeriods, mc);
|
||||
final EMICalculationResult result = emiCalculator.calculateEMIValueAndRateFactors(outstandingBalance, loanProductRelatedDetail,
|
||||
expectedRepaymentPeriods, 1, 6, mc);
|
||||
Assertions.fail();
|
||||
} catch (Exception e) {
|
||||
Assertions.assertInstanceOf(UnsupportedOperationException.class, e);
|
||||
|
||||
Reference in New Issue
Block a user