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 c85400f760 FINERACT-2357: Support moratorium for progressive loans
c85400f760 is described below

commit c85400f760b7a07b1f002e040f65d878e48e98d4
Author: airajena <[email protected]>
AuthorDate: Wed Feb 4 16:59:22 2026 +0530

    FINERACT-2357: Support moratorium for progressive loans
---
 .../mapper/LoanConfigurationDetailsMapper.java     |  2 +-
 .../loanproduct/calc/ProgressiveEMICalculator.java | 66 ++++++++++++++++----
 .../data/LoanInterestScheduleModelModifiers.java   |  3 +-
 .../data/ProgressiveLoanInterestScheduleModel.java | 15 ++++-
 .../loanproduct/calc/data/RepaymentPeriod.java     | 10 ++-
 .../calc/ProgressiveEMICalculatorTest.java         | 65 +++++++++++++++++++
 .../ProgressiveLoanMoratoriumIntegrationTest.java  | 72 ++++++++++++++++++++++
 7 files changed, 216 insertions(+), 17 deletions(-)

diff --git 
a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/mapper/LoanConfigurationDetailsMapper.java
 
b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/mapper/LoanConfigurationDetailsMapper.java
index 0254a6a51a..6a6896b4a8 100644
--- 
a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/mapper/LoanConfigurationDetailsMapper.java
+++ 
b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/mapper/LoanConfigurationDetailsMapper.java
@@ -48,7 +48,7 @@ public final class LoanConfigurationDetailsMapper {
 
         return new LoanConfigurationDetails(currencyData, 
loanProductRelatedDetail.getNominalInterestRatePerPeriod(),
                 loanProductRelatedDetail.getAnnualNominalInterestRate(), 
loanProductRelatedDetail.getGraceOnInterestCharged(),
-                loanProductRelatedDetail.getGraceOnPrincipalPayment(), 
loanProductRelatedDetail.getGraceOnPrincipalPayment(),
+                loanProductRelatedDetail.getGraceOnInterestPayment(), 
loanProductRelatedDetail.getGraceOnPrincipalPayment(),
                 
loanProductRelatedDetail.getRecurringMoratoriumOnPrincipalPeriods(), 
loanProductRelatedDetail.getInterestMethod(),
                 loanProductRelatedDetail.getInterestCalculationPeriodMethod(),
                 
DaysInYearType.fromInt(loanProductRelatedDetail.getDaysInYearType()),
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 c2e1546893..085387e067 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
@@ -658,11 +658,13 @@ public final class ProgressiveEMICalculator implements 
EMICalculator {
     private void calculateEMIValueAndRateFactorsForFlatInterestMethod(final 
LocalDate calculateFromRepaymentPeriodDueDate,
             final ProgressiveLoanInterestScheduleModel scheduleModel, final 
EmiChangeOperation operation) {
         final List<RepaymentPeriod> relatedRepaymentPeriods = 
scheduleModel.getRelatedRepaymentPeriods(calculateFromRepaymentPeriodDueDate);
+        applyInterestMoratoriumIfRequired(scheduleModel);
         calculateRateFactorForPeriods(relatedRepaymentPeriods, scheduleModel);
         if (relatedRepaymentPeriods.isEmpty()) {
             return;
         }
         
calculateEMIOnActualModelWithFlatInterestMethod(relatedRepaymentPeriods, 
scheduleModel);
+        applyPrincipalMoratoriumIfRequired(relatedRepaymentPeriods, 
scheduleModel);
     }
 
     /**
@@ -687,6 +689,7 @@ public final class ProgressiveEMICalculator implements 
EMICalculator {
                 || operation.getAction() == 
EmiChangeOperation.Action.INTEREST_RATE_CHANGE
                 || operation.getAction() == 
EmiChangeOperation.Action.ADD_REPAYMENT_PERIODS || scheduleModel.isCopy();
 
+        applyInterestMoratoriumIfRequired(scheduleModel);
         calculateRateFactorForPeriods(relatedRepaymentPeriods, scheduleModel);
         calculateOutstandingBalance(scheduleModel);
         if (onlyOnActualModelShouldApply) {
@@ -694,6 +697,7 @@ public final class ProgressiveEMICalculator implements 
EMICalculator {
         } else {
             calculateEMIOnNewModelAndMerge(relatedRepaymentPeriods, 
scheduleModel, operation);
         }
+        applyPrincipalMoratoriumIfRequired(relatedRepaymentPeriods, 
scheduleModel);
         calculateOutstandingBalance(scheduleModel);
         calculateLastUnpaidRepaymentPeriodEMI(scheduleModel, 
calculateFromRepaymentPeriodDueDate);
         if (onlyOnActualModelShouldApply) {
@@ -1628,11 +1632,48 @@ public final class ProgressiveEMICalculator implements 
EMICalculator {
         }
     }
 
+    private void applyPrincipalMoratoriumIfRequired(List<RepaymentPeriod> 
repaymentPeriods,
+            ProgressiveLoanInterestScheduleModel scheduleModel) {
+        if (repaymentPeriods.isEmpty()) {
+            return;
+        }
+        Integer graceOnPrincipalPayment = 
scheduleModel.loanProductRelatedDetail().getGraceOnPrincipalPayment();
+        if (graceOnPrincipalPayment == null || graceOnPrincipalPayment <= 0) {
+            return;
+        }
+        int gracePeriods = Math.min(graceOnPrincipalPayment, 
repaymentPeriods.size());
+        List<RepaymentPeriod> gracePeriodsList = repaymentPeriods.subList(0, 
gracePeriods);
+        gracePeriodsList.forEach(period -> {
+            Money interestOnlyEmi = period.getDueInterest();
+            period.setEmi(interestOnlyEmi);
+            period.setOriginalEmi(interestOnlyEmi);
+        });
+        if (gracePeriods == repaymentPeriods.size()) {
+            return;
+        }
+        calculateOutstandingBalance(scheduleModel);
+        calculateEMIOnActualModel(repaymentPeriods.subList(gracePeriods, 
repaymentPeriods.size()), scheduleModel);
+    }
+
+    private void applyInterestMoratoriumIfRequired(final 
ProgressiveLoanInterestScheduleModel scheduleModel) {
+        if (!scheduleModel.isInterestPauseForEmiCalculationEnabled() || 
scheduleModel.repaymentPeriods().isEmpty()) {
+            return;
+        }
+        final Integer graceOnInterestPayment = 
scheduleModel.loanProductRelatedDetail().getGraceOnInterestPayment();
+        if (graceOnInterestPayment == null || graceOnInterestPayment <= 0) {
+            return;
+        }
+        scheduleModel.repaymentPeriods().forEach(repaymentPeriod -> 
repaymentPeriod.setInterestPaymentGrace(false));
+        final int gracePeriods = Math.min(graceOnInterestPayment, 
scheduleModel.repaymentPeriods().size());
+        final List<RepaymentPeriod> gracePeriodsList = 
scheduleModel.repaymentPeriods().subList(0, gracePeriods);
+        gracePeriodsList.forEach(repaymentPeriod -> 
repaymentPeriod.setInterestPaymentGrace(true));
+    }
+
     private void 
calculateEMIOnActualModelWithDecliningBalanceInterestMethod(List<RepaymentPeriod>
 repaymentPeriods,
             ProgressiveLoanInterestScheduleModel scheduleModel) {
         final MathContext mc = scheduleModel.mc();
-        final BigDecimal rateFactorN = 
MathUtil.stripTrailingZeros(calculateRateFactorPlus1N(repaymentPeriods, mc));
-        final BigDecimal fnResult = 
MathUtil.stripTrailingZeros(calculateFnResult(repaymentPeriods, mc));
+        final BigDecimal rateFactorN = 
MathUtil.stripTrailingZeros(calculateRateFactorPlus1NForEmi(repaymentPeriods, 
scheduleModel, mc));
+        final BigDecimal fnResult = 
MathUtil.stripTrailingZeros(calculateFnResultForEmi(repaymentPeriods, 
scheduleModel, mc));
         final RepaymentPeriod startPeriod = repaymentPeriods.getFirst();
 
         final Money outstandingBalance = 
startPeriod.getInitialBalanceForEmiRecalculation();
@@ -1724,24 +1765,25 @@ public final class ProgressiveEMICalculator implements 
EMICalculator {
         return Optional.empty();
     }
 
-    /**
-     * Calculate Rate Factor Product from rate factors
-     */
-    private BigDecimal calculateRateFactorPlus1N(final List<RepaymentPeriod> 
periods, MathContext mc) {
-        return 
periods.stream().map(RepaymentPeriod::getRateFactorPlus1).reduce(BigDecimal.ONE,
+    private BigDecimal calculateRateFactorPlus1NForEmi(final 
List<RepaymentPeriod> periods,
+            final ProgressiveLoanInterestScheduleModel scheduleModel, 
MathContext mc) {
+        return periods.stream().map(period -> getRateFactorPlus1ForEmi(period, 
scheduleModel)).reduce(BigDecimal.ONE,
                 (BigDecimal acc, BigDecimal value) -> acc.multiply(value, mc));
     }
 
-    /**
-     * Summarize Fn values
-     */
-    private BigDecimal calculateFnResult(final List<RepaymentPeriod> periods, 
final MathContext mc) {
+    private BigDecimal calculateFnResultForEmi(final List<RepaymentPeriod> 
periods,
+            final ProgressiveLoanInterestScheduleModel scheduleModel, final 
MathContext mc) {
         return periods.stream()//
                 .skip(1)//
-                .map(RepaymentPeriod::getRateFactorPlus1)//
+                .map(period -> getRateFactorPlus1ForEmi(period, 
scheduleModel))//
                 .reduce(BigDecimal.ONE, (previousFnValue, currentRateFactor) 
-> fnValue(previousFnValue, currentRateFactor, mc));//
     }
 
+    private BigDecimal getRateFactorPlus1ForEmi(final RepaymentPeriod period, 
final ProgressiveLoanInterestScheduleModel scheduleModel) {
+        return scheduleModel.isInterestPauseForEmiCalculationEnabled() && 
period.isInterestPaymentGrace() ? BigDecimal.ONE
+                : period.getRateFactorPlus1();
+    }
+
     /**
      * Calculate the EMI (Equal Monthly Installment) value
      */
diff --git 
a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/calc/data/LoanInterestScheduleModelModifiers.java
 
b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/calc/data/LoanInterestScheduleModelModifiers.java
index 3ebd0bcb25..79faa8ed0f 100644
--- 
a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/calc/data/LoanInterestScheduleModelModifiers.java
+++ 
b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/calc/data/LoanInterestScheduleModelModifiers.java
@@ -21,5 +21,6 @@ package org.apache.fineract.portfolio.loanproduct.calc.data;
 public enum LoanInterestScheduleModelModifiers { //
     EMI_RECALCULATION, //
     COPY, //
-    INTEREST_RECALCULATION_ENABLED //
+    INTEREST_RECALCULATION_ENABLED, //
+    INTEREST_PAUSE_FOR_EMI_CALCULATION //
 }
diff --git 
a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/calc/data/ProgressiveLoanInterestScheduleModel.java
 
b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/calc/data/ProgressiveLoanInterestScheduleModel.java
index 1dabb865da..08bd958a23 100644
--- 
a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/calc/data/ProgressiveLoanInterestScheduleModel.java
+++ 
b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/calc/data/ProgressiveLoanInterestScheduleModel.java
@@ -21,6 +21,7 @@ package org.apache.fineract.portfolio.loanproduct.calc.data;
 import static 
org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleProcessingWrapper.isInPeriod;
 import static 
org.apache.fineract.portfolio.loanproduct.calc.data.LoanInterestScheduleModelModifiers.COPY;
 import static 
org.apache.fineract.portfolio.loanproduct.calc.data.LoanInterestScheduleModelModifiers.EMI_RECALCULATION;
+import static 
org.apache.fineract.portfolio.loanproduct.calc.data.LoanInterestScheduleModelModifiers.INTEREST_PAUSE_FOR_EMI_CALCULATION;
 import static 
org.apache.fineract.portfolio.loanproduct.calc.data.LoanInterestScheduleModelModifiers.INTEREST_RECALCULATION_ENABLED;
 
 import jakarta.validation.constraints.NotNull;
@@ -78,8 +79,11 @@ public class ProgressiveLoanInterestScheduleModel {
         this.installmentAmountInMultiplesOf = installmentAmountInMultiplesOf;
         this.mc = mc;
         this.zero = Money.zero(loanProductRelatedDetail.getCurrencyData(), mc);
+        final boolean interestPauseForEmiCalculation = 
loanProductRelatedDetail.getGraceOnInterestPayment() != null
+                && loanProductRelatedDetail.getGraceOnInterestPayment() > 0;
         modifiers = new HashMap<>(Map.of(EMI_RECALCULATION, true, COPY, false, 
INTEREST_RECALCULATION_ENABLED,
-                loanProductRelatedDetail.isInterestRecalculationEnabled()));
+                loanProductRelatedDetail.isInterestRecalculationEnabled(), 
INTEREST_PAUSE_FOR_EMI_CALCULATION,
+                interestPauseForEmiCalculation));
     }
 
     private ProgressiveLoanInterestScheduleModel(final List<RepaymentPeriod> 
repaymentPeriods, final TreeSet<InterestRate> interestRates,
@@ -92,8 +96,11 @@ public class ProgressiveLoanInterestScheduleModel {
         this.loanProductRelatedDetail = loanProductRelatedDetail;
         this.installmentAmountInMultiplesOf = installmentAmountInMultiplesOf;
         this.zero = Money.zero(loanProductRelatedDetail.getCurrencyData(), mc);
+        final boolean interestPauseForEmiCalculation = 
loanProductRelatedDetail.getGraceOnInterestPayment() != null
+                && loanProductRelatedDetail.getGraceOnInterestPayment() > 0;
         modifiers = new HashMap<>(Map.of(EMI_RECALCULATION, true, COPY, 
isCopiedForCalculation, INTEREST_RECALCULATION_ENABLED,
-                loanProductRelatedDetail.isInterestRecalculationEnabled()));
+                loanProductRelatedDetail.isInterestRecalculationEnabled(), 
INTEREST_PAUSE_FOR_EMI_CALCULATION,
+                interestPauseForEmiCalculation));
     }
 
     public void recordOverdueCorrection(final LocalDate correctionDate, final 
Money amount, final LocalDate affectedRpDueDate) {
@@ -446,6 +453,10 @@ public class ProgressiveLoanInterestScheduleModel {
         return this.modifiers.get(COPY);
     }
 
+    public boolean isInterestPauseForEmiCalculationEnabled() {
+        return this.modifiers.get(INTEREST_PAUSE_FOR_EMI_CALCULATION);
+    }
+
     public Function<Long, LocalDate> 
resolveRepaymentPeriodLengthGeneratorFunction(final LocalDate instance) {
         return switch 
(loanProductRelatedDetail.getRepaymentPeriodFrequencyType()) {
             case MONTHS -> instance::plusMonths;
diff --git 
a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/calc/data/RepaymentPeriod.java
 
b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/calc/data/RepaymentPeriod.java
index eca58ee334..0adaf82529 100644
--- 
a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/calc/data/RepaymentPeriod.java
+++ 
b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/calc/data/RepaymentPeriod.java
@@ -78,6 +78,9 @@ public class RepaymentPeriod {
     @Getter
     @Setter
     private boolean isInterestMovedUpward = false;
+    @Getter
+    @Setter
+    private boolean interestPaymentGrace = false;
 
     @Setter
     private Money totalDisbursedAmount;
@@ -158,6 +161,7 @@ public class RepaymentPeriod {
         
newRepaymentPeriod.setTotalDisbursedAmount(repaymentPeriod.getTotalDisbursedAmount());
         
newRepaymentPeriod.setTotalCapitalizedIncomeAmount(repaymentPeriod.getTotalCapitalizedIncomeAmount());
         
newRepaymentPeriod.setInterestMovedUpward(repaymentPeriod.isInterestMovedUpward());
+        
newRepaymentPeriod.setInterestPaymentGrace(repaymentPeriod.isInterestPaymentGrace());
         newRepaymentPeriod.setCurrency(repaymentPeriod.getCurrency());
         // There is always at least 1 interest period, by default with same 
from-due date as repayment period
         for (InterestPeriod interestPeriod : 
repaymentPeriod.getInterestPeriods()) {
@@ -180,6 +184,7 @@ public class RepaymentPeriod {
         
newRepaymentPeriod.setTotalDisbursedAmount(repaymentPeriod.getTotalDisbursedAmount());
         
newRepaymentPeriod.setTotalCapitalizedIncomeAmount(repaymentPeriod.getTotalCapitalizedIncomeAmount());
         
newRepaymentPeriod.setInterestMovedUpward(repaymentPeriod.isInterestMovedUpward());
+        
newRepaymentPeriod.setInterestPaymentGrace(repaymentPeriod.isInterestPaymentGrace());
         newRepaymentPeriod.setCurrency(repaymentPeriod.getCurrency());
         // There is always at least 1 interest period, by default with same 
from-due date as repayment period
         for (InterestPeriod interestPeriod : 
repaymentPeriod.getInterestPeriods()) {
@@ -265,6 +270,9 @@ public class RepaymentPeriod {
      * @return
      */
     public Money getDueInterest() {
+        if (isInterestPaymentGrace()) {
+            return getPaidInterest();
+        }
         if (dueInterestCalculation == null) {
             // Due interest might be the maximum paid if there is pay-off or 
early repayment
             dueInterestCalculation = Memo.of(
@@ -272,7 +280,7 @@ public class RepaymentPeriod {
                             : MathUtil.min(getCalculatedDueInterest(), 
getEmiPlusCreditedAmountsPlusFutureUnrecognizedInterest(), false),
                             getPaidInterest(), false),
                     () -> new Object[] { paidPrincipal, paidInterest, 
interestPeriods, futureUnrecognizedInterest, totalDisbursedAmount,
-                            fixedInterest, reAged, emi });
+                            fixedInterest, reAged, emi, interestPaymentGrace 
});
         }
         return dueInterestCalculation.get();
     }
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 491d10e452..c6b84d0461 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
@@ -112,6 +112,8 @@ class ProgressiveEMICalculatorTest {
         
Mockito.when(loanProductRelatedDetail.getInterestMethod()).thenReturn(InterestMethod.DECLINING_BALANCE);
         
Mockito.when(loanProductRelatedDetail.getInterestCalculationPeriodMethod()).thenReturn(InterestCalculationPeriodMethod.DAILY);
         
Mockito.when(loanProductRelatedDetail.isAllowPartialPeriodInterestCalculation()).thenReturn(true);
+        
Mockito.when(loanProductRelatedDetail.getGraceOnPrincipalPayment()).thenReturn(0);
+        
Mockito.when(loanProductRelatedDetail.getGraceOnInterestPayment()).thenReturn(0);
     }
 
     private BigDecimal getRateFactorsByMonth(final DaysInYearType 
daysInYearType, final DaysInMonthType daysInMonthType,
@@ -5207,6 +5209,69 @@ class ProgressiveEMICalculatorTest {
                 toCopy.installmentAmountInMultiplesOf());
     }
 
+    @Test
+    public void test_principalGraceForProgressiveSchedule() {
+        final List<LoanScheduleModelRepaymentPeriod> expectedRepaymentPeriods 
= new ArrayList<>();
+        expectedRepaymentPeriods.add(repayment(1, LocalDate.of(2024, 1, 1), 
LocalDate.of(2024, 2, 1)));
+        expectedRepaymentPeriods.add(repayment(2, LocalDate.of(2024, 2, 1), 
LocalDate.of(2024, 3, 1)));
+        expectedRepaymentPeriods.add(repayment(3, LocalDate.of(2024, 3, 1), 
LocalDate.of(2024, 4, 1)));
+        expectedRepaymentPeriods.add(repayment(4, LocalDate.of(2024, 4, 1), 
LocalDate.of(2024, 5, 1)));
+        expectedRepaymentPeriods.add(repayment(5, LocalDate.of(2024, 5, 1), 
LocalDate.of(2024, 6, 1)));
+        expectedRepaymentPeriods.add(repayment(6, LocalDate.of(2024, 6, 1), 
LocalDate.of(2024, 7, 1)));
+
+        
Mockito.when(loanProductRelatedDetail.getAnnualNominalInterestRate()).thenReturn(BigDecimal.valueOf(7.0));
+        
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);
+        
Mockito.when(loanProductRelatedDetail.getNumberOfRepayments()).thenReturn(6);
+        
Mockito.when(loanProductRelatedDetail.getGraceOnPrincipalPayment()).thenReturn(2);
+        
Mockito.when(loanProductRelatedDetail.getGraceOnInterestPayment()).thenReturn(0);
+
+        final ProgressiveLoanInterestScheduleModel interestSchedule = 
emiCalculator
+                .generatePeriodInterestScheduleModel(expectedRepaymentPeriods, 
loanProductRelatedDetail, null, mc);
+
+        emiCalculator.addDisbursement(interestSchedule, LocalDate.of(2024, 1, 
1), toMoney(100.0));
+
+        checkPeriod(interestSchedule, 0, 0.58, 0.58, 0.0, 100.0, false);
+        checkPeriod(interestSchedule, 1, 0.58, 0.58, 0.0, 100.0, false);
+        checkPeriod(interestSchedule, 2, 25.37, 0.58, 24.79, 75.21, false);
+        checkPeriod(interestSchedule, 3, 25.37, 0.44, 24.93, 50.28, false);
+        checkPeriod(interestSchedule, 4, 25.37, 0.29, 25.08, 25.20, false);
+        checkPeriod(interestSchedule, 5, 25.35, 0.15, 25.20, 0.0, false);
+    }
+
+    @Test
+    public void test_interestGraceForProgressiveSchedule() {
+        final List<LoanScheduleModelRepaymentPeriod> expectedRepaymentPeriods 
= new ArrayList<>();
+        expectedRepaymentPeriods.add(repayment(1, LocalDate.of(2024, 1, 1), 
LocalDate.of(2024, 2, 1)));
+        expectedRepaymentPeriods.add(repayment(2, LocalDate.of(2024, 2, 1), 
LocalDate.of(2024, 3, 1)));
+        expectedRepaymentPeriods.add(repayment(3, LocalDate.of(2024, 3, 1), 
LocalDate.of(2024, 4, 1)));
+        expectedRepaymentPeriods.add(repayment(4, LocalDate.of(2024, 4, 1), 
LocalDate.of(2024, 5, 1)));
+        expectedRepaymentPeriods.add(repayment(5, LocalDate.of(2024, 5, 1), 
LocalDate.of(2024, 6, 1)));
+        expectedRepaymentPeriods.add(repayment(6, LocalDate.of(2024, 6, 1), 
LocalDate.of(2024, 7, 1)));
+
+        
Mockito.when(loanProductRelatedDetail.getAnnualNominalInterestRate()).thenReturn(BigDecimal.valueOf(7.0));
+        
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);
+        
Mockito.when(loanProductRelatedDetail.getNumberOfRepayments()).thenReturn(6);
+        
Mockito.when(loanProductRelatedDetail.getGraceOnPrincipalPayment()).thenReturn(0);
+        
Mockito.when(loanProductRelatedDetail.getGraceOnInterestPayment()).thenReturn(2);
+
+        final ProgressiveLoanInterestScheduleModel interestSchedule = 
emiCalculator
+                .generatePeriodInterestScheduleModel(expectedRepaymentPeriods, 
loanProductRelatedDetail, null, mc);
+
+        emiCalculator.addDisbursement(interestSchedule, LocalDate.of(2024, 1, 
1), toMoney(100.0));
+        checkPeriod(interestSchedule, 0, 17.01, 0.0, 17.01, 82.99, false);
+        checkPeriod(interestSchedule, 1, 17.01, 0.0, 17.01, 65.98, false);
+        checkPeriod(interestSchedule, 2, 17.01, 1.44, 15.57, 50.41, false);
+        checkPeriod(interestSchedule, 3, 17.01, 0.29, 16.72, 33.69, false);
+        checkPeriod(interestSchedule, 4, 17.01, 0.20, 16.81, 16.88, false);
+        checkPeriod(interestSchedule, 5, 16.98, 0.10, 16.88, 0.0, false);
+    }
+
     @Test
     public void 
test_fullTermTranche_disbursedAmt200_2ndOnDueDate_dayInYears360_daysInMonth30_repayEvery1Month()
 {
         // Create 7 periods (6 original + 1 extension for second tranche)
diff --git 
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/ProgressiveLoanMoratoriumIntegrationTest.java
 
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/ProgressiveLoanMoratoriumIntegrationTest.java
new file mode 100644
index 0000000000..9864c69cda
--- /dev/null
+++ 
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/ProgressiveLoanMoratoriumIntegrationTest.java
@@ -0,0 +1,72 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.integrationtests;
+
+import java.math.BigDecimal;
+import org.apache.fineract.client.models.PostClientsResponse;
+import org.apache.fineract.client.models.PostLoanProductsResponse;
+import org.apache.fineract.integrationtests.common.ClientHelper;
+import org.junit.jupiter.api.Test;
+
+public class ProgressiveLoanMoratoriumIntegrationTest extends 
BaseLoanIntegrationTest {
+
+    @Test
+    public void testProgressivePrincipalMoratoriumSchedule() {
+        final PostClientsResponse client = 
clientHelper.createClient(ClientHelper.defaultClientCreationRequest());
+
+        runAt("1 January 2024", () -> {
+            PostLoanProductsResponse loanProduct = 
loanProductHelper.createLoanProduct(create4IProgressive().principal(100.0)
+                    
.minPrincipal(100.0).maxPrincipal(100.0).numberOfRepayments(6).interestRatePerPeriod(7.0));
+
+            Long loanId = applyAndApproveProgressiveLoan(client.getClientId(), 
loanProduct.getResourceId(), "1 January 2024", 100.0, 7.0, 6,
+                    request -> request.graceOnPrincipalPayment(2));
+
+            disburseLoan(loanId, BigDecimal.valueOf(100.0), "1 January 2024");
+
+            verifyRepaymentSchedule(loanId, installment(100.0, null, "01 
January 2024"),
+                    installment(0.0, 0.58, 0.58, false, "01 February 2024"), 
installment(0.0, 0.58, 0.58, false, "01 March 2024"), //
+                    installment(24.79, 0.58, 25.37, false, "01 April 2024"), //
+                    installment(24.93, 0.44, 25.37, false, "01 May 2024"), //
+                    installment(25.08, 0.29, 25.37, false, "01 June 2024"), //
+                    installment(25.20, 0.15, 25.35, false, "01 July 2024"));
+        });
+    }
+
+    @Test
+    public void testProgressiveInterestMoratoriumSchedule() {
+        final PostClientsResponse client = 
clientHelper.createClient(ClientHelper.defaultClientCreationRequest());
+
+        runAt("1 January 2024", () -> {
+            PostLoanProductsResponse loanProduct = 
loanProductHelper.createLoanProduct(create4IProgressive().principal(100.0)
+                    
.minPrincipal(100.0).maxPrincipal(100.0).numberOfRepayments(6).interestRatePerPeriod(7.0));
+
+            Long loanId = applyAndApproveProgressiveLoan(client.getClientId(), 
loanProduct.getResourceId(), "1 January 2024", 100.0, 7.0, 6,
+                    request -> request.graceOnInterestPayment(2));
+
+            disburseLoan(loanId, BigDecimal.valueOf(100.0), "1 January 2024");
+
+            verifyRepaymentSchedule(loanId, installment(100.0, null, "01 
January 2024"),
+                    installment(17.01, 0.0, 17.01, false, "01 February 2024"), 
installment(17.01, 0.0, 17.01, false, "01 March 2024"), //
+                    installment(15.57, 1.44, 17.01, false, "01 April 2024"), //
+                    installment(16.72, 0.29, 17.01, false, "01 May 2024"), //
+                    installment(16.81, 0.20, 17.01, false, "01 June 2024"), //
+                    installment(16.88, 0.10, 16.98, false, "01 July 2024"));
+        });
+    }
+}

Reply via email to