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 67440c47a FINERACT-1981: Adjust EMI if needed
67440c47a is described below

commit 67440c47abd08438851dae58bdcd13983825c0e3
Author: Adam Saghy <adamsa...@gmail.com>
AuthorDate: Fri Jul 5 00:01:15 2024 +0200

    FINERACT-1981: Adjust EMI if needed
---
 .../AbstractProgressiveLoanScheduleGenerator.java  |   4 +-
 .../domain/ProgressiveLoanScheduleGenerator.java   |  11 +-
 .../loanproduct/calc/EMICalculationResult.java     |   3 +-
 .../portfolio/loanproduct/calc/EMICalculator.java  |   7 +-
 .../loanproduct/calc/ProgressiveEMICalculator.java | 115 ++++++++++--
 ...MICalculator.java => RepaymentPeriodModel.java} |  12 +-
 ...Calculator.java => RepaymentScheduleModel.java} |  17 +-
 .../calc/ProgressiveEMICalculatorTest.java         |  50 +++---
 ...PaymentAllocationLoanRepaymentScheduleTest.java | 197 ++++++++++++++++++++-
 9 files changed, 344 insertions(+), 72 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 c98935cbc..79b33799c 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,8 +119,8 @@ public abstract class 
AbstractProgressiveLoanScheduleGenerator implements LoanSc
                         : scheduleParams.getPeriodStartDate();
                 List<PreGeneratedLoanSchedulePeriod> expectedRepaymentPeriods 
= getScheduledDateGenerator()
                         .generateRepaymentPeriods(startDate, 
loanApplicationTerms, holidayDetailDTO);
-                emiCalculationResult = 
getEMICalculator().calculateEMIValueAndRateFactors(scheduleParams,
-                        loanApplicationTerms.toLoanProductRelatedDetail(), 
expectedRepaymentPeriods, mc);
+                emiCalculationResult = 
getEMICalculator().calculateEMIValueAndRateFactors(loanApplicationTerms, 
scheduleParams,
+                        expectedRepaymentPeriods, mc);
             }
 
             // 5 determine principal,interest of repayment period
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 3f51b727d..a0556f396 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
@@ -55,12 +55,11 @@ public class ProgressiveLoanScheduleGenerator extends 
AbstractProgressiveLoanSch
             final LoanScheduleParams loanScheduleParams, final 
