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);


Reply via email to