This is an automated email from the ASF dual-hosted git repository.
adamsaghy pushed a commit to branch develop
in repository https://gitbox.apache.org/repos/asf/fineract.git
The following commit(s) were added to refs/heads/develop by this push:
new a4f077d67 FINERACT-1981: Provide more general EMI calculator interface
a4f077d67 is described below
commit a4f077d675920e7488128a8664d534a71843795d
Author: Janos Meszaros <[email protected]>
AuthorDate: Fri Jul 12 02:32:00 2024 +0200
FINERACT-1981: Provide more general EMI calculator interface
---
.../AbstractProgressiveLoanScheduleGenerator.java | 14 +--
.../domain/ProgressiveLoanScheduleGenerator.java | 32 ------
.../portfolio/loanproduct/calc/EMICalculator.java | 14 ++-
.../loanproduct/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 79b33799c..54761f4ea 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 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
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 a0556f396..4bafc7561 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 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;
- }
}
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 6f50b9ebf..c59e1ef07 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 @@ 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);
}
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 d7e830862..564cdfe2e 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.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;
+ }
}
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 0f7dcc663..aebfdcf88 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.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);