From a4f077d675920e7488128a8664d534a71843795d Mon Sep 17 00:00:00 2001 From: Janos Meszaros Date: Fri, 12 Jul 2024 02:32:00 +0200 Subject: [PATCH] FINERACT-1981: Provide more general EMI calculator interface --- ...tractProgressiveLoanScheduleGenerator.java | 14 +- .../ProgressiveLoanScheduleGenerator.java | 32 ----- .../loanproduct/calc/EMICalculator.java | 14 +- .../calc/ProgressiveEMICalculator.java | 122 +++++++----------- .../calc/ProgressiveEMICalculatorTest.java | 63 ++++----- 5 files changed, 89 insertions(+), 156 deletions(-) diff --git a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/AbstractProgressiveLoanScheduleGenerator.java b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/AbstractProgressiveLoanScheduleGenerator.java index 79b33799c89..54761f4ea06 100644 --- a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/AbstractProgressiveLoanScheduleGenerator.java +++ b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/AbstractProgressiveLoanScheduleGenerator.java @@ -119,13 +119,16 @@ public LoanScheduleModel generate(final MathContext mc, final LoanApplicationTer : scheduleParams.getPeriodStartDate(); List 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 LoanRepaymentScheduleInstallment calculatePrepaymentAmount(MonetaryCurren public abstract PaymentPeriodsInOneYearCalculator getPaymentPeriodsInOneYearCalculator(); - public abstract PrincipalInterest calculatePrincipalInterestComponentsForPeriod(LoanApplicationTerms loanApplicationTerms, - LoanScheduleParams loanScheduleParams, EMICalculationResult emiCalculationResult, MathContext mc); - protected abstract EMICalculator getEMICalculator(); // Private, internal methods diff --git a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/ProgressiveLoanScheduleGenerator.java b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/ProgressiveLoanScheduleGenerator.java index a0556f39616..4bafc7561a0 100644 --- a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/ProgressiveLoanScheduleGenerator.java +++ b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/ProgressiveLoanScheduleGenerator.java @@ -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 PaymentPeriodsInOneYearCalculator getPaymentPeriodsInOneYearCalculator() 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; - } } diff --git a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/calc/EMICalculator.java b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/calc/EMICalculator.java index 6f50b9ebffc..c59e1ef07ed 100644 --- a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/calc/EMICalculator.java +++ b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/calc/EMICalculator.java @@ -20,12 +20,18 @@ 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 expectedRepaymentPeriods, MathContext mc); + EMICalculationResult calculateEMIValueAndRateFactors(Money outstandingBalanceAsPerRest, + LoanProductRelatedDetail loanProductRelatedDetail, List expectedRepaymentPeriods, + Integer actualPeriodNumber, Integer numberOfRepayments, MathContext mc); + + PrincipalInterest calculatePrincipalInterestComponentsForPeriod(EMICalculationResult emiCalculationResult, + Money outstandingBalanceAsPerRest, Integer installmentAmountInMultiplesOf, Integer actualPeriodNumber, + Integer actualNoOfRepayments, MathContext mc); } diff --git a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/calc/ProgressiveEMICalculator.java b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/calc/ProgressiveEMICalculator.java index d7e83086215..564cdfe2ee1 100644 --- a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/calc/ProgressiveEMICalculator.java +++ b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/calc/ProgressiveEMICalculator.java @@ -23,15 +23,15 @@ 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 expectedRepaymentPeriods, - final MathContext mc) { - final LoanProductRelatedDetail loanProductRelatedDetail = loanApplicationTerms.toLoanProductRelatedDetail(); + public EMICalculationResult calculateEMIValueAndRateFactors(final Money outstandingBalanceAsPerRest, + final LoanProductRelatedDetail loanProductRelatedDetail, final List 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 EMICalculationResult calculateEMIValueAndRateFactors(final LoanApplicatio final List 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 @@ BigDecimal calculateRateFactorPerPeriodBasedOnRepaymentFrequency(final BigDecima /** * 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 rateFactorList, final Money outstandingBalanceForRest, final MathContext mc) { + EMICalculationResult calculateEMI(final List 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 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 @@ EMICalculationResult calculateEMI(final LoanApplicationTerms loanApplicationTerm * 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 rateFactorMinus1List, + Money adjustEMIForMoreStreamlinedRepaymentSchedule(final Integer actualPeriodNumber, final Integer numberOfRepayments, + final Money outstandingBalanceAsPerRest, final Money equalMonthlyInstallmentValue, List 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 @@ Money adjustEMIForMoreStreamlinedRepaymentSchedule(final LoanApplicationTerms lo 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 @@ Money adjustEMIForMoreStreamlinedRepaymentSchedule(final LoanApplicationTerms lo } } - RepaymentScheduleModel generateRepaymentScheduleModel(LoanApplicationTerms loanApplicationTerms, LoanScheduleParams loanScheduleParams, + RepaymentScheduleModel generateRepaymentScheduleModel(final Integer numberOfRepayments, final Money outstandingBalanceAsPerRest, Money equalMonthlyInstallmentValue, List 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 @@ RepaymentScheduleModel generateRepaymentScheduleModel(LoanApplicationTerms loanA /** * Rate factor -1 values * - * @param rateFactors - * @param mc - * @return */ List getRateFactorMinus1List(final List rateFactors, final MathContext mc) { return rateFactors.stream().map(it -> it.subtract(BigDecimal.ONE, mc)).toList(); @@ -312,10 +280,6 @@ List getRateFactorMinus1List(final List rateFactors, fin /** * Calculate Rate Factor Product from rate factors - * - * @param rateFactors - * @param mc - * @return */ BigDecimal calculateRateFactorN(final List rateFactors, final MathContext mc) { return rateFactors.stream().reduce(BigDecimal.ONE, (BigDecimal acc, BigDecimal value) -> acc.multiply(value, mc)); @@ -323,10 +287,6 @@ BigDecimal calculateRateFactorN(final List rateFactors, final MathCo /** * Summarize Fn values - * - * @param rateFactors - * @param mc - * @return */ BigDecimal calculateFnResult(final List rateFactors, final MathContext mc) { return rateFactors.stream().skip(1).reduce(BigDecimal.ONE, @@ -335,12 +295,6 @@ BigDecimal calculateFnResult(final List rateFactors, final MathConte /** * 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 @@ BigDecimal rateFactorByRepaymentPeriod(final BigDecimal interestRate, final BigD /** * 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 @@ BigDecimal rateFactorByRepaymentPartialPeriod(final BigDecimal interestRate, fin 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; + } } diff --git a/fineract-progressive-loan/src/test/java/org/apache/fineract/portfolio/loanproduct/calc/ProgressiveEMICalculatorTest.java b/fineract-progressive-loan/src/test/java/org/apache/fineract/portfolio/loanproduct/calc/ProgressiveEMICalculatorTest.java index 0f7dcc66367..aebfdcf88b8 100644 --- a/fineract-progressive-loan/src/test/java/org/apache/fineract/portfolio/loanproduct/calc/ProgressiveEMICalculatorTest.java +++ b/fineract-progressive-loan/src/test/java/org/apache/fineract/portfolio/loanproduct/calc/ProgressiveEMICalculatorTest.java @@ -34,8 +34,6 @@ 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 = 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 @@ public static void init() { // 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 @@ public void testEMICalculation_principal100_dayInYearsActual_daysInMonthActual_r 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 @@ public void testEMICalculation_principal10000_dayInYearsActual_daysInMonthActual 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 @@ public void testEMICalculation_principal100_dayInYears365_daysInMonthActual_repa 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 @@ public void testEMICalculation_principal100_dayInYears360_daysInMonth30_repayEve 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 @@ public void testEMICalculation_principal100_dayInYears364_daysInMonthDoesntMatte 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 @@ public void testEMICalculation_principal100_dayInYears360_daysInMonthDoesntMatte 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 @@ public void testEMICalculation_principal100_dayInYears360_daysInMonthDoesntMatte 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 @@ public void testEMICalculation_Disbursement_DownPayment_Principal100_dayInYears3 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 @@ public void testEMICalculation_Disbursement_DownPayment_Principal100_dayInYears3 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 @@ public void testEMICalculation_Principal1000_NoInterest_repayEvery1Month() { 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 @@ public void testEMICalculation_Principal1000_NoInterest_repayEvery1Month() { 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 @@ public void testUnsupportedRepaymentEveryYear() { 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 @@ public void testUnsupportedRepaymentEveryYear() { 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 @@ public void testUnsupportedRepaymentEveryWholeTerm() { 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 @@ public void testUnsupportedRepaymentEveryWholeTerm() { 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 @@ public void testInvalidRepaymentEveryValue() { 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 @@ public void testInvalidRepaymentEveryValue() { 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);