EMICalculationResult emiCalculationResult, final MathContext mc) {
 
         final Money equalMonthlyInstallmentValue = 
loanApplicationTerms.getInstallmentAmountInMultiplesOf() != null
-                ? Money.of(loanApplicationTerms.getCurrency(),
-                        
Money.roundToMultiplesOf(emiCalculationResult.getEqualMonthlyInstallmentValue(),
-                                
loanApplicationTerms.getInstallmentAmountInMultiplesOf()))
-                : Money.of(loanApplicationTerms.getCurrency(), 
emiCalculationResult.getEqualMonthlyInstallmentValue());
+                ? 
Money.roundToMultiplesOf(emiCalculationResult.getEqualMonthlyInstallmentValue(),
+                        
loanApplicationTerms.getInstallmentAmountInMultiplesOf())
+                : emiCalculationResult.getEqualMonthlyInstallmentValue();
         final BigDecimal rateFactorMinus1 = 
emiCalculationResult.getNextRepaymentPeriodRateFactorMinus1();
-        final Money calculatedInterest = 
loanScheduleParams.getOutstandingBalance().multipliedBy(rateFactorMinus1);
+        final Money calculatedInterest = 
loanScheduleParams.getOutstandingBalanceAsPerRest().multipliedBy(rateFactorMinus1);
         final Money calculatedPrincipal = 
equalMonthlyInstallmentValue.minus(calculatedInterest);
 
         return new PrincipalInterest(
@@ -72,7 +71,7 @@ public class ProgressiveLoanScheduleGenerator extends 
AbstractProgressiveLoanSch
             final LoanApplicationTerms loanApplicationTerms, final 
LoanScheduleParams loanScheduleParams) {
         final boolean isLastRepaymentPeriod = 
loanScheduleParams.getPeriodNumber() == 
loanApplicationTerms.getActualNoOfRepaymnets();
         if (isLastRepaymentPeriod) {
-            final Money remainingAmount = 
loanScheduleParams.getOutstandingBalance().minus(calculatedPrincipal);
+            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/EMICalculationResult.java
 
b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/calc/EMICalculationResult.java
index 71b9435c1..1990f6355 100644
--- 
a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/calc/EMICalculationResult.java
+++ 
b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/calc/EMICalculationResult.java
@@ -23,12 +23,13 @@ import java.util.List;
 import lombok.AccessLevel;
 import lombok.Getter;
 import lombok.RequiredArgsConstructor;
+import org.apache.fineract.organisation.monetary.domain.Money;
 
 @RequiredArgsConstructor(access = AccessLevel.PACKAGE)
 public class EMICalculationResult {
 
     @Getter
-    private final BigDecimal equalMonthlyInstallmentValue;
+    private final Money equalMonthlyInstallmentValue;
     private final List<BigDecimal> repaymentPeriodRateFactorMinus1List;
 
     private int counter = 0;
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 bf217b73e..6f50b9ebf 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
@@ -21,12 +21,11 @@ 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.portfolio.loanaccount.loanschedule.domain.LoanScheduleModelPeriod;
-import 
org.apache.fineract.portfolio.loanproduct.domain.LoanProductRelatedDetail;
 
 public interface EMICalculator {
 
-    EMICalculationResult calculateEMIValueAndRateFactors(LoanScheduleParams 
scheduleParams,
-            LoanProductRelatedDetail loanProductRelatedDetail, List<? extends 
LoanScheduleModelPeriod> expectedRepaymentPeriods,
-            MathContext mc);
+    EMICalculationResult calculateEMIValueAndRateFactors(LoanApplicationTerms 
loanApplicationTerms, LoanScheduleParams scheduleParams,
+            List<? extends LoanScheduleModelPeriod> expectedRepaymentPeriods, 
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 f0812a6ed..d7e830862 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
@@ -25,10 +25,12 @@ import java.time.Year;
 import java.util.List;
 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.loanproduct.domain.LoanProductRelatedDetail;
 import org.springframework.stereotype.Component;
@@ -42,25 +44,28 @@ 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 loanProductRelatedDetail
-     *            Loan Product Related Detail from LoanTermApplication
-     *
      * @param expectedRepaymentPeriods
      *            Expected Repayment Periods
      *
      * @param mc
-     * @return
+     *            MathContext for rounding
+     *
+     * @return EMICalculationResult Contains rate factor for each period and 
calculated EMI
      */
     @Override
-    public EMICalculationResult calculateEMIValueAndRateFactors(final 
LoanScheduleParams scheduleParams,
-            final LoanProductRelatedDetail loanProductRelatedDetail, final 
List<? extends LoanScheduleModelPeriod> expectedRepaymentPeriods,
+    public EMICalculationResult calculateEMIValueAndRateFactors(final 
LoanApplicationTerms loanApplicationTerms,
+            final LoanScheduleParams scheduleParams, final List<? extends 
LoanScheduleModelPeriod> expectedRepaymentPeriods,
             final MathContext mc) {
+        final LoanProductRelatedDetail loanProductRelatedDetail = 
loanApplicationTerms.toLoanProductRelatedDetail();
         final BigDecimal nominalInterestRatePerPeriod = 
calcNominalInterestRatePerPeriod(
                 loanProductRelatedDetail.getNominalInterestRatePerPeriod(), 
mc);
-        final BigDecimal outstandingBalance = 
scheduleParams.getOutstandingBalanceAsPerRest().getAmount();
+        final Money outstandingBalance = 
scheduleParams.getOutstandingBalanceAsPerRest();
         final DaysInYearType daysInYearType = 
DaysInYearType.fromInt(loanProductRelatedDetail.getDaysInYearType());
         final DaysInMonthType daysInMonthType = 
DaysInMonthType.fromInt(loanProductRelatedDetail.getDaysInMonthType());
         final PeriodFrequencyType repaymentFrequency = 
loanProductRelatedDetail.getRepaymentPeriodFrequencyType();
@@ -69,7 +74,7 @@ public final class ProgressiveEMICalculator implements 
EMICalculator {
         final List<BigDecimal> rateFactorList = 
getRateFactorList(expectedRepaymentPeriods, nominalInterestRatePerPeriod, 
daysInYearType,
                 daysInMonthType, repaymentFrequency, repaymentEvery, mc);
 
-        return calculateEMI(rateFactorList, outstandingBalance, mc);
+        return calculateEMI(loanApplicationTerms, scheduleParams, 
rateFactorList, outstandingBalance, mc);
     }
 
     /**
@@ -200,18 +205,98 @@ public final class ProgressiveEMICalculator implements 
EMICalculator {
      * Calculate EMI parts and return an EMI calculation result object with 
repayment installment rate factors
      *
      * @param rateFactorList
-     * @param principal
+     * @param outstandingBalanceForRest
      * @param mc
      * @return
      */
-    EMICalculationResult calculateEMI(final List<BigDecimal> rateFactorList, 
final BigDecimal principal, final MathContext mc) {
+    EMICalculationResult calculateEMI(final LoanApplicationTerms 
loanApplicationTerms, final LoanScheduleParams loanScheduleParams,
+            final List<BigDecimal> rateFactorList, final Money 
outstandingBalanceForRest, final MathContext mc) {
         final BigDecimal rateFactorN = 
MathUtil.stripTrailingZeros(calculateRateFactorN(rateFactorList, mc));
         final BigDecimal fnResult = 
MathUtil.stripTrailingZeros(calculateFnResult(rateFactorList, mc));
 
-        final BigDecimal emiValue = 
MathUtil.stripTrailingZeros(calculateEMIValue(rateFactorN, principal, fnResult, 
mc));
+        final Money emiValue = Money.of(loanApplicationTerms.getCurrency(),
+                calculateEMIValue(rateFactorN, 
outstandingBalanceForRest.getAmount(), fnResult, mc));
         final List<BigDecimal> rateFactorMinus1List = 
getRateFactorMinus1List(rateFactorList, mc);
 
-        return new EMICalculationResult(emiValue, rateFactorMinus1List);
+        final Money adjustedEqualMonthlyInstallmentValue = 
adjustEMIForMoreStreamlinedRepaymentSchedule(loanApplicationTerms,
+                loanScheduleParams, emiValue, rateFactorMinus1List, mc);
+
+        return new EMICalculationResult(adjustedEqualMonthlyInstallmentValue, 
rateFactorMinus1List);
+    }
+
+    /**
+     * 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,
+            final MathContext mc) {
+        int numberOfUpcomingPeriods = 
loanApplicationTerms.getNumberOfRepayments() - 
loanScheduleParams.getPeriodNumber() + 1;
+        if (numberOfUpcomingPeriods < 2) {
+            return equalMonthlyInstallmentValue;
+        }
+
+        RepaymentScheduleModel repaymentScheduleModel = 
generateRepaymentScheduleModel(loanApplicationTerms, loanScheduleParams,
+                equalMonthlyInstallmentValue, rateFactorMinus1List);
+        Money calculatedLastEMI = 
repaymentScheduleModel.getScheduleList().get(repaymentScheduleModel.getScheduleList().size()
 - 1).emi();
+        Money originalDifference = 
calculatedLastEMI.minus(equalMonthlyInstallmentValue);
+        if (originalDifference.isZero()) {
+            return equalMonthlyInstallmentValue;
+        }
+        double lowerHalfOfUpcomingPeriods = Math.floor((double) 
numberOfUpcomingPeriods / 2);
+        if (lowerHalfOfUpcomingPeriods == 0.0) {
+            return equalMonthlyInstallmentValue;
+        }
+        boolean shouldBeAdjusted = originalDifference.abs().multipliedBy(100)
+                
.isGreaterThan(Money.of(equalMonthlyInstallmentValue.getCurrency(), 
BigDecimal.valueOf(lowerHalfOfUpcomingPeriods)));
+        // Reiterate only when needed
+        if (shouldBeAdjusted) {
+            Money adjustment = 
originalDifference.dividedBy(numberOfUpcomingPeriods, mc.getRoundingMode());
+
+            Money adjustedEqualMonthlyInstallmentValue = 
equalMonthlyInstallmentValue.plus(adjustment);
+            RepaymentScheduleModel repaymentScheduleModelWithAdjustedEMI = 
generateRepaymentScheduleModel(loanApplicationTerms,
+                    loanScheduleParams, adjustedEqualMonthlyInstallmentValue, 
rateFactorMinus1List);
+            Money calculatedLastEMIAfterAdjustment = 
repaymentScheduleModelWithAdjustedEMI.getScheduleList()
+                    
.get(repaymentScheduleModelWithAdjustedEMI.getScheduleList().size() - 1).emi();
+            Money differenceAfterEMIAdjustment = 
calculatedLastEMIAfterAdjustment.minus(adjustedEqualMonthlyInstallmentValue);
+            // Use the adjusted EMI only if it is better than the original one
+            return 
differenceAfterEMIAdjustment.abs().isLessThan(originalDifference.abs()) ? 
adjustedEqualMonthlyInstallmentValue
+                    : equalMonthlyInstallmentValue;
+        } else {
+            return equalMonthlyInstallmentValue;
+        }
+    }
+
+    RepaymentScheduleModel generateRepaymentScheduleModel(LoanApplicationTerms 
loanApplicationTerms, LoanScheduleParams loanScheduleParams,
+            Money equalMonthlyInstallmentValue, List<BigDecimal> 
rateFactorMinus1List) {
+        RepaymentScheduleModel repaymentScheduleModel = new 
RepaymentScheduleModel();
+        Money balanceOfLoan = 
loanScheduleParams.getOutstandingBalanceAsPerRest();
+        for (int i = 0; i < loanApplicationTerms.getNumberOfRepayments(); 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) {
+                equalMonthlyInstallmentValue = 
balanceOfLoan.plus(calculatedInterest);
+            }
+            final Money calculatedPrincipal = 
equalMonthlyInstallmentValue.minus(calculatedInterest);
+            repaymentScheduleModel.addRepaymentPeriodModel(
+                    new RepaymentPeriodModel(balanceOfLoan, 
equalMonthlyInstallmentValue, calculatedInterest, calculatedPrincipal));
+            balanceOfLoan = balanceOfLoan.minus(calculatedPrincipal);
+            // We can stop processing if there is no outstanding principal
+            if (balanceOfLoan.isZero()) {
+                break;
+            }
+        }
+        return repaymentScheduleModel;
     }
 
     /**
@@ -252,14 +337,14 @@ public final class ProgressiveEMICalculator implements 
EMICalculator {
      * Calculate the EMI (Equal Monthly Installment) value
      *
      * @param rateFactorN
-     * @param principal
+     * @param outstandingBalanceForRest
      * @param fnResult
      * @param mc
      * @return
      */
-    BigDecimal calculateEMIValue(final BigDecimal rateFactorN, final 
BigDecimal principal, final BigDecimal fnResult,
+    BigDecimal calculateEMIValue(final BigDecimal rateFactorN, final 
BigDecimal outstandingBalanceForRest, final BigDecimal fnResult,
             final MathContext mc) {
-        return rateFactorN.multiply(principal, mc).divide(fnResult, mc);
+        return rateFactorN.multiply(outstandingBalanceForRest, 
mc).divide(fnResult, mc);
     }
 
     /**
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/RepaymentPeriodModel.java
similarity index 58%
copy from 
fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/calc/EMICalculator.java
copy to 
fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/calc/RepaymentPeriodModel.java
index bf217b73e..de99d69c0 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/RepaymentPeriodModel.java
@@ -18,15 +18,7 @@
  */
 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.LoanScheduleModelPeriod;
-import 
org.apache.fineract.portfolio.loanproduct.domain.LoanProductRelatedDetail;
+import org.apache.fineract.organisation.monetary.domain.Money;
 
-public interface EMICalculator {
-
-    EMICalculationResult calculateEMIValueAndRateFactors(LoanScheduleParams 
scheduleParams,
-            LoanProductRelatedDetail loanProductRelatedDetail, List<? extends 
LoanScheduleModelPeriod> expectedRepaymentPeriods,
-            MathContext mc);
+public record RepaymentPeriodModel(Money balanceOfLoan, Money emi, Money 
principal, Money interest) {
 }
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/RepaymentScheduleModel.java
similarity index 60%
copy from 
fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/calc/EMICalculator.java
copy to 
fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/calc/RepaymentScheduleModel.java
index bf217b73e..14d224921 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/RepaymentScheduleModel.java
@@ -18,15 +18,16 @@
  */
 package org.apache.fineract.portfolio.loanproduct.calc;
 
-import java.math.MathContext;
+import java.util.ArrayList;
 import java.util.List;
-import 
org.apache.fineract.portfolio.loanaccount.loanschedule.data.LoanScheduleParams;
-import 
org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleModelPeriod;
-import 
org.apache.fineract.portfolio.loanproduct.domain.LoanProductRelatedDetail;
+import lombok.Getter;
 
-public interface EMICalculator {
+@Getter
+public class RepaymentScheduleModel {
 
-    EMICalculationResult calculateEMIValueAndRateFactors(LoanScheduleParams 
scheduleParams,
-            LoanProductRelatedDetail loanProductRelatedDetail, List<? extends 
LoanScheduleModelPeriod> expectedRepaymentPeriods,
-            MathContext mc);
+    List<RepaymentPeriodModel> scheduleList = new ArrayList<>();
+
+    public void addRepaymentPeriodModel(final RepaymentPeriodModel 
repaymentPeriodModel) {
+        scheduleList.add(repaymentPeriodModel);
+    }
 }
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 94cb18a26..0f7dcc663 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
@@ -35,6 +35,7 @@ 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,6 +56,7 @@ class ProgressiveEMICalculatorTest {
 
     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
@@ -77,6 +79,8 @@ 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,
@@ -166,11 +170,11 @@ class ProgressiveEMICalculatorTest {
         
Mockito.when(loanProductRelatedDetail.getRepaymentPeriodFrequencyType()).thenReturn(PeriodFrequencyType.MONTHS);
         Mockito.when(loanProductRelatedDetail.getRepayEvery()).thenReturn(1);
 
-        final EMICalculationResult result = 
emiCalculator.calculateEMIValueAndRateFactors(scheduleParams, 
loanProductRelatedDetail,
+        final EMICalculationResult result = 
emiCalculator.calculateEMIValueAndRateFactors(loanApplicationTerms, 
scheduleParams,
                 expectedRepaymentPeriods, mc);
 
         // 17.13
-        Assertions.assertEquals(BigDecimal.valueOf(17.1280789505), 
result.getEqualMonthlyInstallmentValue());
+        Assertions.assertEquals(BigDecimal.valueOf(17.13), 
result.getEqualMonthlyInstallmentValue().getAmount());
 
         Assertions.assertEquals(BigDecimal.valueOf(0.00803137158), 
result.getNextRepaymentPeriodRateFactorMinus1());
         Assertions.assertEquals(BigDecimal.valueOf(0.00751321858), 
result.getNextRepaymentPeriodRateFactorMinus1());
@@ -208,11 +212,11 @@ class ProgressiveEMICalculatorTest {
         
Mockito.when(loanProductRelatedDetail.getRepaymentPeriodFrequencyType()).thenReturn(PeriodFrequencyType.MONTHS);
         Mockito.when(loanProductRelatedDetail.getRepayEvery()).thenReturn(1);
 
-        final EMICalculationResult result = 
emiCalculator.calculateEMIValueAndRateFactors(scheduleParams, 
loanProductRelatedDetail,
+        final EMICalculationResult result = 
emiCalculator.calculateEMIValueAndRateFactors(loanApplicationTerms, 
scheduleParams,
                 expectedRepaymentPeriods, mc);
 
         // 1713.12
-        Assertions.assertEquals(BigDecimal.valueOf(1713.12436983), 
result.getEqualMonthlyInstallmentValue());
+        Assertions.assertEquals(BigDecimal.valueOf(1713.12), 
result.getEqualMonthlyInstallmentValue().getAmount());
 
         Assertions.assertEquals(BigDecimal.valueOf(0.00804485776), 
result.getNextRepaymentPeriodRateFactorMinus1());
         Assertions.assertEquals(BigDecimal.valueOf(0.00803137158), 
result.getNextRepaymentPeriodRateFactorMinus1());
@@ -247,11 +251,11 @@ class ProgressiveEMICalculatorTest {
         
Mockito.when(loanProductRelatedDetail.getRepaymentPeriodFrequencyType()).thenReturn(PeriodFrequencyType.MONTHS);
         Mockito.when(loanProductRelatedDetail.getRepayEvery()).thenReturn(1);
 
-        final EMICalculationResult result = 
emiCalculator.calculateEMIValueAndRateFactors(scheduleParams, 
loanProductRelatedDetail,
+        final EMICalculationResult result = 
emiCalculator.calculateEMIValueAndRateFactors(loanApplicationTerms, 
scheduleParams,
                 expectedRepaymentPeriods, mc);
 
         // 17.13
-        Assertions.assertEquals(BigDecimal.valueOf(17.1293512777), 
result.getEqualMonthlyInstallmentValue());
+        Assertions.assertEquals(BigDecimal.valueOf(17.13), 
result.getEqualMonthlyInstallmentValue().getAmount());
 
         Assertions.assertEquals(BigDecimal.valueOf(0.00805337534), 
result.getNextRepaymentPeriodRateFactorMinus1());
         Assertions.assertEquals(BigDecimal.valueOf(0.00753380274), 
result.getNextRepaymentPeriodRateFactorMinus1());
@@ -290,11 +294,11 @@ class ProgressiveEMICalculatorTest {
         
Mockito.when(loanProductRelatedDetail.getRepaymentPeriodFrequencyType()).thenReturn(PeriodFrequencyType.MONTHS);
         Mockito.when(loanProductRelatedDetail.getRepayEvery()).thenReturn(1);
 
-        final EMICalculationResult result = 
emiCalculator.calculateEMIValueAndRateFactors(scheduleParams, 
loanProductRelatedDetail,
+        final EMICalculationResult result = 
emiCalculator.calculateEMIValueAndRateFactors(loanApplicationTerms, 
scheduleParams,
                 expectedRepaymentPeriods, mc);
 
         // 17.13
-        Assertions.assertEquals(BigDecimal.valueOf(17.1306301273), 
result.getEqualMonthlyInstallmentValue());
+        Assertions.assertEquals(BigDecimal.valueOf(17.13), 
result.getEqualMonthlyInstallmentValue().getAmount());
 
         Assertions.assertEquals(BigDecimal.valueOf(0.00790183333), 
result.getNextRepaymentPeriodRateFactorMinus1());
         Assertions.assertEquals(BigDecimal.valueOf(0.00790183333), 
result.getNextRepaymentPeriodRateFactorMinus1());
@@ -333,11 +337,11 @@ class ProgressiveEMICalculatorTest {
         
Mockito.when(loanProductRelatedDetail.getRepaymentPeriodFrequencyType()).thenReturn(PeriodFrequencyType.WEEKS);
         Mockito.when(loanProductRelatedDetail.getRepayEvery()).thenReturn(1);
 
-        final EMICalculationResult result = 
emiCalculator.calculateEMIValueAndRateFactors(scheduleParams, 
loanProductRelatedDetail,
+        final EMICalculationResult result = 
emiCalculator.calculateEMIValueAndRateFactors(loanApplicationTerms, 
scheduleParams,
                 expectedRepaymentPeriods, mc);
 
         // 16.77
-        Assertions.assertEquals(BigDecimal.valueOf(16.7731989919), 
result.getEqualMonthlyInstallmentValue());
+        Assertions.assertEquals(BigDecimal.valueOf(16.77), 
result.getEqualMonthlyInstallmentValue().getAmount());
 
         final BigDecimal fixValue = new BigDecimal("0.0018235");
         Assertions.assertEquals(fixValue, 
result.getNextRepaymentPeriodRateFactorMinus1());
@@ -377,11 +381,11 @@ class ProgressiveEMICalculatorTest {
         
Mockito.when(loanProductRelatedDetail.getRepaymentPeriodFrequencyType()).thenReturn(PeriodFrequencyType.WEEKS);
         Mockito.when(loanProductRelatedDetail.getRepayEvery()).thenReturn(2);
 
-        final EMICalculationResult result = 
emiCalculator.calculateEMIValueAndRateFactors(scheduleParams, 
loanProductRelatedDetail,
+        final EMICalculationResult result = 
emiCalculator.calculateEMIValueAndRateFactors(loanApplicationTerms, 
scheduleParams,
                 expectedRepaymentPeriods, mc);
 
         // 16.88
-        Assertions.assertEquals(new BigDecimal("16.8824319133"), 
result.getEqualMonthlyInstallmentValue());
+        Assertions.assertEquals(new BigDecimal("16.88"), 
result.getEqualMonthlyInstallmentValue().getAmount());
 
         final BigDecimal fixValue = new BigDecimal("0.00368752222");
         Assertions.assertEquals(fixValue, 
result.getNextRepaymentPeriodRateFactorMinus1());
@@ -421,11 +425,11 @@ class ProgressiveEMICalculatorTest {
         
Mockito.when(loanProductRelatedDetail.getRepaymentPeriodFrequencyType()).thenReturn(PeriodFrequencyType.DAYS);
         Mockito.when(loanProductRelatedDetail.getRepayEvery()).thenReturn(15);
 
-        final EMICalculationResult result = 
emiCalculator.calculateEMIValueAndRateFactors(scheduleParams, 
loanProductRelatedDetail,
+        final EMICalculationResult result = 
emiCalculator.calculateEMIValueAndRateFactors(loanApplicationTerms, 
scheduleParams,
                 expectedRepaymentPeriods, mc);
 
         // 16.90
-        Assertions.assertEquals(new BigDecimal("16.8978941103"), 
result.getEqualMonthlyInstallmentValue());
+        Assertions.assertEquals(new BigDecimal("16.90"), 
result.getEqualMonthlyInstallmentValue().getAmount());
 
         final BigDecimal fixValue = new BigDecimal("0.00395091667");
         Assertions.assertEquals(fixValue, 
result.getNextRepaymentPeriodRateFactorMinus1());
@@ -470,11 +474,11 @@ 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(scheduleParams, 
loanProductRelatedDetail,
+        final EMICalculationResult result = 
emiCalculator.calculateEMIValueAndRateFactors(loanApplicationTerms, 
scheduleParams,
                 expectedRepaymentPeriods, mc);
 
-        // 17.13
-        Assertions.assertEquals(new BigDecimal("15.3574482569"), 
result.getEqualMonthlyInstallmentValue());
+        // 15.36
+        Assertions.assertEquals(new BigDecimal("15.36"), 
result.getEqualMonthlyInstallmentValue().getAmount());
 
         Assertions.assertEquals(BigDecimal.valueOf(0.00790183333), 
result.getNextRepaymentPeriodRateFactorMinus1());
         Assertions.assertEquals(BigDecimal.valueOf(0.00790183333), 
result.getNextRepaymentPeriodRateFactorMinus1());
@@ -510,11 +514,11 @@ 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(scheduleParams, 
loanProductRelatedDetail,
+        final EMICalculationResult result = 
emiCalculator.calculateEMIValueAndRateFactors(loanApplicationTerms, 
scheduleParams,
                 expectedRepaymentPeriods, mc);
 
-        // 17.13
-        Assertions.assertEquals(new BigDecimal("250"), 
result.getEqualMonthlyInstallmentValue());
+        // 250.00
+        Assertions.assertEquals(new BigDecimal("250.00"), 
result.getEqualMonthlyInstallmentValue().getAmount());
 
         Assertions.assertEquals(BigDecimal.ZERO, 
result.getNextRepaymentPeriodRateFactorMinus1());
         Assertions.assertEquals(BigDecimal.ZERO, 
result.getNextRepaymentPeriodRateFactorMinus1());
@@ -548,7 +552,7 @@ class ProgressiveEMICalculatorTest {
         expectedRepaymentPeriods.add(new PreGeneratedLoanSchedulePeriod(1, 
LocalDate.of(2024, 1, 1), LocalDate.of(2024, 1, 16)));
 
         try {
-            emiCalculator.calculateEMIValueAndRateFactors(scheduleParams, 
loanProductRelatedDetail, expectedRepaymentPeriods, mc);
+            
emiCalculator.calculateEMIValueAndRateFactors(loanApplicationTerms, 
scheduleParams, expectedRepaymentPeriods, mc);
             Assertions.fail();
         } catch (Exception e) {
             Assertions.assertInstanceOf(UnsupportedOperationException.class, 
e);
@@ -574,7 +578,7 @@ class ProgressiveEMICalculatorTest {
         expectedRepaymentPeriods.add(new PreGeneratedLoanSchedulePeriod(1, 
LocalDate.of(2024, 1, 1), LocalDate.of(2024, 1, 16)));
 
         try {
-            emiCalculator.calculateEMIValueAndRateFactors(scheduleParams, 
loanProductRelatedDetail, expectedRepaymentPeriods, mc);
+            
emiCalculator.calculateEMIValueAndRateFactors(loanApplicationTerms, 
scheduleParams, expectedRepaymentPeriods, mc);
             Assertions.fail();
         } catch (Exception e) {
             Assertions.assertInstanceOf(UnsupportedOperationException.class, 
e);
@@ -600,7 +604,7 @@ class ProgressiveEMICalculatorTest {
         expectedRepaymentPeriods.add(new PreGeneratedLoanSchedulePeriod(1, 
LocalDate.of(2024, 1, 1), LocalDate.of(2024, 1, 16)));
 
         try {
-            emiCalculator.calculateEMIValueAndRateFactors(scheduleParams, 
loanProductRelatedDetail, expectedRepaymentPeriods, mc);
+            
emiCalculator.calculateEMIValueAndRateFactors(loanApplicationTerms, 
scheduleParams, expectedRepaymentPeriods, mc);
             Assertions.fail();
         } catch (Exception e) {
             Assertions.assertInstanceOf(UnsupportedOperationException.class, 
e);
diff --git 
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/AdvancedPaymentAllocationLoanRepaymentScheduleTest.java
 
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/AdvancedPaymentAllocationLoanRepaymentScheduleTest.java
index a85911696..5d08ad104 100644
--- 
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/AdvancedPaymentAllocationLoanRepaymentScheduleTest.java
+++ 
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/AdvancedPaymentAllocationLoanRepaymentScheduleTest.java
@@ -18,6 +18,8 @@
  */
 package org.apache.fineract.integrationtests;
 
+import static 
org.apache.fineract.integrationtests.BaseLoanIntegrationTest.InterestRateFrequencyType.YEARS;
+import static 
org.apache.fineract.integrationtests.BaseLoanIntegrationTest.RepaymentFrequencyType.DAYS;
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertNotNull;
 import static org.junit.jupiter.api.Assertions.assertThrows;
@@ -70,8 +72,9 @@ import 
org.apache.fineract.integrationtests.common.accounting.Account;
 import org.apache.fineract.integrationtests.common.accounting.AccountHelper;
 import org.apache.fineract.integrationtests.common.charges.ChargesHelper;
 import 
org.apache.fineract.integrationtests.common.loans.LoanProductTestBuilder;
-import 
org.apache.fineract.integrationtests.common.loans.LoanTestLifecycleExtension;
 import org.apache.fineract.integrationtests.common.loans.LoanTransactionHelper;
+import org.apache.fineract.portfolio.common.domain.DaysInMonthType;
+import org.apache.fineract.portfolio.common.domain.DaysInYearType;
 import 
org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.impl.AdvancedPaymentScheduleTransactionProcessor;
 import 
org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.impl.EarlyPaymentLoanRepaymentScheduleTransactionProcessor;
 import 
org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.impl.FineractStyleLoanRepaymentScheduleTransactionProcessor;
@@ -81,11 +84,9 @@ import 
org.apache.fineract.portfolio.loanproduct.domain.PaymentAllocationType;
 import org.junit.jupiter.api.Assertions;
 import org.junit.jupiter.api.BeforeAll;
 import org.junit.jupiter.api.Test;
-import org.junit.jupiter.api.extension.ExtendWith;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-@ExtendWith(LoanTestLifecycleExtension.class)
 public class AdvancedPaymentAllocationLoanRepaymentScheduleTest extends 
BaseLoanIntegrationTest {
 
     private static final Logger LOG = 
LoggerFactory.getLogger(AdvancedPaymentAllocationLoanRepaymentScheduleTest.class);
@@ -4410,6 +4411,196 @@ public class 
AdvancedPaymentAllocationLoanRepaymentScheduleTest extends BaseLoan
         });
     }
 
+    // UC138: Advanced payment allocation with Interest, EMI is correctly 
calculated, no adjustment
+    // ADVANCED_PAYMENT_ALLOCATION_STRATEGY
+    // 1. Create a Loan product with Adv. Pment. Alloc. and with 5% Interest, 
360/30, 1 repayment per month
+    // 2. Submit Loan and approve
+    // 3. Disburse
+    // 4. Validate Repayment Schedule
+    @Test
+    public void uc141() {
+        final String operationDate = "1 January 2024";
+        runAt(operationDate, () -> {
+            Long clientId = 
clientHelper.createClient(ClientHelper.defaultClientCreationRequest()).getClientId();
+            PostLoanProductsRequest product = 
createOnePeriod30DaysLongNoInterestPeriodicAccrualProductWithAdvancedPaymentAllocation()
+                    
.interestRatePerPeriod(5.0).interestCalculationPeriodType(DAYS).interestRateFrequencyType(YEARS)
+                    
.daysInMonthType(DaysInMonthType.DAYS_30.getValue()).daysInYearType(DaysInYearType.DAYS_360.getValue())
+                    .numberOfRepayments(5)//
+                    .repaymentEvery(1)//
+                    .repaymentFrequencyType(2L)//
+                    .enableDownPayment(true)//
+                    .allowPartialPeriodInterestCalcualtion(false)//
+                    .enableAutoRepaymentForDownPayment(false)//
+                    .multiDisburseLoan(false)//
+                    .disallowExpectedDisbursements(null)//
+                    .allowApprovedDisbursedAmountsOverApplied(null)//
+                    .overAppliedCalculationType(null)//
+                    .overAppliedNumber(null)//
+                    .installmentAmountInMultiplesOf(null)//
+                    
.disbursedAmountPercentageForDownPayment(BigDecimal.valueOf(25))//
+            ;//
+            PostLoanProductsResponse loanProductResponse = 
loanProductHelper.createLoanProduct(product);
+            PostLoansRequest applicationRequest = applyLoanRequest(clientId, 
loanProductResponse.getResourceId(), operationDate, 100.0, 5);
+
+            applicationRequest = 
applicationRequest.numberOfRepayments(5).loanTermFrequency(5).loanTermFrequencyType(2)
+                    
.interestRatePerPeriod(BigDecimal.valueOf(5)).interestCalculationPeriodType(DAYS)
+                    
.transactionProcessingStrategyCode(LoanProductTestBuilder.ADVANCED_PAYMENT_ALLOCATION_STRATEGY).repaymentEvery(1)
+                    .repaymentFrequencyType(2);
+
+            PostLoansResponse loanResponse = 
loanTransactionHelper.applyLoan(applicationRequest);
+
+            loanTransactionHelper.approveLoan(loanResponse.getLoanId(), new 
PostLoansLoanIdRequest()
+                    
.approvedLoanAmount(BigDecimal.valueOf(100)).dateFormat(DATETIME_PATTERN).approvedOnDate(operationDate).locale("en"));
+
+            loanTransactionHelper.disburseLoan(loanResponse.getLoanId(), new 
PostLoansLoanIdRequest().actualDisbursementDate(operationDate)
+                    
.dateFormat(DATETIME_PATTERN).transactionAmount(BigDecimal.valueOf(100.0)).locale("en"));
+
+            GetLoansLoanIdResponse loanDetails = 
loanTransactionHelper.getLoanDetails(loanResponse.getLoanId());
+            validateLoanSummaryBalances(loanDetails, 100.94, 0.0, 100.0, 0.0, 
null);
+            validateRepaymentPeriod(loanDetails, 1, LocalDate.of(2024, 1, 1), 
25.0, 0.0, 25.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0,
+                    0.0, 0.0);
+            validateRepaymentPeriod(loanDetails, 2, LocalDate.of(2024, 2, 1), 
14.88, 0.0, 14.88, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.31, 0.0,
+                    0.31, 0.0, 0.0);
+            validateRepaymentPeriod(loanDetails, 3, LocalDate.of(2024, 3, 1), 
14.94, 0.0, 14.94, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.25, 0.0,
+                    0.25, 0.0, 0.0);
+            validateRepaymentPeriod(loanDetails, 4, LocalDate.of(2024, 4, 1), 
15.00, 0.0, 15.00, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.19, 0.0,
+                    0.19, 0.0, 0.0);
+            validateRepaymentPeriod(loanDetails, 5, LocalDate.of(2024, 5, 1), 
15.06, 0.0, 15.06, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.13, 0.0,
+                    0.13, 0.0, 0.0);
+            validateRepaymentPeriod(loanDetails, 6, LocalDate.of(2024, 6, 1), 
15.12, 0.0, 15.12, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.06, 0.0,
+                    0.06, 0.0, 0.0);
+            assertTrue(loanDetails.getStatus().getActive());
+            assertEquals(loanDetails.getNumberOfRepayments(), 5);
+        });
+    }
+
+    // UC139: Advanced payment allocation with Interest, originally calculated 
EMI, need adjustment
+    // ADVANCED_PAYMENT_ALLOCATION_STRATEGY
+    // 1. Create a Loan product with Adv. Pment. Alloc. and with 12.3% 
Interest, 360/30, 1 repayment per month
+    // 2. Submit Loan and approve
+    // 3. Disburse
+    // 4. Validate Repayment Schedule
+    @Test
+    public void uc142() {
+        final String operationDate = "1 January 2024";
+        runAt(operationDate, () -> {
+            Long clientId = 
clientHelper.createClient(ClientHelper.defaultClientCreationRequest()).getClientId();
+            PostLoanProductsRequest product = 
createOnePeriod30DaysLongNoInterestPeriodicAccrualProductWithAdvancedPaymentAllocation()
+                    
.interestRatePerPeriod(12.3).interestCalculationPeriodType(RepaymentFrequencyType.DAYS).interestRateFrequencyType(YEARS)
+                    
.daysInMonthType(DaysInMonthType.DAYS_30.getValue()).daysInYearType(DaysInYearType.DAYS_360.getValue())
+                    .numberOfRepayments(5)//
+                    .repaymentEvery(1)//
+                    .repaymentFrequencyType(2L)//
+                    .enableDownPayment(true)//
+                    .allowPartialPeriodInterestCalcualtion(false)//
+                    .enableAutoRepaymentForDownPayment(false)//
+                    .multiDisburseLoan(false)//
+                    .disallowExpectedDisbursements(null)//
+                    .allowApprovedDisbursedAmountsOverApplied(null)//
+                    .overAppliedCalculationType(null)//
+                    .overAppliedNumber(null)//
+                    .installmentAmountInMultiplesOf(null)//
+                    
.disbursedAmountPercentageForDownPayment(BigDecimal.valueOf(25))//
+            ;//
+            PostLoanProductsResponse loanProductResponse = 
loanProductHelper.createLoanProduct(product);
+            PostLoansRequest applicationRequest = applyLoanRequest(clientId, 
loanProductResponse.getResourceId(), operationDate, 100.0, 5);
+
+            applicationRequest = 
applicationRequest.numberOfRepayments(5).loanTermFrequency(5).loanTermFrequencyType(2)
+                    
.interestRatePerPeriod(BigDecimal.valueOf(12.3)).interestCalculationPeriodType(DAYS)
+                    
.transactionProcessingStrategyCode(LoanProductTestBuilder.ADVANCED_PAYMENT_ALLOCATION_STRATEGY).repaymentEvery(1)
+                    .repaymentFrequencyType(2);
+
+            PostLoansResponse loanResponse = 
loanTransactionHelper.applyLoan(applicationRequest);
+
+            loanTransactionHelper.approveLoan(loanResponse.getLoanId(), new 
PostLoansLoanIdRequest()
+                    
.approvedLoanAmount(BigDecimal.valueOf(100)).dateFormat(DATETIME_PATTERN).approvedOnDate(operationDate).locale("en"));
+
+            loanTransactionHelper.disburseLoan(loanResponse.getLoanId(), new 
PostLoansLoanIdRequest().actualDisbursementDate(operationDate)
+                    
.dateFormat(DATETIME_PATTERN).transactionAmount(BigDecimal.valueOf(100.0)).locale("en"));
+
+            GetLoansLoanIdResponse loanDetails = 
loanTransactionHelper.getLoanDetails(loanResponse.getLoanId());
+            validateLoanSummaryBalances(loanDetails, 102.33, 0.0, 100.0, 0.0, 
null);
+            validateRepaymentPeriod(loanDetails, 1, LocalDate.of(2024, 1, 1), 
25.0, 0.0, 25.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0,
+                    0.0, 0.0);
+            validateRepaymentPeriod(loanDetails, 2, LocalDate.of(2024, 2, 1), 
14.70, 0.0, 14.70, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.77, 0.0,
+                    0.77, 0.0, 0.0);
+            validateRepaymentPeriod(loanDetails, 3, LocalDate.of(2024, 3, 1), 
14.85, 0.0, 14.85, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.62, 0.0,
+                    0.62, 0.0, 0.0);
+            validateRepaymentPeriod(loanDetails, 4, LocalDate.of(2024, 4, 1), 
15.00, 0.0, 15.00, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.47, 0.0,
+                    0.47, 0.0, 0.0);
+            validateRepaymentPeriod(loanDetails, 5, LocalDate.of(2024, 5, 1), 
15.16, 0.0, 15.16, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.31, 0.0,
+                    0.31, 0.0, 0.0);
+            validateRepaymentPeriod(loanDetails, 6, LocalDate.of(2024, 6, 1), 
15.29, 0.0, 15.29, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.16, 0.0,
+                    0.16, 0.0, 0.0);
+            assertTrue(loanDetails.getStatus().getActive());
+            assertEquals(loanDetails.getNumberOfRepayments(), 5);
+        });
+    }
+
+    // UC14-: Advanced payment allocation with Interest, originally calculated 
EMI, need adjustment, adjusted EMI is not
+    // better than original, use original EMI
+    // ADVANCED_PAYMENT_ALLOCATION_STRATEGY
+    // 1. Create a Loan product with Adv. Pment. Alloc. and with 5% Interest, 
360/30, 1 repayment per month
+    // 2. Submit Loan and approve
+    // 3. Disburse
+    // 4. Validate Repayment Schedule
+    @Test
+    public void uc143() {
+        final String operationDate = "1 January 2024";
+        runAt(operationDate, () -> {
+            Long clientId = 
clientHelper.createClient(ClientHelper.defaultClientCreationRequest()).getClientId();
+            PostLoanProductsRequest product = 
createOnePeriod30DaysLongNoInterestPeriodicAccrualProductWithAdvancedPaymentAllocation()
+                    
.interestRatePerPeriod(12.3).interestCalculationPeriodType(RepaymentFrequencyType.DAYS).interestRateFrequencyType(YEARS)
+                    
.daysInMonthType(DaysInMonthType.DAYS_30.getValue()).daysInYearType(DaysInYearType.DAYS_360.getValue())
+                    .numberOfRepayments(5)//
+                    .repaymentEvery(1)//
+                    .repaymentFrequencyType(2L)//
+                    .enableDownPayment(true)//
+                    .allowPartialPeriodInterestCalcualtion(false)//
+                    .enableAutoRepaymentForDownPayment(false)//
+                    .multiDisburseLoan(false)//
+                    .disallowExpectedDisbursements(null)//
+                    .allowApprovedDisbursedAmountsOverApplied(null)//
+                    .overAppliedCalculationType(null)//
+                    .overAppliedNumber(null)//
+                    .installmentAmountInMultiplesOf(null)//
+                    
.disbursedAmountPercentageForDownPayment(BigDecimal.valueOf(25))//
+            ;//
+            PostLoanProductsResponse loanProductResponse = 
loanProductHelper.createLoanProduct(product);
+            PostLoansRequest applicationRequest = applyLoanRequest(clientId, 
loanProductResponse.getResourceId(), operationDate, 100.0, 5);
+
+            applicationRequest = 
applicationRequest.numberOfRepayments(5).loanTermFrequency(5).loanTermFrequencyType(2)
+                    
.interestRatePerPeriod(BigDecimal.valueOf(12.3)).interestCalculationPeriodType(DAYS)
+                    
.transactionProcessingStrategyCode(LoanProductTestBuilder.ADVANCED_PAYMENT_ALLOCATION_STRATEGY).repaymentEvery(1)
+                    .repaymentFrequencyType(2);
+
+            PostLoansResponse loanResponse = 
loanTransactionHelper.applyLoan(applicationRequest);
+
+            loanTransactionHelper.approveLoan(loanResponse.getLoanId(), new 
PostLoansLoanIdRequest()
+                    
.approvedLoanAmount(BigDecimal.valueOf(100)).dateFormat(DATETIME_PATTERN).approvedOnDate(operationDate).locale("en"));
+
+            loanTransactionHelper.disburseLoan(loanResponse.getLoanId(), new 
PostLoansLoanIdRequest().actualDisbursementDate(operationDate)
+                    
.dateFormat(DATETIME_PATTERN).transactionAmount(BigDecimal.valueOf(100.0)).locale("en"));
+
+            GetLoansLoanIdResponse loanDetails = 
loanTransactionHelper.getLoanDetails(loanResponse.getLoanId());
+            validateLoanSummaryBalances(loanDetails, 102.33, 0.0, 100.0, 0.0, 
null);
+            validateRepaymentPeriod(loanDetails, 1, LocalDate.of(2024, 1, 1), 
25.0, 0.0, 25.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0,
+                    0.0, 0.0);
+            validateRepaymentPeriod(loanDetails, 2, LocalDate.of(2024, 2, 1), 
14.70, 0.0, 14.70, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.77, 0.0,
+                    0.77, 0.0, 0.0);
+            validateRepaymentPeriod(loanDetails, 3, LocalDate.of(2024, 3, 1), 
14.85, 0.0, 14.85, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.62, 0.0,
+                    0.62, 0.0, 0.0);
+            validateRepaymentPeriod(loanDetails, 4, LocalDate.of(2024, 4, 1), 
15.00, 0.0, 15.00, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.47, 0.0,
+                    0.47, 0.0, 0.0);
+            validateRepaymentPeriod(loanDetails, 5, LocalDate.of(2024, 5, 1), 
15.16, 0.0, 15.16, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.31, 0.0,
+                    0.31, 0.0, 0.0);
+            validateRepaymentPeriod(loanDetails, 6, LocalDate.of(2024, 6, 1), 
15.29, 0.0, 15.29, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.16, 0.0,
+                    0.16, 0.0, 0.0);
+            assertTrue(loanDetails.getStatus().getActive());
+            assertEquals(loanDetails.getNumberOfRepayments(), 5);
+        });
+    }
+
     private Long 
applyAndApproveLoanProgressiveAdvancedPaymentAllocationStrategyMonthlyRepayments(Long
 clientId, Long loanProductId,
             Integer numberOfRepayments, String loanDisbursementDate, double 
amount) {
         LOG.info("------------------------------APPLY AND APPROVE LOAN 
---------------------------------------");

Reply via email to