You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@fineract.apache.org by ad...@apache.org on 2024/08/07 21:04:34 UTC

(fineract) branch develop updated: FINERACT-1981: Repayment Schedule Interest Model with multi disbursement support, AdvPymnTxProcessor fix

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 69dd19c74 FINERACT-1981: Repayment Schedule Interest Model with multi disbursement support, AdvPymnTxProcessor fix
69dd19c74 is described below

commit 69dd19c74b374136d82ee9049c4cc7fb17b4388c
Author: Janos Meszaros <ja...@outlook.hu>
AuthorDate: Wed Jul 31 14:34:05 2024 +0200

    FINERACT-1981: Repayment Schedule Interest Model with multi disbursement support, AdvPymnTxProcessor fix
---
 .../src/test/resources/features/Loan.feature       |   8 +-
 .../resources/features/LoanDownPayment.feature     |   4 +-
 .../portfolio/loanaccount/domain/Loan.java         |  18 +-
 .../domain/LoanRepaymentScheduleInstallment.java   |  21 +-
 ...tLoanRepaymentScheduleTransactionProcessor.java |   7 +-
 .../LoanRepaymentScheduleTransactionProcessor.java |  18 -
 .../transactionprocessor/TransactionCtx.java       |  26 +-
 .../AbstractCumulativeLoanScheduleGenerator.java   |   5 +-
 .../LoanScheduleModelDisbursementPeriod.java       |   7 -
 .../domain/LoanScheduleModelRepaymentPeriod.java   |  12 +-
 .../domain/PreGeneratedLoanSchedulePeriod.java     |  96 ---
 .../domain/ScheduledDateGenerator.java             |   2 +-
 ...dvancedPaymentScheduleTransactionProcessor.java | 261 ++++---
 .../impl/ProgressiveTransactionCtx.java            |  45 ++
 ...ressiveLoanInterestRepaymentInterestPeriod.java |  52 ++
 .../ProgressiveLoanInterestRepaymentModel.java     |  82 ++
 .../ProgressiveLoanInterestScheduleModel.java}     |  22 +-
 .../AbstractProgressiveLoanScheduleGenerator.java  | 620 ---------------
 .../domain/ProgressiveLoanScheduleGenerator.java   | 344 ++++++++-
 .../portfolio/loanproduct/calc/EMICalculator.java  |  28 +-
 .../loanproduct/calc/ProgressiveEMICalculator.java | 527 ++++++++-----
 .../loanproduct/calc/RepaymentPeriodModel.java     |  24 -
 ...rogressiveLoanInterestRepaymentModelMapper.java |  37 +
 ...cedPaymentScheduleTransactionProcessorTest.java |  20 +-
 .../calc/ProgressiveEMICalculatorTest.java         | 845 ++++++++++++---------
 .../ApplyChargeToOverdueLoansBusinessStep.java     |   1 -
 .../domain/DefaultScheduledDateGenerator.java      |  30 +-
 .../LoanChargeWritePlatformServiceImpl.java        |   5 +-
 .../LoanWritePlatformServiceJpaRepositoryImpl.java |   6 +-
 .../service/reaging/LoanReAgingServiceImpl.java    |   6 +-
 .../LoanReAmortizationServiceImpl.java             |   6 +-
 .../starter/LoanAccountAutoStarter.java            |   7 +-
 .../domain/DefaultScheduledDateGeneratorTest.java  |   2 +-
 ...PaymentAllocationLoanRepaymentScheduleTest.java | 245 ++++--
 34 files changed, 1844 insertions(+), 1595 deletions(-)

diff --git a/fineract-e2e-tests-runner/src/test/resources/features/Loan.feature b/fineract-e2e-tests-runner/src/test/resources/features/Loan.feature
index 60555c9b0..07e2df118 100644
--- a/fineract-e2e-tests-runner/src/test/resources/features/Loan.feature
+++ b/fineract-e2e-tests-runner/src/test/resources/features/Loan.feature
@@ -5237,8 +5237,8 @@ Feature: Loan
       | 2  | 15   | 16 January 2024  | 16 January 2024 | 250.0           | 125.0         | 0.0      | 0.0  | 0.0       | 125.0 | 125.0 | 0.0        | 0.0  | 0.0         |
       |    |      | 16 January 2024  |                 | 500.0           |               |          | 0.0  |           | 0.0   | 0.0   |            |      |             |
       | 3  | 0    | 16 January 2024  | 16 January 2024 | 625.0           | 125.0         | 0.0      | 0.0  | 0.0       | 125.0 | 125.0 | 0.0        | 0.0  | 0.0         |
-      | 4  | 15   | 31 January 2024  |                 | 312.0           | 313.0         | 0.0      | 0.0  | 0.0       | 313.0 | 125.0 | 125.0      | 0.0  | 188.0       |
-      | 5  | 15   | 15 February 2024 |                 | 0.0             | 312.0         | 0.0      | 0.0  | 0.0       | 312.0 | 125.0 | 125.0      | 0.0  | 187.0       |
+      | 4  | 15   | 31 January 2024  |                 | 313.0           | 312.0         | 0.0      | 0.0  | 0.0       | 312.0 | 125.0 | 125.0      | 0.0  | 187.0       |
+      | 5  | 15   | 15 February 2024 |                 | 0.0             | 313.0         | 0.0      | 0.0  | 0.0       | 313.0 | 125.0 | 125.0      | 0.0  | 188.0       |
     Then Loan Repayment schedule has the following data in Total row:
       | Principal due | Interest | Fees | Penalties | Due    | Paid  | In advance | Late | Outstanding |
       | 1000.0        | 0        | 0.0  | 0         | 1000.0 | 625.0 | 250.0      | 0    | 375.0       |
@@ -5274,8 +5274,8 @@ Feature: Loan
       | 2  | 15   | 16 January 2024  | 16 January 2024 | 250.0           | 125.0         | 0.0      | 0.0  | 0.0       | 125.0 | 125.0 | 0.0        | 0.0  | 0.0         |
       |    |      | 16 January 2024  |                 | 500.0           |               |          | 0.0  |           | 0.0   | 0.0   |            |      |             |
       | 3  | 0    | 16 January 2024  | 16 January 2024 | 625.0           | 125.0         | 0.0      | 0.0  | 0.0       | 125.0 | 125.0 | 0.0        | 0.0  | 0.0         |
-      | 4  | 15   | 31 January 2024  |                 | 312.0           | 313.0         | 0.0      | 0.0  | 0.0       | 313.0 | 125.0 | 125.0      | 0.0  | 188.0       |
-      | 5  | 15   | 15 February 2024 |                 | 0.0             | 312.0         | 0.0      | 0.0  | 0.0       | 312.0 | 125.0 | 125.0      | 0.0  | 187.0       |
+      | 4  | 15   | 31 January 2024  |                 | 313.0           | 312.0         | 0.0      | 0.0  | 0.0       | 312.0 | 125.0 | 125.0      | 0.0  | 187.0       |
+      | 5  | 15   | 15 February 2024 |                 | 0.0             | 313.0         | 0.0      | 0.0  | 0.0       | 313.0 | 125.0 | 125.0      | 0.0  | 188.0       |
     Then Loan Repayment schedule has the following data in Total row:
       | Principal due | Interest | Fees | Penalties | Due    | Paid  | In advance | Late | Outstanding |
       | 1000.0        | 0        | 0.0  | 0         | 1000.0 | 625.0 | 250.0      | 0    | 375.0       |
diff --git a/fineract-e2e-tests-runner/src/test/resources/features/LoanDownPayment.feature b/fineract-e2e-tests-runner/src/test/resources/features/LoanDownPayment.feature
index c66222764..a2f316a2b 100644
--- a/fineract-e2e-tests-runner/src/test/resources/features/LoanDownPayment.feature
+++ b/fineract-e2e-tests-runner/src/test/resources/features/LoanDownPayment.feature
@@ -2446,8 +2446,8 @@ Feature: Loan DownPayment
       | 2  | 15   | 16 February 2024 | 16 February 2024 | 250.0           | 125.0         | 0.0      | 0.0  | 0.0       | 125.0 | 125.0 | 0.0        | 0.0  | 0.0         |
       |    |      | 20 February 2024 |                  | 625.0           |               |          | 0.0  |           | 0.0   | 0.0   |            |      |             |
       | 3  | 0    | 20 February 2024 | 20 February 2024 | 719.0           | 156.0         | 0.0      | 0.0  | 0.0       | 156.0 | 156.0 | 0.0        | 0.0  | 0.0         |
-      | 4  | 15   | 02 March 2024    | 20 February 2024 | 360.0           | 359.0         | 0.0      | 0.0  | 0.0       | 359.0 | 359.0 | 359.0      | 0.0  | 0.0         |
-      | 5  | 15   | 17 March 2024    | 20 February 2024 | 0.0             | 360.0         | 0.0      | 0.0  | 0.0       | 360.0 | 360.0 | 360.0      | 0.0  | 0.0         |
+      | 4  | 15   | 02 March 2024    | 20 February 2024 | 359.0           | 360.0         | 0.0      | 0.0  | 0.0       | 360.0 | 360.0 | 360.0      | 0.0  | 0.0         |
+      | 5  | 15   | 17 March 2024    | 20 February 2024 | 0.0             | 359.0         | 0.0      | 0.0  | 0.0       | 359.0 | 359.0 | 359.0      | 0.0  | 0.0         |
     Then Loan Repayment schedule has the following data in Total row:
       | Principal due | Interest | Fees | Penalties | Due   | Paid  | In advance | Late | Outstanding |
       | 1125.0         | 0.0      | 0.0  | 0.0       | 1125.0 | 1125.0 | 719.0      | 0.0  | 0.0         |
diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java
index e6c59a5d7..17c7281e6 100644
--- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java
+++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java
@@ -111,8 +111,8 @@ import org.apache.fineract.portfolio.loanaccount.data.HolidayDetailDTO;
 import org.apache.fineract.portfolio.loanaccount.data.LoanTermVariationsData;
 import org.apache.fineract.portfolio.loanaccount.data.ScheduleGeneratorDTO;
 import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.LoanRepaymentScheduleTransactionProcessor;
-import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.LoanRepaymentScheduleTransactionProcessor.TransactionCtx;
 import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.MoneyHolder;
+import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.TransactionCtx;
 import org.apache.fineract.portfolio.loanaccount.exception.InvalidLoanStateTransitionException;
 import org.apache.fineract.portfolio.loanaccount.exception.InvalidLoanTransactionTypeException;
 import org.apache.fineract.portfolio.loanaccount.exception.InvalidRefundDateException;
@@ -801,8 +801,8 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
         }
         final Set<LoanCharge> loanCharges = new HashSet<>(1);
         loanCharges.add(charge);
-        loanRepaymentScheduleTransactionProcessor.processLatestTransaction(chargesPayment,
-                new TransactionCtx(getCurrency(), chargePaymentInstallments, loanCharges, new MoneyHolder(getTotalOverpaidAsMoney())));
+        loanRepaymentScheduleTransactionProcessor.processLatestTransaction(chargesPayment, new TransactionCtx(getCurrency(),
+                chargePaymentInstallments, loanCharges, new MoneyHolder(getTotalOverpaidAsMoney()), null));
 
         updateLoanSummaryDerivedFields();
         doPostLoanTransactionChecks(chargesPayment.getTransactionDate(), loanLifecycleStateMachine);
@@ -1887,7 +1887,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
     public void updateLoanRepaymentPeriodsDerivedFields(final LocalDate actualDisbursementDate) {
         List<LoanRepaymentScheduleInstallment> installments = getRepaymentScheduleInstallments();
         for (final LoanRepaymentScheduleInstallment repaymentPeriod : installments) {
-            repaymentPeriod.updateDerivedFields(loanCurrency(), actualDisbursementDate);
+            repaymentPeriod.updateObligationsMet(loanCurrency(), actualDisbursementDate);
         }
     }
 
@@ -2395,7 +2395,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
         if (isTransactionChronologicallyLatest && adjustedTransaction == null
                 && (!reprocess || !this.repaymentScheduleDetail().isInterestRecalculationEnabled()) && !isForeclosure()) {
             loanRepaymentScheduleTransactionProcessor.processLatestTransaction(loanTransaction, new TransactionCtx(getCurrency(),
-                    getRepaymentScheduleInstallments(), getActiveCharges(), new MoneyHolder(getTotalOverpaidAsMoney())));
+                    getRepaymentScheduleInstallments(), getActiveCharges(), new MoneyHolder(getTotalOverpaidAsMoney()), null));
             reprocess = false;
             if (this.repaymentScheduleDetail().isInterestRecalculationEnabled()) {
                 if (currentInstallment == null || currentInstallment.isNotFullyPaidOff()) {
@@ -2843,7 +2843,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
 
             addLoanTransaction(loanTransaction);
             loanRepaymentScheduleTransactionProcessor.processLatestTransaction(loanTransaction, new TransactionCtx(loanCurrency(),
-                    getRepaymentScheduleInstallments(), getActiveCharges(), new MoneyHolder(getTotalOverpaidAsMoney())));
+                    getRepaymentScheduleInstallments(), getActiveCharges(), new MoneyHolder(getTotalOverpaidAsMoney()), null));
 
             updateLoanSummaryDerivedFields();
         }
@@ -2945,7 +2945,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
 
                 addLoanTransaction(loanTransaction);
                 loanRepaymentScheduleTransactionProcessor.processLatestTransaction(loanTransaction, new TransactionCtx(loanCurrency(),
-                        getRepaymentScheduleInstallments(), getActiveCharges(), new MoneyHolder(getTotalOverpaidAsMoney())));
+                        getRepaymentScheduleInstallments(), getActiveCharges(), new MoneyHolder(getTotalOverpaidAsMoney()), null));
 
                 updateLoanSummaryDerivedFields();
             } else if (totalOutstanding.isGreaterThanZero()) {
@@ -4858,7 +4858,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
         // If it's a refund
         if (adjustedTransaction == null) {
             loanRepaymentScheduleTransactionProcessor.processLatestTransaction(loanTransaction, new TransactionCtx(getCurrency(),
-                    getRepaymentScheduleInstallments(), getActiveCharges(), new MoneyHolder(getTotalOverpaidAsMoney())));
+                    getRepaymentScheduleInstallments(), getActiveCharges(), new MoneyHolder(getTotalOverpaidAsMoney()), null));
         } else {
             final List<LoanTransaction> allNonContraTransactionsPostDisbursement = retrieveListOfTransactionsForReprocessing();
             changedTransactionDetail = loanRepaymentScheduleTransactionProcessor.reprocessLoanTransactions(getDisbursementDate(),
@@ -4888,7 +4888,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
 
         addLoanTransaction(chargebackTransaction);
         loanRepaymentScheduleTransactionProcessor.processLatestTransaction(chargebackTransaction, new TransactionCtx(getCurrency(),
-                getRepaymentScheduleInstallments(), getActiveCharges(), new MoneyHolder(getTotalOverpaidAsMoney())));
+                getRepaymentScheduleInstallments(), getActiveCharges(), new MoneyHolder(getTotalOverpaidAsMoney()), null));
 
         updateLoanSummaryDerivedFields();
         if (!doPostLoanTransactionChecks(chargebackTransaction.getTransactionDate(), loanLifecycleStateMachine)) {
diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanRepaymentScheduleInstallment.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanRepaymentScheduleInstallment.java
index 97c2ea015..7c072bf1b 100644
--- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanRepaymentScheduleInstallment.java
+++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanRepaymentScheduleInstallment.java
@@ -743,10 +743,13 @@ public class LoanRepaymentScheduleInstallment extends AbstractAuditableWithUTCDa
         this.penaltyAccrued = defaultToNullIfZero(penalityCharges.getAmount());
     }
 
-    public void updateDerivedFields(final MonetaryCurrency currency, final LocalDate actualDisbursementDate) {
+    public void updateObligationsMet(final MonetaryCurrency currency, final LocalDate transactionDate) {
         if (!this.obligationsMet && getTotalOutstanding(currency).isZero()) {
             this.obligationsMet = true;
-            this.obligationsMetOnDate = actualDisbursementDate;
+            this.obligationsMetOnDate = transactionDate;
+        } else if (this.obligationsMet && !getTotalOutstanding(currency).isZero()) {
+            this.obligationsMet = false;
+            this.obligationsMetOnDate = null;
         }
     }
 
@@ -843,6 +846,15 @@ public class LoanRepaymentScheduleInstallment extends AbstractAuditableWithUTCDa
         checkIfRepaymentPeriodObligationsAreMet(transactionDate, transactionAmount.getCurrency());
     }
 
+    public void addToInterest(final LocalDate transactionDate, final Money transactionAmount) {
+        if (this.interestCharged == null) {
+            this.interestCharged = transactionAmount.getAmount();
+        } else {
+            this.interestCharged = this.interestCharged.add(transactionAmount.getAmount());
+        }
+        checkIfRepaymentPeriodObligationsAreMet(transactionDate, transactionAmount.getCurrency());
+    }
+
     public void addToCreditedPrincipal(final BigDecimal amount) {
         if (this.creditedPrincipal == null) {
             this.creditedPrincipal = amount;
@@ -1051,6 +1063,11 @@ public class LoanRepaymentScheduleInstallment extends AbstractAuditableWithUTCDa
         resetDerivedComponents();
         resetPrincipalDue();
         resetChargesCharged();
+        resetInterestDue();
+    }
+
+    public void resetInterestDue() {
+        this.interestCharged = null;
     }
 
     public void resetPrincipalDue() {
diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/AbstractLoanRepaymentScheduleTransactionProcessor.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/AbstractLoanRepaymentScheduleTransactionProcessor.java
index 742f2bb51..e5978412e 100644
--- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/AbstractLoanRepaymentScheduleTransactionProcessor.java
+++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/AbstractLoanRepaymentScheduleTransactionProcessor.java
@@ -83,7 +83,7 @@ public abstract class AbstractLoanRepaymentScheduleTransactionProcessor implemen
 
         for (final LoanRepaymentScheduleInstallment currentInstallment : installments) {
             currentInstallment.resetDerivedComponents();
-            currentInstallment.updateDerivedFields(currency, disbursementDate);
+            currentInstallment.updateObligationsMet(currency, disbursementDate);
         }
 
         // re-process loan charges over repayment periods (picking up on waived
@@ -164,7 +164,7 @@ public abstract class AbstractLoanRepaymentScheduleTransactionProcessor implemen
             if (loanTransaction.isRepaymentLikeType() || loanTransaction.isInterestWaiver() || loanTransaction.isRecoveryRepayment()) {
                 // pass through for new transactions
                 if (loanTransaction.getId() == null) {
-                    processLatestTransaction(loanTransaction, new TransactionCtx(currency, installments, charges, overpaymentHolder));
+                    processLatestTransaction(loanTransaction, new TransactionCtx(currency, installments, charges, overpaymentHolder, null));
                     loanTransaction.adjustInterestComponent(currency);
                 } else {
                     /**
@@ -175,7 +175,8 @@ public abstract class AbstractLoanRepaymentScheduleTransactionProcessor implemen
 
                     // Reset derived component of new loan transaction and
                     // re-process transaction
-                    processLatestTransaction(newLoanTransaction, new TransactionCtx(currency, installments, charges, overpaymentHolder));
+                    processLatestTransaction(newLoanTransaction,
+                            new TransactionCtx(currency, installments, charges, overpaymentHolder, null));
                     newLoanTransaction.adjustInterestComponent(currency);
                     /**
                      * Check if the transaction amounts have changed. If so, reverse the original transaction and update
diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/LoanRepaymentScheduleTransactionProcessor.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/LoanRepaymentScheduleTransactionProcessor.java
index 7e862ebe1..f49635f62 100644
--- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/LoanRepaymentScheduleTransactionProcessor.java
+++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/LoanRepaymentScheduleTransactionProcessor.java
@@ -21,8 +21,6 @@ package org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor;
 import java.time.LocalDate;
 import java.util.List;
 import java.util.Set;
-import lombok.AllArgsConstructor;
-import lombok.Data;
 import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
 import org.apache.fineract.organisation.monetary.domain.Money;
 import org.apache.fineract.portfolio.loanaccount.domain.ChangedTransactionDetail;
@@ -32,22 +30,6 @@ import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
 
 public interface LoanRepaymentScheduleTransactionProcessor {
 
-    @Data
-    @AllArgsConstructor
-    class TransactionCtx {
-
-        private final MonetaryCurrency currency;
-        private final List<LoanRepaymentScheduleInstallment> installments;
-        private final Set<LoanCharge> charges;
-        private final MoneyHolder overpaymentHolder;
-        private final ChangedTransactionDetail changedTransactionDetail;
-
-        public TransactionCtx(MonetaryCurrency currency, List<LoanRepaymentScheduleInstallment> installments, Set<LoanCharge> charges,
-                MoneyHolder overpaymentHolder) {
-            this(currency, installments, charges, overpaymentHolder, null);
-        }
-    }
-
     String getCode();
 
     String getName();
diff --git a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/calc/RepaymentScheduleModel.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/TransactionCtx.java
similarity index 50%
copy from fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/calc/RepaymentScheduleModel.java
copy to fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/TransactionCtx.java
index 14d224921..bda760734 100644
--- a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/calc/RepaymentScheduleModel.java
+++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/TransactionCtx.java
@@ -16,18 +16,24 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.fineract.portfolio.loanproduct.calc;
+package org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor;
 
-import java.util.ArrayList;
 import java.util.List;
-import lombok.Getter;
+import java.util.Set;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
+import org.apache.fineract.portfolio.loanaccount.domain.ChangedTransactionDetail;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanCharge;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment;
 
-@Getter
-public class RepaymentScheduleModel {
+@Data
+@AllArgsConstructor
+public class TransactionCtx {
 
-    List<RepaymentPeriodModel> scheduleList = new ArrayList<>();
-
-    public void addRepaymentPeriodModel(final RepaymentPeriodModel repaymentPeriodModel) {
-        scheduleList.add(repaymentPeriodModel);
-    }
+    private final MonetaryCurrency currency;
+    private final List<LoanRepaymentScheduleInstallment> installments;
+    private final Set<LoanCharge> charges;
+    private final MoneyHolder overpaymentHolder;
+    private final ChangedTransactionDetail changedTransactionDetail;
 }
diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/AbstractCumulativeLoanScheduleGenerator.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/AbstractCumulativeLoanScheduleGenerator.java
index 7e46ce5cd..c4d4b8847 100644
--- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/AbstractCumulativeLoanScheduleGenerator.java
+++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/AbstractCumulativeLoanScheduleGenerator.java
@@ -2001,8 +2001,9 @@ public abstract class AbstractCumulativeLoanScheduleGenerator implements LoanSch
             final LoanScheduleParams loanScheduleParams, final BigDecimal chargesDueAtTimeOfDisbursement) {
         List<LoanScheduleModelPeriod> periods = new ArrayList<>();
         if (!loanApplicationTerms.isMultiDisburseLoan()) {
-            final LoanScheduleModelDisbursementPeriod disbursementPeriod = LoanScheduleModelDisbursementPeriod
-                    .disbursement(loanApplicationTerms, chargesDueAtTimeOfDisbursement);
+            final LoanScheduleModelDisbursementPeriod disbursementPeriod = LoanScheduleModelDisbursementPeriod.disbursement(
+                    loanApplicationTerms.getExpectedDisbursementDate(), loanApplicationTerms.getPrincipal(),
+                    chargesDueAtTimeOfDisbursement);
             periods.add(disbursementPeriod);
             if (loanApplicationTerms.isDownPaymentEnabled()) {
                 final LoanScheduleModelDownPaymentPeriod downPaymentPeriod = createDownPaymentPeriod(loanApplicationTerms,
diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/LoanScheduleModelDisbursementPeriod.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/LoanScheduleModelDisbursementPeriod.java
index 3f9894ea9..e5e31e91d 100644
--- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/LoanScheduleModelDisbursementPeriod.java
+++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/LoanScheduleModelDisbursementPeriod.java
@@ -37,13 +37,6 @@ public final class LoanScheduleModelDisbursementPeriod implements LoanScheduleMo
     private final BigDecimal chargesDueAtTimeOfDisbursement;
     private boolean isEMIFixedSpecificToInstallment = false;
 
-    public static LoanScheduleModelDisbursementPeriod disbursement(final LoanApplicationTerms loanApplicationTerms,
-            final BigDecimal chargesDueAtTimeOfDisbursement) {
-
-        return new LoanScheduleModelDisbursementPeriod(null, loanApplicationTerms.getExpectedDisbursementDate(),
-                loanApplicationTerms.getPrincipal(), chargesDueAtTimeOfDisbursement);
-    }
-
     public static LoanScheduleModelDisbursementPeriod disbursement(final LocalDate disbursementDate, final Money principalDisbursed,
             final BigDecimal chargesDueAtTimeOfDisbursement) {
         return new LoanScheduleModelDisbursementPeriod(null, disbursementDate, principalDisbursed, chargesDueAtTimeOfDisbursement);
diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/LoanScheduleModelRepaymentPeriod.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/LoanScheduleModelRepaymentPeriod.java
index a3db61f20..fd848751e 100644
--- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/LoanScheduleModelRepaymentPeriod.java
+++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/LoanScheduleModelRepaymentPeriod.java
@@ -33,11 +33,11 @@ import org.apache.fineract.portfolio.loanaccount.loanschedule.data.LoanScheduleP
 @Getter
 public final class LoanScheduleModelRepaymentPeriod implements LoanScheduleModelPeriod {
 
-    private final int periodNumber;
+    private int periodNumber;
     private final LocalDate fromDate;
     private final LocalDate dueDate;
     private Money principalDue;
-    private final Money outstandingLoanBalance;
+    private Money outstandingLoanBalance;
     private Money interestDue;
     private Money feeChargesDue;
     private Money penaltyChargesDue;
@@ -166,6 +166,14 @@ public final class LoanScheduleModelRepaymentPeriod implements LoanScheduleModel
         this.totalDue = this.totalDue.plus(interestDue);
     }
 
+    public void setPeriodNumber(int periodNumber) {
+        this.periodNumber = periodNumber;
+    }
+
+    public void setOutstandingLoanBalance(Money outstandingLoanBalance) {
+        this.outstandingLoanBalance = outstandingLoanBalance;
+    }
+
     @Override
     public Set<LoanInterestRecalcualtionAdditionalDetails> getLoanCompoundingDetails() {
         return this.loanCompoundingDetails;
diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/PreGeneratedLoanSchedulePeriod.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/PreGeneratedLoanSchedulePeriod.java
deleted file mode 100644
index 636cf0046..000000000
--- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/PreGeneratedLoanSchedulePeriod.java
+++ /dev/null
@@ -1,96 +0,0 @@
-/**
- * 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.portfolio.loanaccount.loanschedule.domain;
-
-import java.math.BigDecimal;
-import java.time.LocalDate;
-import java.util.Set;
-import lombok.Data;
-import lombok.experimental.Accessors;
-import org.apache.fineract.organisation.monetary.domain.Money;
-import org.apache.fineract.portfolio.loanaccount.domain.LoanInterestRecalcualtionAdditionalDetails;
-import org.apache.fineract.portfolio.loanaccount.loanschedule.data.LoanSchedulePeriodData;
-
-@Data
-@Accessors(fluent = true)
-public class PreGeneratedLoanSchedulePeriod implements LoanScheduleModelPeriod {
-
-    private Integer periodNumber;
-    private boolean isRepaymentPeriod;
-    private boolean isDownPaymentPeriod;
-    private LocalDate periodFromDate;
-    private LocalDate periodDueDate;
-    private BigDecimal principalDue;
-    private BigDecimal interestDue;
-    private BigDecimal feeChargesDue;
-    private BigDecimal penaltyChargesDue;
-    private BigDecimal rescheduleInterestPortion;
-    private boolean isRecalculatedInterestComponent;
-    private boolean isEMIFixedSpecificToInstallment;
-
-    public PreGeneratedLoanSchedulePeriod(Integer periodNumber, LocalDate periodFromDate, LocalDate periodDueDate) {
-        this.periodNumber = periodNumber;
-        this.isRepaymentPeriod = true;
-        this.isDownPaymentPeriod = false;
-        this.periodFromDate = periodFromDate;
-        this.periodDueDate = periodDueDate;
-        this.principalDue = BigDecimal.ZERO;
-        this.interestDue = BigDecimal.ZERO;
-        this.feeChargesDue = BigDecimal.ZERO;
-        this.penaltyChargesDue = BigDecimal.ZERO;
-        this.rescheduleInterestPortion = BigDecimal.ZERO;
-        this.isRecalculatedInterestComponent = false;
-        this.isEMIFixedSpecificToInstallment = false;
-    }
-
-    @Override
-    public LoanSchedulePeriodData toData() {
-        return null;
-    }
-
-    @Override
-    public void addLoanCharges(BigDecimal feeCharge, BigDecimal penaltyCharge) {
-        throw new UnsupportedOperationException();
-    }
-
-    @Override
-    public void addPrincipalAmount(Money principalDue) {
-        this.principalDue = this.principalDue.add(principalDue.getAmount());
-    }
-
-    @Override
-    public void addInterestAmount(Money interestDue) {
-        this.interestDue = this.principalDue.add(interestDue.getAmount());
-    }
-
-    @Override
-    public Set<LoanInterestRecalcualtionAdditionalDetails> getLoanCompoundingDetails() {
-        return Set.of();
-    }
-
-    @Override
-    public void setEMIFixedSpecificToInstallmentTrue() {
-        throw new UnsupportedOperationException();
-    }
-
-    @Override
-    public void setRescheduleInterestPortion(BigDecimal rescheduleInterestPortion) {
-        throw new UnsupportedOperationException();
-    }
-}
diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/ScheduledDateGenerator.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/ScheduledDateGenerator.java
index 88271f95c..fd5e824e8 100644
--- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/ScheduledDateGenerator.java
+++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/ScheduledDateGenerator.java
@@ -27,7 +27,7 @@ import org.apache.fineract.portfolio.loanaccount.data.HolidayDetailDTO;
 
 public interface ScheduledDateGenerator {
 
-    List<PreGeneratedLoanSchedulePeriod> generateRepaymentPeriods(LocalDate scheduledDueDate, LoanApplicationTerms loanApplicationTerms,
+    List<LoanScheduleModelRepaymentPeriod> generateRepaymentPeriods(LocalDate scheduledDueDate, LoanApplicationTerms loanApplicationTerms,
             HolidayDetailDTO holidayDetailDTO);
 
     LocalDate getLastRepaymentDate(LoanApplicationTerms loanApplicationTerms, HolidayDetailDTO holidayDetailDTO);
diff --git a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/AdvancedPaymentScheduleTransactionProcessor.java b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/AdvancedPaymentScheduleTransactionProcessor.java
index 0dfbfb7a8..a9b5773b9 100644
--- a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/AdvancedPaymentScheduleTransactionProcessor.java
+++ b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/AdvancedPaymentScheduleTransactionProcessor.java
@@ -74,10 +74,12 @@ import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRelationT
 import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionToRepaymentScheduleMapping;
 import org.apache.fineract.portfolio.loanaccount.domain.SingleLoanChargeRepaymentScheduleProcessingWrapper;
 import org.apache.fineract.portfolio.loanaccount.domain.reaging.LoanReAgeParameter;
-import org.apache.fineract.portfolio.loanaccount.domain.reaging.LoanReAgingParameterRepository;
 import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.AbstractLoanRepaymentScheduleTransactionProcessor;
 import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.MoneyHolder;
+import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.TransactionCtx;
+import org.apache.fineract.portfolio.loanaccount.loanschedule.data.ProgressiveLoanInterestScheduleModel;
 import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleProcessingType;
+import org.apache.fineract.portfolio.loanproduct.calc.EMICalculator;
 import org.apache.fineract.portfolio.loanproduct.domain.AllocationType;
 import org.apache.fineract.portfolio.loanproduct.domain.CreditAllocationTransactionType;
 import org.apache.fineract.portfolio.loanproduct.domain.DueType;
@@ -95,7 +97,7 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
 
     public final SingleLoanChargeRepaymentScheduleProcessingWrapper loanChargeProcessor = new SingleLoanChargeRepaymentScheduleProcessingWrapper();
 
-    private final LoanReAgingParameterRepository reAgingParameterRepository;
+    public final EMICalculator emiCalculator;
 
     @Override
     public String getCode() {
@@ -144,6 +146,10 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
     @Override
     public ChangedTransactionDetail reprocessLoanTransactions(LocalDate disbursementDate, List<LoanTransaction> loanTransactions,
             MonetaryCurrency currency, List<LoanRepaymentScheduleInstallment> installments, Set<LoanCharge> charges) {
+        final ChangedTransactionDetail changedTransactionDetail = new ChangedTransactionDetail();
+        if (loanTransactions.isEmpty()) {
+            return changedTransactionDetail;
+        }
         if (charges != null) {
             for (final LoanCharge loanCharge : charges) {
                 if (!loanCharge.isDueAtDisbursement()) {
@@ -160,16 +166,22 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
 
         for (final LoanRepaymentScheduleInstallment currentInstallment : installments) {
             currentInstallment.resetBalances();
-            currentInstallment.updateDerivedFields(currency, disbursementDate);
+            currentInstallment.updateObligationsMet(currency, disbursementDate);
         }
 
         List<ChargeOrTransaction> chargeOrTransactions = createSortedChargesAndTransactionsList(loanTransactions, charges);
 
-        final ChangedTransactionDetail changedTransactionDetail = new ChangedTransactionDetail();
         MoneyHolder overpaymentHolder = new MoneyHolder(Money.zero(currency));
+        final MathContext mc = MoneyHelper.getMathContext();
+        final Loan loan = loanTransactions.get(0).getLoan();
+        final Integer installmentAmountInMultiplesOf = loan.getLoanProduct().getInstallmentAmountInMultiplesOf();
+        final LoanProductRelatedDetail loanProductRelatedDetail = loan.getLoanRepaymentScheduleDetail();
+        ProgressiveLoanInterestScheduleModel scheduleModel = emiCalculator.generateModel(loanProductRelatedDetail,
+                installmentAmountInMultiplesOf, installments, mc);
+
         for (final ChargeOrTransaction chargeOrTransaction : chargeOrTransactions) {
             chargeOrTransaction.getLoanTransaction().ifPresent(loanTransaction -> processSingleTransaction(loanTransaction, currency,
-                    installments, charges, changedTransactionDetail, overpaymentHolder));
+                    installments, charges, changedTransactionDetail, overpaymentHolder, scheduleModel));
             chargeOrTransaction.getLoanCharge()
                     .ifPresent(loanCharge -> processSingleCharge(loanCharge, currency, installments, disbursementDate));
         }
@@ -182,7 +194,7 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
     @Override
     public void processLatestTransaction(LoanTransaction loanTransaction, TransactionCtx ctx) {
         switch (loanTransaction.getTypeOf()) {
-            case DISBURSEMENT -> handleDisbursement(loanTransaction, ctx.getCurrency(), ctx.getInstallments(), ctx.getOverpaymentHolder());
+            case DISBURSEMENT -> handleDisbursement(loanTransaction, ctx);
             case WRITEOFF -> handleWriteOff(loanTransaction, ctx.getCurrency(), ctx.getInstallments());
             case REFUND_FOR_ACTIVE_LOAN -> handleRefund(loanTransaction, ctx.getCurrency(), ctx.getInstallments(), ctx.getCharges());
             case CHARGEBACK -> handleChargeback(loanTransaction, ctx);
@@ -190,12 +202,11 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
                 handleCreditBalanceRefund(loanTransaction, ctx.getCurrency(), ctx.getInstallments(), ctx.getOverpaymentHolder());
             case REPAYMENT, MERCHANT_ISSUED_REFUND, PAYOUT_REFUND, GOODWILL_CREDIT, CHARGE_REFUND, CHARGE_ADJUSTMENT, DOWN_PAYMENT,
                     WAIVE_INTEREST, RECOVERY_REPAYMENT, INTEREST_PAYMENT_WAIVER ->
-                handleRepayment(loanTransaction, ctx.getCurrency(), ctx.getInstallments(), ctx.getCharges(), ctx.getOverpaymentHolder());
-            case CHARGE_OFF -> handleChargeOff(loanTransaction, ctx.getCurrency(), ctx.getInstallments());
-            case CHARGE_PAYMENT -> handleChargePayment(loanTransaction, ctx.getCurrency(), ctx.getInstallments(), ctx.getCharges(),
-                    ctx.getOverpaymentHolder());
+                handleRepayment(loanTransaction, ctx);
+            case CHARGE_OFF -> handleChargeOff(loanTransaction, ctx);
+            case CHARGE_PAYMENT -> handleChargePayment(loanTransaction, ctx);
             case WAIVE_CHARGES -> log.debug("WAIVE_CHARGES transaction will not be processed.");
-            case REAMORTIZE -> handleReAmortization(loanTransaction, ctx.getCurrency(), ctx.getInstallments());
+            case REAMORTIZE -> handleReAmortization(loanTransaction, ctx);
             case REAGE -> handleReAge(loanTransaction, ctx);
             case ACCRUAL_ACTIVITY -> calculateAccrualActivity(loanTransaction, ctx.getCurrency(), ctx.getInstallments());
             // TODO: Cover rest of the transaction types
@@ -205,28 +216,29 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
         }
     }
 
-    private void handleReAmortization(LoanTransaction loanTransaction, MonetaryCurrency currency,
-            List<LoanRepaymentScheduleInstallment> installments) {
+    private void handleReAmortization(LoanTransaction loanTransaction, TransactionCtx transactionCtx) {
         LocalDate transactionDate = loanTransaction.getTransactionDate();
-        List<LoanRepaymentScheduleInstallment> previousInstallments = installments.stream() //
+        List<LoanRepaymentScheduleInstallment> previousInstallments = transactionCtx.getInstallments().stream() //
                 .filter(installment -> !installment.getDueDate().isAfter(transactionDate)) //
                 .toList();
-        List<LoanRepaymentScheduleInstallment> futureInstallments = installments.stream() //
+        List<LoanRepaymentScheduleInstallment> futureInstallments = transactionCtx.getInstallments().stream() //
                 .filter(installment -> installment.getDueDate().isAfter(transactionDate)) //
                 .filter(installment -> !installment.isAdditional() && !installment.isDownPayment() && !installment.isReAged()) //
                 .toList();
 
         BigDecimal overallOverDuePrincipal = ZERO;
         for (LoanRepaymentScheduleInstallment installment : previousInstallments) {
-            Money principalCompleted = installment.getPrincipalCompleted(currency);
-            overallOverDuePrincipal = overallOverDuePrincipal.add(installment.getPrincipal(currency).minus(principalCompleted).getAmount());
-            installment.updatePrincipal(installment.getPrincipalCompleted(currency).getAmount());
-            installment.updateDerivedFields(currency, transactionDate);
+            Money principalCompleted = installment.getPrincipalCompleted(transactionCtx.getCurrency());
+            overallOverDuePrincipal = overallOverDuePrincipal
+                    .add(installment.getPrincipal(transactionCtx.getCurrency()).minus(principalCompleted).getAmount());
+            installment.updatePrincipal(installment.getPrincipalCompleted(transactionCtx.getCurrency()).getAmount());
+            installment.updateObligationsMet(transactionCtx.getCurrency(), transactionDate);
         }
 
         loanTransaction.resetDerivedComponents();
-        loanTransaction.updateComponentsAndTotal(Money.of(currency, overallOverDuePrincipal), Money.zero(currency), Money.zero(currency),
-                Money.zero(currency));
+        loanTransaction.updateComponentsAndTotal(Money.of(transactionCtx.getCurrency(), overallOverDuePrincipal),
+                Money.zero(transactionCtx.getCurrency()), Money.zero(transactionCtx.getCurrency()),
+                Money.zero(transactionCtx.getCurrency()));
 
         LoanRepaymentScheduleInstallment lastFutureInstallment = futureInstallments.stream()
                 .max(Comparator.comparing(LoanRepaymentScheduleInstallment::getDueDate)).get();
@@ -236,13 +248,13 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
 
         for (LoanRepaymentScheduleInstallment installment : futureInstallments) {
             if (lastFutureInstallment.equals(installment)) {
-                installment.addToPrincipal(transactionDate, Money.of(currency, overallOverDuePrincipal));
+                installment.addToPrincipal(transactionDate, Money.of(transactionCtx.getCurrency(), overallOverDuePrincipal));
             } else {
                 if (installmentAmountInMultiplesOf != null) {
                     reAmortizationAmountPerInstallment = Money.roundToMultiplesOf(reAmortizationAmountPerInstallment,
                             installmentAmountInMultiplesOf);
                 }
-                installment.addToPrincipal(transactionDate, Money.of(currency, reAmortizationAmountPerInstallment));
+                installment.addToPrincipal(transactionDate, Money.of(transactionCtx.getCurrency(), reAmortizationAmountPerInstallment));
                 overallOverDuePrincipal = overallOverDuePrincipal.subtract(reAmortizationAmountPerInstallment);
             }
         }
@@ -361,8 +373,7 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
                             loan.addLoanRepaymentScheduleInstallment(installment);
                         }
                     }
-                    allocateOverpayment(loanTransaction, ctx.getCurrency(), loanTransaction.getLoan().getRepaymentScheduleInstallments(),
-                            ctx.getOverpaymentHolder());
+                    allocateOverpayment(loanTransaction, ctx);
                 } else {
                     throw new RuntimeException("Unsupported transaction " + loanTransaction.getTypeOf().name());
                 }
@@ -544,8 +555,9 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
 
     private void processSingleTransaction(LoanTransaction loanTransaction, MonetaryCurrency currency,
             List<LoanRepaymentScheduleInstallment> installments, Set<LoanCharge> charges, ChangedTransactionDetail changedTransactionDetail,
-            MoneyHolder overpaymentHolder) {
-        TransactionCtx ctx = new TransactionCtx(currency, installments, charges, overpaymentHolder, changedTransactionDetail);
+            MoneyHolder overpaymentHolder, ProgressiveLoanInterestScheduleModel scheduleModel) {
+        TransactionCtx ctx = new ProgressiveTransactionCtx(currency, installments, charges, overpaymentHolder, changedTransactionDetail,
+                scheduleModel);
         if (loanTransaction.getId() == null) {
             processLatestTransaction(loanTransaction, ctx);
             if (loanTransaction.isInterestWaiver()) {
@@ -613,63 +625,113 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
         return chargeOrTransactions;
     }
 
-    private void handleDisbursement(LoanTransaction loanTransaction, MonetaryCurrency currency,
-            List<LoanRepaymentScheduleInstallment> installments, MoneyHolder overpaymentHolder) {
-        updateLoanSchedule(loanTransaction, currency, installments, overpaymentHolder);
+    private void handleDisbursementWithEMICalculator(LoanTransaction disbursementTransaction, TransactionCtx transactionCtx) {
+        ProgressiveTransactionCtx progressiveTransactionCtx = (ProgressiveTransactionCtx) transactionCtx;
+        if (progressiveTransactionCtx.getModel() == null) {
+            throw new IllegalStateException("TransactionCtx has no model");
+        }
+        disbursementTransaction.resetDerivedComponents();
+        final MathContext mc = MoneyHelper.getMathContext();
+        LoanProductRelatedDetail loanProductRelatedDetail = disbursementTransaction.getLoan().getLoanRepaymentScheduleDetail();
+        Integer installmentAmountInMultiplesOf = disbursementTransaction.getLoan().getLoanProduct().getInstallmentAmountInMultiplesOf();
+        Money downPaymentAmount = Money.zero(progressiveTransactionCtx.getCurrency());
+        if (loanProductRelatedDetail.isEnableDownPayment()) {
+            LoanRepaymentScheduleInstallment downPaymentInstallment = progressiveTransactionCtx.getInstallments().stream()
+                    .filter(i -> i.isDownPayment() && i.getPrincipal(progressiveTransactionCtx.getCurrency()).isZero()).findFirst()
+                    .orElseThrow();
+            BigDecimal downPaymentAmt = MathUtil.percentageOf(disbursementTransaction.getAmount(),
+                    loanProductRelatedDetail.getDisbursedAmountPercentageForDownPayment(), mc);
+            if (installmentAmountInMultiplesOf != null) {
+                downPaymentAmt = Money.roundToMultiplesOf(downPaymentAmt, installmentAmountInMultiplesOf);
+            }
+            downPaymentAmount = Money.of(transactionCtx.getCurrency(), downPaymentAmt);
+            downPaymentInstallment.addToPrincipal(disbursementTransaction.getTransactionDate(), downPaymentAmount);
+        }
+
+        Money amortizableAmount = disbursementTransaction.getAmount(transactionCtx.getCurrency()).minus(downPaymentAmount);
+
+        emiCalculator.addDisbursement(progressiveTransactionCtx.getModel(), disbursementTransaction.getTransactionDate(),
+                amortizableAmount);
+
+        if (amortizableAmount.isGreaterThanZero()) {
+            progressiveTransactionCtx.getModel().repayments().forEach(rm -> {
+                LoanRepaymentScheduleInstallment installment = transactionCtx.getInstallments().stream()
+                        .filter(ri -> ri.getDueDate().equals(rm.getDueDate()) && !ri.isDownPayment()
+                                && !ri.getDueDate().isBefore(disbursementTransaction.getTransactionDate()))
+                        .findFirst().orElse(null);
+                if (installment != null) {
+                    installment.updatePrincipal(rm.getPrincipalDue().getAmount());
+                    installment.updateInterestCharged(rm.getInterestDue().getAmount());
+                    installment.updateObligationsMet(progressiveTransactionCtx.getCurrency(), disbursementTransaction.getTransactionDate());
+                }
+            });
+        }
+
+        allocateOverpayment(disbursementTransaction, transactionCtx);
+    }
+
+    private void handleDisbursement(LoanTransaction disbursementTransaction, TransactionCtx transactionCtx) {
+        // TODO: Fix this and enhance EMICalculator to support reamortization and reaging
+        if (disbursementTransaction.getLoan().isInterestBearing()) {
+            handleDisbursementWithEMICalculator(disbursementTransaction, transactionCtx);
+        } else {
+            handleDisbursementWithoutEMICalculator(disbursementTransaction, transactionCtx);
+        }
     }
 
-    private void updateLoanSchedule(LoanTransaction disbursementTransaction, MonetaryCurrency currency,
-            List<LoanRepaymentScheduleInstallment> installments, MoneyHolder overpaymentHolder) {
+    private void handleDisbursementWithoutEMICalculator(LoanTransaction disbursementTransaction, TransactionCtx transactionCtx) {
         disbursementTransaction.resetDerivedComponents();
         final MathContext mc = MoneyHelper.getMathContext();
-        List<LoanRepaymentScheduleInstallment> candidateRepaymentInstallments = installments.stream().filter(
+        List<LoanRepaymentScheduleInstallment> candidateRepaymentInstallments = transactionCtx.getInstallments().stream().filter(
                 i -> i.getDueDate().isAfter(disbursementTransaction.getTransactionDate()) && !i.isDownPayment() && !i.isAdditional())
                 .toList();
         int noCandidateRepaymentInstallments = candidateRepaymentInstallments.size();
         LoanProductRelatedDetail loanProductRelatedDetail = disbursementTransaction.getLoan().getLoanRepaymentScheduleDetail();
         Integer installmentAmountInMultiplesOf = disbursementTransaction.getLoan().getLoanProduct().getInstallmentAmountInMultiplesOf();
-        Money downPaymentAmount = Money.zero(currency);
+        Money downPaymentAmount = Money.zero(transactionCtx.getCurrency());
         if (loanProductRelatedDetail.isEnableDownPayment()) {
-            LoanRepaymentScheduleInstallment downPaymentInstallment = installments.stream()
-                    .filter(i -> i.isDownPayment() && i.getPrincipal(currency).isZero()).findFirst().orElseThrow();
+            LoanRepaymentScheduleInstallment downPaymentInstallment = transactionCtx.getInstallments().stream()
+                    .filter(i -> i.isDownPayment() && i.getPrincipal(transactionCtx.getCurrency()).isZero()).findFirst().orElseThrow();
             BigDecimal downPaymentAmt = MathUtil.percentageOf(disbursementTransaction.getAmount(),
                     loanProductRelatedDetail.getDisbursedAmountPercentageForDownPayment(), mc);
             if (installmentAmountInMultiplesOf != null) {
                 downPaymentAmt = Money.roundToMultiplesOf(downPaymentAmt, installmentAmountInMultiplesOf);
             }
-            downPaymentAmount = Money.of(currency, downPaymentAmt);
+            downPaymentAmount = Money.of(transactionCtx.getCurrency(), downPaymentAmt);
             downPaymentInstallment.addToPrincipal(disbursementTransaction.getTransactionDate(), downPaymentAmount);
         }
 
-        Money amortizableAmount = disbursementTransaction.getAmount(currency).minus(downPaymentAmount);
+        Money amortizableAmount = disbursementTransaction.getAmount(transactionCtx.getCurrency()).minus(downPaymentAmount);
 
         if (amortizableAmount.isGreaterThanZero()) {
             Money increasePrincipalBy = amortizableAmount.dividedBy(noCandidateRepaymentInstallments, mc.getRoundingMode());
-            if (installmentAmountInMultiplesOf != null) {
-                increasePrincipalBy = Money.roundToMultiplesOf(increasePrincipalBy, installmentAmountInMultiplesOf);
-            }
-
-            Money remainingAmount = amortizableAmount
-                    .minus(increasePrincipalBy.multiplyRetainScale(noCandidateRepaymentInstallments, mc.getRoundingMode()));
+            MoneyHolder moneyHolder = new MoneyHolder(amortizableAmount);
 
-            Money finalIncreasePrincipalBy = increasePrincipalBy;
-            candidateRepaymentInstallments
-                    .forEach(i -> i.addToPrincipal(disbursementTransaction.getTransactionDate(), finalIncreasePrincipalBy));
+            candidateRepaymentInstallments.forEach(i -> {
+                Money previousPrincipal = i.getPrincipal(transactionCtx.getCurrency());
+                Money newPrincipal = previousPrincipal.add(increasePrincipalBy);
+                if (installmentAmountInMultiplesOf != null) {
+                    newPrincipal = Money.roundToMultiplesOf(newPrincipal, installmentAmountInMultiplesOf);
+                }
+                i.updatePrincipal(newPrincipal.getAmount());
+                moneyHolder.setMoneyObject(moneyHolder.getMoneyObject().minus(newPrincipal).plus(previousPrincipal));
+                i.updateObligationsMet(transactionCtx.getCurrency(), disbursementTransaction.getTransactionDate());
+            });
             // Hence the rounding, we might need to amend the last installment amount
             candidateRepaymentInstallments.get(noCandidateRepaymentInstallments - 1)
-                    .addToPrincipal(disbursementTransaction.getTransactionDate(), remainingAmount);
+                    .addToPrincipal(disbursementTransaction.getTransactionDate(), moneyHolder.getMoneyObject());
         }
 
-        allocateOverpayment(disbursementTransaction, currency, installments, overpaymentHolder);
+        allocateOverpayment(disbursementTransaction, transactionCtx);
     }
 
-    private void allocateOverpayment(LoanTransaction loanTransaction, MonetaryCurrency currency,
-            List<LoanRepaymentScheduleInstallment> installments, MoneyHolder overpaymentHolder) {
-        if (overpaymentHolder.getMoneyObject().isGreaterThanZero()) {
-            if (overpaymentHolder.getMoneyObject().isGreaterThan(loanTransaction.getAmount(currency))) {
-                loanTransaction.setOverPayments(loanTransaction.getAmount(currency));
+    private void allocateOverpayment(LoanTransaction loanTransaction, TransactionCtx transactionCtx) {
+        if (transactionCtx.getOverpaymentHolder().getMoneyObject().isGreaterThanZero()) {
+            if (transactionCtx.getOverpaymentHolder().getMoneyObject()
+                    .isGreaterThan(loanTransaction.getAmount(transactionCtx.getCurrency()))) {
+                loanTransaction.setOverPayments(loanTransaction.getAmount(transactionCtx.getCurrency()));
             } else {
-                loanTransaction.setOverPayments(overpaymentHolder.getMoneyObject());
+                loanTransaction.setOverPayments(transactionCtx.getOverpaymentHolder().getMoneyObject());
             }
             List<LoanTransactionToRepaymentScheduleMapping> transactionMappings = new ArrayList<>();
             List<LoanPaymentAllocationRule> paymentAllocationRules = loanTransaction.getLoan().getPaymentAllocationRules();
@@ -677,29 +739,30 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
                     .filter(e -> DEFAULT.equals(e.getTransactionType())).findFirst().orElseThrow();
 
             Money transactionAmountUnprocessed = null;
-            Money zero = Money.zero(currency);
+            Money zero = Money.zero(transactionCtx.getCurrency());
             Balances balances = new Balances(zero, zero, zero, zero);
             if (LoanScheduleProcessingType.HORIZONTAL
                     .equals(loanTransaction.getLoan().getLoanProductRelatedDetail().getLoanScheduleProcessingType())) {
-                transactionAmountUnprocessed = processPeriodsHorizontally(loanTransaction, currency, installments,
-                        overpaymentHolder.getMoneyObject(), defaultPaymentAllocationRule, transactionMappings, Set.of(), balances);
+                transactionAmountUnprocessed = processPeriodsHorizontally(loanTransaction, transactionCtx.getCurrency(),
+                        transactionCtx.getInstallments(), transactionCtx.getOverpaymentHolder().getMoneyObject(),
+                        defaultPaymentAllocationRule, transactionMappings, Set.of(), balances);
             } else if (LoanScheduleProcessingType.VERTICAL
                     .equals(loanTransaction.getLoan().getLoanProductRelatedDetail().getLoanScheduleProcessingType())) {
-                transactionAmountUnprocessed = processPeriodsVertically(loanTransaction, currency, installments,
-                        overpaymentHolder.getMoneyObject(), defaultPaymentAllocationRule, transactionMappings, Set.of(), balances);
+                transactionAmountUnprocessed = processPeriodsVertically(loanTransaction, transactionCtx.getCurrency(),
+                        transactionCtx.getInstallments(), transactionCtx.getOverpaymentHolder().getMoneyObject(),
+                        defaultPaymentAllocationRule, transactionMappings, Set.of(), balances);
             }
-            overpaymentHolder.setMoneyObject(transactionAmountUnprocessed);
+            transactionCtx.getOverpaymentHolder().setMoneyObject(transactionAmountUnprocessed);
             loanTransaction.updateLoanTransactionToRepaymentScheduleMappings(transactionMappings);
         }
     }
 
-    private void handleRepayment(LoanTransaction loanTransaction, MonetaryCurrency currency,
-            List<LoanRepaymentScheduleInstallment> installments, Set<LoanCharge> charges, MoneyHolder overpaymentHolder) {
+    private void handleRepayment(LoanTransaction loanTransaction, TransactionCtx transactionCtx) {
         if (loanTransaction.isRepaymentLikeType() || loanTransaction.isInterestWaiver() || loanTransaction.isRecoveryRepayment()) {
             loanTransaction.resetDerivedComponents();
         }
-        Money transactionAmountUnprocessed = loanTransaction.getAmount(currency);
-        processTransaction(loanTransaction, currency, installments, transactionAmountUnprocessed, charges, overpaymentHolder);
+        Money transactionAmountUnprocessed = loanTransaction.getAmount(transactionCtx.getCurrency());
+        processTransaction(loanTransaction, transactionCtx, transactionAmountUnprocessed);
     }
 
     private LoanTransactionToRepaymentScheduleMapping getTransactionMapping(
@@ -781,44 +844,44 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
         }
     }
 
-    private void handleChargeOff(LoanTransaction loanTransaction, MonetaryCurrency currency,
-            List<LoanRepaymentScheduleInstallment> installments) {
+    private void handleChargeOff(LoanTransaction loanTransaction, TransactionCtx transactionCtx) {
         loanTransaction.resetDerivedComponents();
         // determine how much is outstanding total and breakdown for principal, interest and charges
-        Money principalPortion = Money.zero(currency);
-        Money interestPortion = Money.zero(currency);
-        Money feeChargesPortion = Money.zero(currency);
-        Money penaltychargesPortion = Money.zero(currency);
-        for (final LoanRepaymentScheduleInstallment currentInstallment : installments) {
+        Money principalPortion = Money.zero(transactionCtx.getCurrency());
+        Money interestPortion = Money.zero(transactionCtx.getCurrency());
+        Money feeChargesPortion = Money.zero(transactionCtx.getCurrency());
+        Money penaltychargesPortion = Money.zero(transactionCtx.getCurrency());
+        for (final LoanRepaymentScheduleInstallment currentInstallment : transactionCtx.getInstallments()) {
             if (currentInstallment.isNotFullyPaidOff()) {
-                principalPortion = principalPortion.plus(currentInstallment.getPrincipalOutstanding(currency));
-                interestPortion = interestPortion.plus(currentInstallment.getInterestOutstanding(currency));
-                feeChargesPortion = feeChargesPortion.plus(currentInstallment.getFeeChargesOutstanding(currency));
-                penaltychargesPortion = penaltychargesPortion.plus(currentInstallment.getPenaltyChargesOutstanding(currency));
+                principalPortion = principalPortion.plus(currentInstallment.getPrincipalOutstanding(transactionCtx.getCurrency()));
+                interestPortion = interestPortion.plus(currentInstallment.getInterestOutstanding(transactionCtx.getCurrency()));
+                feeChargesPortion = feeChargesPortion.plus(currentInstallment.getFeeChargesOutstanding(transactionCtx.getCurrency()));
+                penaltychargesPortion = penaltychargesPortion
+                        .plus(currentInstallment.getPenaltyChargesOutstanding(transactionCtx.getCurrency()));
             }
         }
 
         loanTransaction.updateComponentsAndTotal(principalPortion, interestPortion, feeChargesPortion, penaltychargesPortion);
     }
 
-    private void handleChargePayment(LoanTransaction loanTransaction, MonetaryCurrency currency,
-            List<LoanRepaymentScheduleInstallment> installments, Set<LoanCharge> charges, MoneyHolder overpaymentHolder) {
-        Money zero = Money.zero(currency);
+    private void handleChargePayment(LoanTransaction loanTransaction, TransactionCtx transactionCtx) {
+        Money zero = Money.zero(transactionCtx.getCurrency());
         Money feeChargesPortion = zero;
         Money penaltyChargesPortion = zero;
         List<LoanTransactionToRepaymentScheduleMapping> transactionMappings = new ArrayList<>();
         LoanChargePaidBy loanChargePaidBy = loanTransaction.getLoanChargesPaid().stream().findFirst().get();
         LoanCharge loanCharge = loanChargePaidBy.getLoanCharge();
-        Money amountToBePaid = Money.of(currency, loanTransaction.getAmount());
-        if (loanCharge.getAmountOutstanding(currency).isLessThan(amountToBePaid)) {
-            amountToBePaid = loanCharge.getAmountOutstanding(currency);
+        Money amountToBePaid = Money.of(transactionCtx.getCurrency(), loanTransaction.getAmount());
+        if (loanCharge.getAmountOutstanding(transactionCtx.getCurrency()).isLessThan(amountToBePaid)) {
+            amountToBePaid = loanCharge.getAmountOutstanding(transactionCtx.getCurrency());
         }
 
         LocalDate startDate = loanTransaction.getLoan().getDisbursementDate();
 
-        Money unprocessed = loanTransaction.getAmount(currency);
-        int firstNormalInstallmentNumber = LoanRepaymentScheduleProcessingWrapper.fetchFirstNormalInstallmentNumber(installments);
-        for (final LoanRepaymentScheduleInstallment installment : installments) {
+        Money unprocessed = loanTransaction.getAmount(transactionCtx.getCurrency());
+        int firstNormalInstallmentNumber = LoanRepaymentScheduleProcessingWrapper
+                .fetchFirstNormalInstallmentNumber(transactionCtx.getInstallments());
+        for (final LoanRepaymentScheduleInstallment installment : transactionCtx.getInstallments()) {
             boolean isDue = installment.getInstallmentNumber().equals(firstNormalInstallmentNumber)
                     ? loanCharge.isDueForCollectionFromIncludingAndUpToAndIncluding(startDate, installment.getDueDate())
                     : loanCharge.isDueForCollectionFromAndUpToAndIncluding(startDate, installment.getDueDate());
@@ -827,7 +890,7 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
                 Money paidAmount = loanCharge.updatePaidAmountBy(amountToBePaid, installmentNumber, zero);
 
                 LoanTransactionToRepaymentScheduleMapping loanTransactionToRepaymentScheduleMapping = getTransactionMapping(
-                        transactionMappings, loanTransaction, installment, currency);
+                        transactionMappings, loanTransaction, installment, transactionCtx.getCurrency());
 
                 if (loanTransaction.isPenaltyPayment()) {
                     penaltyChargesPortion = installment.payPenaltyChargesComponent(loanTransaction.getTransactionDate(), paidAmount);
@@ -842,13 +905,13 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
                 }
 
                 loanTransaction.updateComponents(zero, zero, feeChargesPortion, penaltyChargesPortion);
-                unprocessed = loanTransaction.getAmount(currency).minus(paidAmount);
+                unprocessed = loanTransaction.getAmount(transactionCtx.getCurrency()).minus(paidAmount);
                 loanTransaction.updateLoanTransactionToRepaymentScheduleMappings(transactionMappings);
             }
         }
 
         if (unprocessed.isGreaterThanZero()) {
-            processTransaction(loanTransaction, currency, installments, unprocessed, charges, overpaymentHolder);
+            processTransaction(loanTransaction, transactionCtx, unprocessed);
         }
     }
 
@@ -1032,10 +1095,8 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
         return inAdvanceInstallments;
     }
 
-    private void processTransaction(LoanTransaction loanTransaction, MonetaryCurrency currency,
-            List<LoanRepaymentScheduleInstallment> installments, Money transactionAmountUnprocessed, Set<LoanCharge> charges,
-            MoneyHolder overpaymentHolder) {
-        Money zero = Money.zero(currency);
+    private void processTransaction(LoanTransaction loanTransaction, TransactionCtx transactionCtx, Money transactionAmountUnprocessed) {
+        Money zero = Money.zero(transactionCtx.getCurrency());
         List<LoanTransactionToRepaymentScheduleMapping> transactionMappings = new ArrayList<>();
 
         List<LoanPaymentAllocationRule> paymentAllocationRules = loanTransaction.getLoan().getPaymentAllocationRules();
@@ -1048,18 +1109,20 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
 
         if (LoanScheduleProcessingType.HORIZONTAL
                 .equals(loanTransaction.getLoan().getLoanProductRelatedDetail().getLoanScheduleProcessingType())) {
-            transactionAmountUnprocessed = processPeriodsHorizontally(loanTransaction, currency, installments, transactionAmountUnprocessed,
-                    paymentAllocationRule, transactionMappings, charges, balances);
+            transactionAmountUnprocessed = processPeriodsHorizontally(loanTransaction, transactionCtx.getCurrency(),
+                    transactionCtx.getInstallments(), transactionAmountUnprocessed, paymentAllocationRule, transactionMappings,
+                    transactionCtx.getCharges(), balances);
         } else if (LoanScheduleProcessingType.VERTICAL
                 .equals(loanTransaction.getLoan().getLoanProductRelatedDetail().getLoanScheduleProcessingType())) {
-            transactionAmountUnprocessed = processPeriodsVertically(loanTransaction, currency, installments, transactionAmountUnprocessed,
-                    paymentAllocationRule, transactionMappings, charges, balances);
+            transactionAmountUnprocessed = processPeriodsVertically(loanTransaction, transactionCtx.getCurrency(),
+                    transactionCtx.getInstallments(), transactionAmountUnprocessed, paymentAllocationRule, transactionMappings,
+                    transactionCtx.getCharges(), balances);
         }
         loanTransaction.updateComponents(balances.getAggregatedPrincipalPortion(), balances.getAggregatedInterestPortion(),
                 balances.getAggregatedFeeChargesPortion(), balances.getAggregatedPenaltyChargesPortion());
         loanTransaction.updateLoanTransactionToRepaymentScheduleMappings(transactionMappings);
 
-        handleOverpayment(transactionAmountUnprocessed, loanTransaction, overpaymentHolder);
+        handleOverpayment(transactionAmountUnprocessed, loanTransaction, transactionCtx.getOverpaymentHolder());
     }
 
     private Money processPeriodsHorizontally(LoanTransaction loanTransaction, MonetaryCurrency currency,
@@ -1339,7 +1402,7 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
                 lastNormalInstallment.getLoan(), lastNormalInstallment.getInstallmentNumber() + 1, lastNormalInstallment.getDueDate(),
                 loanTransaction.getLoanReAgeParameter().getStartDate(), calculatedPrincipal.getAmount());
         installments.add(reAgedInstallment);
-        reAgedInstallment.updateDerivedFields(currency, loanTransaction.getTransactionDate());
+        reAgedInstallment.updateObligationsMet(currency, loanTransaction.getTransactionDate());
 
         for (int i = 1; i < loanTransaction.getLoanReAgeParameter().getNumberOfInstallments(); i++) {
             LocalDate calculatedDueDate = calculateReAgedInstallmentDueDate(loanTransaction.getLoanReAgeParameter(),
@@ -1348,7 +1411,7 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
                     reAgedInstallment.getInstallmentNumber() + 1, reAgedInstallment.getDueDate(), calculatedDueDate,
                     calculatedPrincipal.getAmount());
             installments.add(reAgedInstallment);
-            reAgedInstallment.updateDerivedFields(currency, loanTransaction.getTransactionDate());
+            reAgedInstallment.updateObligationsMet(currency, loanTransaction.getTransactionDate());
         }
         reAgedInstallment.addToPrincipal(loanTransaction.getTransactionDate(), adjustCalculatedPrincipal);
         reprocessInstallmentsOrder(installments);
diff --git a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/ProgressiveTransactionCtx.java b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/ProgressiveTransactionCtx.java
new file mode 100644
index 000000000..c1348f004
--- /dev/null
+++ b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/ProgressiveTransactionCtx.java
@@ -0,0 +1,45 @@
+/**
+ * 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.portfolio.loanaccount.domain.transactionprocessor.impl;
+
+import java.util.List;
+import java.util.Set;
+import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
+import org.apache.fineract.portfolio.loanaccount.domain.ChangedTransactionDetail;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanCharge;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment;
+import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.MoneyHolder;
+import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.TransactionCtx;
+import org.apache.fineract.portfolio.loanaccount.loanschedule.data.ProgressiveLoanInterestScheduleModel;
+
+public class ProgressiveTransactionCtx extends TransactionCtx {
+
+    private final ProgressiveLoanInterestScheduleModel model;
+
+    public ProgressiveTransactionCtx(MonetaryCurrency currency, List<LoanRepaymentScheduleInstallment> installments,
+            Set<LoanCharge> charges, MoneyHolder overpaymentHolder, ChangedTransactionDetail changedTransactionDetail,
+            ProgressiveLoanInterestScheduleModel model) {
+        super(currency, installments, charges, overpaymentHolder, changedTransactionDetail);
+        this.model = model;
+    }
+
+    public ProgressiveLoanInterestScheduleModel getModel() {
+        return model;
+    }
+}
diff --git a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/data/ProgressiveLoanInterestRepaymentInterestPeriod.java b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/data/ProgressiveLoanInterestRepaymentInterestPeriod.java
new file mode 100644
index 000000000..2a5a04ecb
--- /dev/null
+++ b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/data/ProgressiveLoanInterestRepaymentInterestPeriod.java
@@ -0,0 +1,52 @@
+/**
+ * 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.portfolio.loanaccount.loanschedule.data;
+
+import java.math.BigDecimal;
+import java.time.LocalDate;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import org.apache.fineract.organisation.monetary.domain.Money;
+import org.jetbrains.annotations.NotNull;
+
+@AllArgsConstructor
+@Data
+public class ProgressiveLoanInterestRepaymentInterestPeriod implements Comparable<ProgressiveLoanInterestRepaymentInterestPeriod> {
+
+    private LocalDate fromDate;
+    private LocalDate dueDate;
+
+    private BigDecimal rateFactorMinus1;
+
+    private Money disbursedAmount;
+    private Money interestDue;
+
+    public ProgressiveLoanInterestRepaymentInterestPeriod(final ProgressiveLoanInterestRepaymentInterestPeriod period) {
+        this(period.fromDate, period.dueDate, period.rateFactorMinus1, period.disbursedAmount, period.interestDue);
+    }
+
+    @Override
+    public int compareTo(@NotNull ProgressiveLoanInterestRepaymentInterestPeriod o) {
+        return dueDate.compareTo(o.dueDate);
+    }
+
+    public void addDisbursedAmount(final Money outstandingBalance) {
+        this.disbursedAmount = this.disbursedAmount.add(outstandingBalance);
+    }
+}
diff --git a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/data/ProgressiveLoanInterestRepaymentModel.java b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/data/ProgressiveLoanInterestRepaymentModel.java
new file mode 100644
index 000000000..f39f808f3
--- /dev/null
+++ b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/data/ProgressiveLoanInterestRepaymentModel.java
@@ -0,0 +1,82 @@
+/**
+ * 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.portfolio.loanaccount.loanschedule.data;
+
+import java.math.BigDecimal;
+import java.time.LocalDate;
+import java.util.LinkedList;
+import lombok.Data;
+import org.apache.fineract.organisation.monetary.domain.Money;
+
+@Data
+public class ProgressiveLoanInterestRepaymentModel {
+
+    private final LocalDate fromDate;
+    private final LocalDate dueDate;
+
+    private LinkedList<ProgressiveLoanInterestRepaymentInterestPeriod> interestPeriods;
+
+    private Money equalMonthlyInstallment;
+    private Money principalDue;
+    private Money remainingBalance;
+
+    public ProgressiveLoanInterestRepaymentModel(final LocalDate fromDate, final LocalDate dueDate, final Money equalMonthlyInstallment) {
+        this.fromDate = fromDate;
+        this.dueDate = dueDate;
+        this.equalMonthlyInstallment = equalMonthlyInstallment;
+
+        final Money zeroAmount = Money.zero(equalMonthlyInstallment.getCurrency());
+        this.remainingBalance = zeroAmount;
+        this.principalDue = zeroAmount;
+        this.interestPeriods = new LinkedList<>();
+        this.interestPeriods
+                .add(new ProgressiveLoanInterestRepaymentInterestPeriod(fromDate, dueDate, BigDecimal.ZERO, zeroAmount, zeroAmount));
+    }
+
+    public ProgressiveLoanInterestRepaymentModel(ProgressiveLoanInterestRepaymentModel repaymentModel) {
+        this.fromDate = repaymentModel.fromDate;
+        this.dueDate = repaymentModel.dueDate;
+        this.equalMonthlyInstallment = repaymentModel.equalMonthlyInstallment;
+        this.remainingBalance = repaymentModel.remainingBalance;
+        this.principalDue = repaymentModel.principalDue;
+        this.interestPeriods = new LinkedList<>();
+        for (final ProgressiveLoanInterestRepaymentInterestPeriod interestPeriod : repaymentModel.interestPeriods) {
+            this.interestPeriods.add(new ProgressiveLoanInterestRepaymentInterestPeriod(interestPeriod));
+        }
+    }
+
+    public BigDecimal getRateFactor() {
+        return interestPeriods.stream().map(ProgressiveLoanInterestRepaymentInterestPeriod::getRateFactorMinus1).reduce(BigDecimal.ONE,
+                BigDecimal::add);
+    }
+
+    public Money getDisbursedAmountInPeriod() {
+        return interestPeriods.stream().map(ProgressiveLoanInterestRepaymentInterestPeriod::getDisbursedAmount)
+                .reduce(Money.zero(equalMonthlyInstallment.getCurrency()), Money::plus);
+    }
+
+    public Money getInterestDue() {
+        return interestPeriods.stream().map(ProgressiveLoanInterestRepaymentInterestPeriod::getInterestDue)
+                .reduce(Money.zero(equalMonthlyInstallment.getCurrency()), Money::plus);
+    }
+
+    public Money getOutstandingBalance() {
+        return remainingBalance.plus(principalDue);
+    }
+}
diff --git a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/calc/RepaymentScheduleModel.java b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/data/ProgressiveLoanInterestScheduleModel.java
similarity index 50%
rename from fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/calc/RepaymentScheduleModel.java
rename to fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/data/ProgressiveLoanInterestScheduleModel.java
index 14d224921..6755f230d 100644
--- a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/calc/RepaymentScheduleModel.java
+++ b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/data/ProgressiveLoanInterestScheduleModel.java
@@ -16,18 +16,22 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.fineract.portfolio.loanproduct.calc;
+package org.apache.fineract.portfolio.loanaccount.loanschedule.data;
 
-import java.util.ArrayList;
+import java.math.MathContext;
+import java.time.temporal.ChronoUnit;
 import java.util.List;
-import lombok.Getter;
+import org.apache.fineract.portfolio.loanproduct.domain.LoanProductRelatedDetail;
 
-@Getter
-public class RepaymentScheduleModel {
+public record ProgressiveLoanInterestScheduleModel(List<ProgressiveLoanInterestRepaymentModel> repayments,
+        LoanProductRelatedDetail loanProductRelatedDetail, Integer installmentAmountInMultiplesOf, MathContext mc) {
 
-    List<RepaymentPeriodModel> scheduleList = new ArrayList<>();
-
-    public void addRepaymentPeriodModel(final RepaymentPeriodModel repaymentPeriodModel) {
-        scheduleList.add(repaymentPeriodModel);
+    public int getLoanTermInDays() {
+        if (repayments.isEmpty()) {
+            return 0;
+        }
+        final var firstPeriod = repayments.get(0);
+        final var lastPeriod = repayments.size() > 1 ? repayments.get(repayments.size() - 1) : firstPeriod;
+        return Math.toIntExact(ChronoUnit.DAYS.between(firstPeriod.getFromDate(), lastPeriod.getDueDate()));
     }
 }
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
deleted file mode 100644
index 54761f4ea..000000000
--- a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/AbstractProgressiveLoanScheduleGenerator.java
+++ /dev/null
@@ -1,620 +0,0 @@
-/**
- * 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.portfolio.loanaccount.loanschedule.domain;
-
-import java.math.BigDecimal;
-import java.math.MathContext;
-import java.time.LocalDate;
-import java.time.temporal.ChronoUnit;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.HashSet;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import org.apache.fineract.infrastructure.core.service.DateUtils;
-import org.apache.fineract.infrastructure.core.service.MathUtil;
-import org.apache.fineract.organisation.monetary.domain.ApplicationCurrency;
-import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
-import org.apache.fineract.organisation.monetary.domain.Money;
-import org.apache.fineract.organisation.workingdays.data.AdjustedDateDetailsDTO;
-import org.apache.fineract.portfolio.calendar.domain.CalendarInstance;
-import org.apache.fineract.portfolio.calendar.service.CalendarUtils;
-import org.apache.fineract.portfolio.loanaccount.data.DisbursementData;
-import org.apache.fineract.portfolio.loanaccount.data.HolidayDetailDTO;
-import org.apache.fineract.portfolio.loanaccount.data.LoanTermVariationsData;
-import org.apache.fineract.portfolio.loanaccount.domain.Loan;
-import org.apache.fineract.portfolio.loanaccount.domain.LoanCharge;
-import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment;
-import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.LoanRepaymentScheduleTransactionProcessor;
-import org.apache.fineract.portfolio.loanaccount.loanschedule.data.LoanScheduleDTO;
-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.exception.MultiDisbursementOutstandingAmoutException;
-import org.apache.fineract.portfolio.loanproduct.calc.EMICalculationResult;
-import org.apache.fineract.portfolio.loanproduct.calc.EMICalculator;
-import org.apache.fineract.portfolio.loanproduct.domain.RepaymentStartDateType;
-
-public abstract class AbstractProgressiveLoanScheduleGenerator implements LoanScheduleGenerator {
-
-    @Override
-    public LoanScheduleModel generate(final MathContext mc, final LoanApplicationTerms loanApplicationTerms,
-            final Set<LoanCharge> loanCharges, final HolidayDetailDTO holidayDetailDTO) {
-        // TODO: handle interest calculation
-        final ApplicationCurrency applicationCurrency = loanApplicationTerms.getApplicationCurrency();
-        // generate list of proposed schedule due dates
-        LocalDate loanEndDate = getScheduledDateGenerator().getLastRepaymentDate(loanApplicationTerms, holidayDetailDTO);
-        LoanTermVariationsData lastDueDateVariation = loanApplicationTerms.getLoanTermVariations()
-                .fetchLoanTermDueDateVariationsData(loanEndDate);
-        if (lastDueDateVariation != null) {
-            loanEndDate = lastDueDateVariation.getDateValue();
-        }
-        loanApplicationTerms.updateLoanEndDate(loanEndDate);
-
-        // determine the total charges due at time of disbursement
-        final BigDecimal chargesDueAtTimeOfDisbursement = deriveTotalChargesDueAtTimeOfDisbursement(loanCharges);
-
-        // setup variables for tracking important facts required for loan
-        // schedule generation.
-
-        final MonetaryCurrency currency = loanApplicationTerms.getCurrency();
-        LocalDate periodStartDate = RepaymentStartDateType.DISBURSEMENT_DATE.equals(loanApplicationTerms.getRepaymentStartDateType())
-                ? loanApplicationTerms.getExpectedDisbursementDate()
-                : loanApplicationTerms.getSubmittedOnDate();
-
-        LoanScheduleParams scheduleParams = LoanScheduleParams.createLoanScheduleParams(currency,
-                Money.of(currency, chargesDueAtTimeOfDisbursement), periodStartDate, Money.zero(currency));
-
-        List<LoanScheduleModelPeriod> periods = createNewLoanScheduleListWithDisbursementDetails(loanApplicationTerms, scheduleParams,
-                chargesDueAtTimeOfDisbursement);
-
-        EMICalculationResult emiCalculationResult = null;
-        boolean isFirstRepayment = true;
-
-        // charges which depends on total loan interest will be added to this
-        // set and handled separately after all installments generated
-        final Set<LoanCharge> nonCompoundingCharges = separateTotalCompoundingPercentageCharges(loanCharges);
-        boolean isNextRepaymentAvailable = true;
-
-        boolean thereIsDisbursementBeforeOrOnLoanEndDate = scheduleParams.getDisburseDetailMap().entrySet().stream()
-                .anyMatch(d -> !d.getKey().isAfter(loanApplicationTerms.getLoanEndDate()));
-        while (!scheduleParams.getOutstandingBalance().isZero() || thereIsDisbursementBeforeOrOnLoanEndDate) {
-            scheduleParams.setActualRepaymentDate(getScheduledDateGenerator().generateNextRepaymentDate(
-                    scheduleParams.getActualRepaymentDate(), loanApplicationTerms, isFirstRepayment, scheduleParams.getPeriodNumber()));
-            AdjustedDateDetailsDTO adjustedDateDetailsDTO = getScheduledDateGenerator()
-                    .adjustRepaymentDate(scheduleParams.getActualRepaymentDate(), loanApplicationTerms, holidayDetailDTO);
-            scheduleParams.setActualRepaymentDate(adjustedDateDetailsDTO.getChangedActualRepaymentDate());
-            LocalDate scheduledDueDate = adjustedDateDetailsDTO.getChangedScheduleDate();
-
-            // Loan Schedule Exceptions that need to be applied for Loan Account
-            LoanTermVariationParams termVariationParams = applyLoanTermVariations(loanApplicationTerms, scheduleParams, scheduledDueDate);
-            scheduledDueDate = termVariationParams.scheduledDueDate();
-
-            // Updates total days in term
-            scheduleParams
-                    .addLoanTermInDays(Math.toIntExact(ChronoUnit.DAYS.between(scheduleParams.getPeriodStartDate(), scheduledDueDate)));
-
-            ScheduleCurrentPeriodParams currentPeriodParams = new ScheduleCurrentPeriodParams(currency, BigDecimal.ZERO);
-            final boolean hasAnyProcessedDisbursement = processDisbursements(loanApplicationTerms, chargesDueAtTimeOfDisbursement,
-                    scheduleParams, periods, scheduledDueDate);
-            if (isFirstRepayment || hasAnyProcessedDisbursement) {
-                final LocalDate startDate = isFirstRepayment ? loanApplicationTerms.getRepaymentStartDate()
-                        : scheduleParams.getPeriodStartDate();
-                List<PreGeneratedLoanSchedulePeriod> expectedRepaymentPeriods = getScheduledDateGenerator()
-                        .generateRepaymentPeriods(startDate, loanApplicationTerms, holidayDetailDTO);
-                emiCalculationResult = getEMICalculator().calculateEMIValueAndRateFactors(scheduleParams.getOutstandingBalanceAsPerRest(),
-                        loanApplicationTerms.toLoanProductRelatedDetail(), expectedRepaymentPeriods, scheduleParams.getPeriodNumber(),
-                        loanApplicationTerms.getNumberOfRepayments(), mc);
-            }
-
-            // 5 determine principal,interest of repayment period
-            PrincipalInterest principalInterestForThisPeriod = getEMICalculator().calculatePrincipalInterestComponentsForPeriod(
-                    emiCalculationResult, scheduleParams.getOutstandingBalanceAsPerRest(),
-                    loanApplicationTerms.getInstallmentAmountInMultiplesOf(), scheduleParams.getPeriodNumber(),
-                    loanApplicationTerms.getActualNoOfRepaymnets(), mc);
-
-            // update cumulative fields for principal
-            currentPeriodParams.setPrincipalForThisPeriod(principalInterestForThisPeriod.principal());
-            currentPeriodParams.setInterestForThisPeriod(principalInterestForThisPeriod.interest());
-            updateOutstandingBalance(scheduleParams, currentPeriodParams);
-
-            if (scheduleParams.getOutstandingBalance().isLessThanZero() || !isNextRepaymentAvailable) {
-                currentPeriodParams.plusPrincipalForThisPeriod(scheduleParams.getOutstandingBalance());
-                scheduleParams.setOutstandingBalance(Money.zero(currency));
-            }
-
-            if (!isNextRepaymentAvailable) {
-                scheduleParams.getDisburseDetailMap().clear();
-            }
-
-            // applies charges for the period
-            applyChargesForCurrentPeriod(loanCharges, currency, scheduleParams, scheduledDueDate, currentPeriodParams, mc);
-
-            // sum up real totalInstallmentDue from components
-            final Money totalInstallmentDue = currentPeriodParams.fetchTotalAmountForPeriod();
-
-            // if previous installment is last then add interest to same
-            // installment
-            if (currentPeriodParams.getLastInstallment() != null && currentPeriodParams.getPrincipalForThisPeriod().isZero()) {
-                currentPeriodParams.getLastInstallment().addInterestAmount(currentPeriodParams.getInterestForThisPeriod());
-                continue;
-            }
-
-            // create repayment period from parts
-            LoanScheduleModelPeriod installment = LoanScheduleModelRepaymentPeriod.repayment(scheduleParams.getInstalmentNumber(),
-                    scheduleParams.getPeriodStartDate(), scheduledDueDate, currentPeriodParams.getPrincipalForThisPeriod(),
-                    scheduleParams.getOutstandingBalance(), currentPeriodParams.getInterestForThisPeriod(),
-                    currentPeriodParams.getFeeChargesForInstallment(), currentPeriodParams.getPenaltyChargesForInstallment(),
-                    totalInstallmentDue, false);
-            if (principalInterestForThisPeriod.getRescheduleInterestPortion() != null) {
-                installment.setRescheduleInterestPortion(principalInterestForThisPeriod.getRescheduleInterestPortion().getAmount());
-            }
-            addLoanRepaymentScheduleInstallment(scheduleParams.getInstallments(), installment);
-
-            if (loanApplicationTerms.getCurrentPeriodFixedEmiAmount() != null) {
-                installment.setEMIFixedSpecificToInstallmentTrue();
-            }
-
-            periods.add(installment);
-
-            // Updates principal paid map with effective date for reducing
-            // the amount from outstanding balance(interest calculation)
-            updateAmountsWithEffectiveDate(loanApplicationTerms, holidayDetailDTO, scheduleParams, scheduledDueDate, currentPeriodParams,
-                    installment);
-
-            // handle cumulative fields
-
-            scheduleParams.addTotalCumulativePrincipal(currentPeriodParams.getPrincipalForThisPeriod());
-            scheduleParams.addTotalRepaymentExpected(totalInstallmentDue);
-            scheduleParams.addTotalCumulativeInterest(currentPeriodParams.getInterestForThisPeriod());
-            scheduleParams.setPeriodStartDate(scheduledDueDate);
-            scheduleParams.incrementInstalmentNumber();
-            scheduleParams.incrementPeriodNumber();
-            // if (termVariationParams.isRecalculateAmounts()) {
-            // loanApplicationTerms.setCurrentPeriodFixedEmiAmount(null);
-            // loanApplicationTerms.setCurrentPeriodFixedPrincipalAmount(null);
-            // adjustInstallmentOrPrincipalAmount(loanApplicationTerms, scheduleParams.getTotalCumulativePrincipal(),
-            // scheduleParams.getPeriodNumber(), mc);
-            // }
-            thereIsDisbursementBeforeOrOnLoanEndDate = scheduleParams.getDisburseDetailMap().entrySet().stream()
-                    .anyMatch(d -> !d.getKey().isAfter(loanApplicationTerms.getLoanEndDate()));
-            isFirstRepayment = false;
-        }
-
-        // If the disbursement happened after maturity date
-        if (loanApplicationTerms.isMultiDisburseLoan()) {
-            processDisbursements(loanApplicationTerms, chargesDueAtTimeOfDisbursement, scheduleParams, periods,
-                    DateUtils.getBusinessLocalDate().plusDays(1));
-        }
-
-        // determine fees and penalties for charges which depends on total
-        // loan interest
-        updatePeriodsWithCharges(currency, scheduleParams, periods, nonCompoundingCharges, mc);
-
-        final BigDecimal totalPrincipalPaid = BigDecimal.ZERO;
-        final BigDecimal totalOutstanding = BigDecimal.ZERO;
-
-        return LoanScheduleModel.from(periods, applicationCurrency, scheduleParams.getLoanTermInDays(),
-                scheduleParams.getPrincipalToBeScheduled().plus(loanApplicationTerms.getDownPaymentAmount()),
-                scheduleParams.getTotalCumulativePrincipal().plus(loanApplicationTerms.getDownPaymentAmount()).getAmount(),
-                totalPrincipalPaid, scheduleParams.getTotalCumulativeInterest().getAmount(),
-                scheduleParams.getTotalFeeChargesCharged().getAmount(), scheduleParams.getTotalPenaltyChargesCharged().getAmount(),
-                scheduleParams.getTotalRepaymentExpected().getAmount(), totalOutstanding);
-    }
-
-    @Override
-    public LoanScheduleDTO rescheduleNextInstallments(MathContext mc, LoanApplicationTerms loanApplicationTerms, Loan loan,
-            HolidayDetailDTO holidayDetailDTO, LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor,
-            LocalDate rescheduleFrom) {
-
-        LoanScheduleModel model = generate(mc, loanApplicationTerms, loan.getActiveCharges(), holidayDetailDTO);
-        return LoanScheduleDTO.from(null, model);
-    }
-
-    @Override
-    public LoanRepaymentScheduleInstallment calculatePrepaymentAmount(MonetaryCurrency currency, LocalDate onDate,
-            LoanApplicationTerms loanApplicationTerms, MathContext mc, Loan loan, HolidayDetailDTO holidayDetailDTO,
-            LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor) {
-        return null;
-    }
-
-    // Abstract methods
-    public abstract ScheduledDateGenerator getScheduledDateGenerator();
-
-    public abstract PaymentPeriodsInOneYearCalculator getPaymentPeriodsInOneYearCalculator();
-
-    protected abstract EMICalculator getEMICalculator();
-
-    // Private, internal methods
-    private BigDecimal deriveTotalChargesDueAtTimeOfDisbursement(final Set<LoanCharge> loanCharges) {
-        BigDecimal chargesDueAtTimeOfDisbursement = BigDecimal.ZERO;
-        for (final LoanCharge loanCharge : loanCharges) {
-            if (loanCharge.isDueAtDisbursement()) {
-                chargesDueAtTimeOfDisbursement = chargesDueAtTimeOfDisbursement.add(loanCharge.amount());
-            }
-        }
-        return chargesDueAtTimeOfDisbursement;
-    }
-
-    private List<LoanScheduleModelPeriod> createNewLoanScheduleListWithDisbursementDetails(final LoanApplicationTerms loanApplicationTerms,
-            final LoanScheduleParams loanScheduleParams, final BigDecimal chargesDueAtTimeOfDisbursement) {
-        List<LoanScheduleModelPeriod> periods = new ArrayList<>();
-
-        // In case of `disallowExpectedDisbursementDetails = true`, anyway at least 1 disbursement details must exist
-        if (loanApplicationTerms.getDisbursementDatas().isEmpty()) {
-            loanApplicationTerms.getDisbursementDatas()
-                    .add(new DisbursementData(1L, loanApplicationTerms.getExpectedDisbursementDate(),
-                            loanApplicationTerms.getExpectedDisbursementDate(), loanApplicationTerms.getPrincipal().getAmount(), null, null,
-                            null, null));
-        }
-        for (DisbursementData disbursementData : loanApplicationTerms.getDisbursementDatas()) {
-            Money principalMoney = Money.of(loanApplicationTerms.getCurrency(), disbursementData.getPrincipal());
-            if (disbursementData.disbursementDate().equals(loanScheduleParams.getPeriodStartDate())) {
-                final LoanScheduleModelDisbursementPeriod disbursementPeriod = LoanScheduleModelDisbursementPeriod.disbursement(
-                        disbursementData.disbursementDate(), Money.of(loanScheduleParams.getCurrency(), disbursementData.getPrincipal()),
-                        chargesDueAtTimeOfDisbursement);
-                periods.add(disbursementPeriod);
-                loanScheduleParams.addOutstandingBalance(principalMoney);
-                loanScheduleParams.addOutstandingBalanceAsPerRest(principalMoney);
-                loanScheduleParams.addPrincipalToBeScheduled(principalMoney);
-                loanApplicationTerms.resetFixedEmiAmount();
-                if (loanApplicationTerms.isDownPaymentEnabled()) {
-                    final LoanScheduleModelDownPaymentPeriod downPaymentPeriod = createDownPaymentPeriod(loanApplicationTerms,
-                            loanScheduleParams, loanApplicationTerms.getExpectedDisbursementDate(), disbursementData.getPrincipal());
-                    periods.add(downPaymentPeriod);
-                }
-            } else {
-                Money disbursedAmount = loanScheduleParams.getDisburseDetailMap().getOrDefault(disbursementData.disbursementDate(),
-                        Money.zero(loanApplicationTerms.getCurrency()));
-                loanScheduleParams.getDisburseDetailMap().put(disbursementData.disbursementDate(), disbursedAmount.add(principalMoney));
-            }
-        }
-
-        return periods;
-    }
-
-    private LoanScheduleModelDownPaymentPeriod createDownPaymentPeriod(LoanApplicationTerms loanApplicationTerms,
-            LoanScheduleParams scheduleParams, LocalDate date, BigDecimal periodBaseAmount) {
-        Money downPaymentAmount = Money.of(loanApplicationTerms.getCurrency(),
-                MathUtil.percentageOf(periodBaseAmount, loanApplicationTerms.getDisbursedAmountPercentageForDownPayment(), 19));
-        if (loanApplicationTerms.getInstallmentAmountInMultiplesOf() != null) {
-            downPaymentAmount = Money.roundToMultiplesOf(downPaymentAmount, loanApplicationTerms.getInstallmentAmountInMultiplesOf());
-        }
-        LoanScheduleModelDownPaymentPeriod installment = LoanScheduleModelDownPaymentPeriod.downPayment(
-                scheduleParams.getInstalmentNumber(), date, downPaymentAmount,
-                scheduleParams.getOutstandingBalance().minus(downPaymentAmount));
-
-        addLoanRepaymentScheduleInstallment(scheduleParams.getInstallments(), installment);
-
-        scheduleParams.incrementInstalmentNumber();
-        scheduleParams.addTotalRepaymentExpected(downPaymentAmount);
-        scheduleParams.reduceOutstandingBalance(downPaymentAmount);
-        scheduleParams.reduceOutstandingBalanceAsPerRest(downPaymentAmount);
-        scheduleParams.setPrincipalToBeScheduled(scheduleParams.getPrincipalToBeScheduled().minus(downPaymentAmount));
-        loanApplicationTerms.setPrincipal(loanApplicationTerms.getPrincipal().minus(downPaymentAmount));
-        loanApplicationTerms.resetFixedEmiAmount();
-        return installment;
-    }
-
-    private LoanRepaymentScheduleInstallment addLoanRepaymentScheduleInstallment(final List<LoanRepaymentScheduleInstallment> installments,
-            final LoanScheduleModelPeriod scheduledLoanInstallment) {
-        LoanRepaymentScheduleInstallment installment = null;
-        if (scheduledLoanInstallment.isRepaymentPeriod() || scheduledLoanInstallment.isDownPaymentPeriod()) {
-            installment = new LoanRepaymentScheduleInstallment(null, scheduledLoanInstallment.periodNumber(),
-                    scheduledLoanInstallment.periodFromDate(), scheduledLoanInstallment.periodDueDate(),
-                    scheduledLoanInstallment.principalDue(), scheduledLoanInstallment.interestDue(),
-                    scheduledLoanInstallment.feeChargesDue(), scheduledLoanInstallment.penaltyChargesDue(),
-                    scheduledLoanInstallment.isRecalculatedInterestComponent(), scheduledLoanInstallment.getLoanCompoundingDetails(),
-                    scheduledLoanInstallment.rescheduleInterestPortion(), scheduledLoanInstallment.isDownPaymentPeriod());
-            installments.add(installment);
-        }
-        return installment;
-    }
-
-    /**
-     * Method add extra disbursement periods (if applicable) and update the schedule params
-     */
-    private boolean processDisbursements(final LoanApplicationTerms loanApplicationTerms, final BigDecimal chargesDueAtTimeOfDisbursement,
-            LoanScheduleParams scheduleParams, final Collection<LoanScheduleModelPeriod> periods, final LocalDate scheduledDueDate) {
-        Iterator<Map.Entry<LocalDate, Money>> iter = scheduleParams.getDisburseDetailMap().entrySet().iterator();
-        boolean hasProcessedDisbursement = false;
-        while (iter.hasNext()) {
-            Map.Entry<LocalDate, Money> disburseDetail = iter.next();
-            if ((disburseDetail.getKey().isEqual(scheduleParams.getPeriodStartDate())
-                    || disburseDetail.getKey().isAfter(scheduleParams.getPeriodStartDate()))
-                    && disburseDetail.getKey().isBefore(scheduledDueDate)) {
-                // validation check for amount not exceeds specified max
-                // amount as per the configuration
-                if (loanApplicationTerms.isMultiDisburseLoan() && loanApplicationTerms.getMaxOutstandingBalance() != null) {
-                    Money maxOutstandingBalance = loanApplicationTerms.getMaxOutstandingBalanceMoney();
-                    if (scheduleParams.getOutstandingBalance().plus(disburseDetail.getValue()).isGreaterThan(maxOutstandingBalance)) {
-                        String errorMsg = "Outstanding balance must not exceed the amount: " + maxOutstandingBalance;
-                        throw new MultiDisbursementOutstandingAmoutException(errorMsg, loanApplicationTerms.getMaxOutstandingBalance(),
-                                disburseDetail.getValue());
-                    }
-                }
-
-                // creates and add disbursement detail to the repayments
-                // period
-                final LoanScheduleModelDisbursementPeriod disbursementPeriod = LoanScheduleModelDisbursementPeriod
-                        .disbursement(disburseDetail.getKey(), disburseDetail.getValue(), chargesDueAtTimeOfDisbursement);
-                periods.add(disbursementPeriod);
-
-                BigDecimal downPaymentAmt = BigDecimal.ZERO;
-                // updates actual outstanding balance with new
-                // disbursement detail
-                scheduleParams.addOutstandingBalance(disburseDetail.getValue());
-                scheduleParams.addOutstandingBalanceAsPerRest(disburseDetail.getValue());
-                scheduleParams.addPrincipalToBeScheduled(disburseDetail.getValue());
-                loanApplicationTerms.setPrincipal(loanApplicationTerms.getPrincipal().plus(disburseDetail.getValue()));
-                loanApplicationTerms.resetFixedEmiAmount();
-                if (loanApplicationTerms.isDownPaymentEnabled()) {
-                    // get list of disbursements done on same day and create down payment periods
-                    List<DisbursementData> disbursementsOnSameDate = loanApplicationTerms.getDisbursementDatas().stream()
-                            .filter(disbursementData -> DateUtils.isEqual(disbursementData.disbursementDate(), disburseDetail.getKey()))
-                            .toList();
-                    for (DisbursementData disbursementData : disbursementsOnSameDate) {
-                        final LoanScheduleModelDownPaymentPeriod downPaymentPeriod = createDownPaymentPeriod(loanApplicationTerms,
-                                scheduleParams, disbursementData.disbursementDate(), disbursementData.getPrincipal());
-                        periods.add(downPaymentPeriod);
-                        downPaymentAmt = downPaymentAmt.add(downPaymentPeriod.principalDue());
-                    }
-                }
-                iter.remove();
-                hasProcessedDisbursement = true;
-            }
-        }
-        return hasProcessedDisbursement;
-    }
-
-    private void applyChargesForCurrentPeriod(final Set<LoanCharge> loanCharges, final MonetaryCurrency currency,
-            LoanScheduleParams scheduleParams, LocalDate scheduledDueDate, ScheduleCurrentPeriodParams currentPeriodParams,
-            final MathContext mc) {
-        PrincipalInterest principalInterest = new PrincipalInterest(currentPeriodParams.getPrincipalForThisPeriod(),
-                currentPeriodParams.getInterestForThisPeriod(), null);
-        currentPeriodParams.setFeeChargesForInstallment(cumulativeFeeChargesDueWithin(scheduleParams.getPeriodStartDate(), scheduledDueDate,
-                loanCharges, currency, principalInterest, scheduleParams.getPrincipalToBeScheduled(),
-                scheduleParams.getTotalCumulativeInterest(), true, scheduleParams.isFirstPeriod(), mc));
-        currentPeriodParams.setPenaltyChargesForInstallment(cumulativePenaltyChargesDueWithin(scheduleParams.getPeriodStartDate(),
-                scheduledDueDate, loanCharges, currency, principalInterest, scheduleParams.getPrincipalToBeScheduled(),
-                scheduleParams.getTotalCumulativeInterest(), true, scheduleParams.isFirstPeriod(), mc));
-        scheduleParams.addTotalFeeChargesCharged(currentPeriodParams.getFeeChargesForInstallment());
-        scheduleParams.addTotalPenaltyChargesCharged(currentPeriodParams.getPenaltyChargesForInstallment());
-    }
-
-    private Money cumulativeFeeChargesDueWithin(final LocalDate periodStart, final LocalDate periodEnd, final Set<LoanCharge> loanCharges,
-            final MonetaryCurrency monetaryCurrency, final PrincipalInterest principalInterestForThisPeriod, final Money principalDisbursed,
-            final Money totalInterestChargedForFullLoanTerm, boolean isInstallmentChargeApplicable, final boolean isFirstPeriod,
-            final MathContext mc) {
-        Money cumulative = Money.zero(monetaryCurrency);
-        for (final LoanCharge loanCharge : loanCharges) {
-            if (!loanCharge.isDueAtDisbursement() && loanCharge.isFeeCharge()) {
-                cumulative = getCumulativeAmountOfCharge(periodStart, periodEnd, principalInterestForThisPeriod, principalDisbursed,
-                        totalInterestChargedForFullLoanTerm, isInstallmentChargeApplicable, isFirstPeriod, loanCharge, cumulative, mc);
-            }
-        }
-        return cumulative;
-    }
-
-    private Money getCumulativeAmountOfCharge(LocalDate periodStart, LocalDate periodEnd, PrincipalInterest principalInterestForThisPeriod,
-            Money principalDisbursed, Money totalInterestChargedForFullLoanTerm, boolean isInstallmentChargeApplicable,
-            boolean isFirstPeriod, LoanCharge loanCharge, Money cumulative, MathContext mc) {
-        boolean isDue = isFirstPeriod ? loanCharge.isDueForCollectionFromIncludingAndUpToAndIncluding(periodStart, periodEnd)
-                : loanCharge.isDueForCollectionFromAndUpToAndIncluding(periodStart, periodEnd);
-        if (loanCharge.isInstalmentFee() && isInstallmentChargeApplicable) {
-            cumulative = calculateInstallmentCharge(principalInterestForThisPeriod, cumulative, loanCharge, mc);
-        } else if (loanCharge.isOverdueInstallmentCharge() && isDue && loanCharge.getChargeCalculation().isPercentageBased()) {
-            cumulative = cumulative.plus(loanCharge.chargeAmount());
-        } else if (isDue && loanCharge.getChargeCalculation().isPercentageBased()) {
-            cumulative = calculateSpecificDueDateChargeWithPercentage(principalDisbursed, totalInterestChargedForFullLoanTerm, cumulative,
-                    loanCharge, mc);
-        } else if (isDue) {
-            cumulative = cumulative.plus(loanCharge.amount());
-        }
-        return cumulative;
-    }
-
-    private Money cumulativePenaltyChargesDueWithin(final LocalDate periodStart, final LocalDate periodEnd,
-            final Set<LoanCharge> loanCharges, final MonetaryCurrency monetaryCurrency,
-            final PrincipalInterest principalInterestForThisPeriod, final Money principalDisbursed,
-            final Money totalInterestChargedForFullLoanTerm, boolean isInstallmentChargeApplicable, final boolean isFirstPeriod,
-            final MathContext mc) {
-        Money cumulative = Money.zero(monetaryCurrency);
-        for (final LoanCharge loanCharge : loanCharges) {
-            if (loanCharge.isPenaltyCharge()) {
-                cumulative = getCumulativeAmountOfCharge(periodStart, periodEnd, principalInterestForThisPeriod, principalDisbursed,
-                        totalInterestChargedForFullLoanTerm, isInstallmentChargeApplicable, isFirstPeriod, loanCharge, cumulative, mc);
-            }
-        }
-        return cumulative;
-    }
-
-    private Money calculateInstallmentCharge(final PrincipalInterest principalInterestForThisPeriod, Money cumulative,
-            final LoanCharge loanCharge, final MathContext mc) {
-        if (loanCharge.getChargeCalculation().isPercentageBased()) {
-            BigDecimal amount = BigDecimal.ZERO;
-            if (loanCharge.getChargeCalculation().isPercentageOfAmountAndInterest()) {
-                amount = amount.add(principalInterestForThisPeriod.principal().getAmount())
-                        .add(principalInterestForThisPeriod.interest().getAmount());
-            } else if (loanCharge.getChargeCalculation().isPercentageOfInterest()) {
-                amount = amount.add(principalInterestForThisPeriod.interest().getAmount());
-            } else {
-                amount = amount.add(principalInterestForThisPeriod.principal().getAmount());
-            }
-            BigDecimal loanChargeAmt = amount.multiply(loanCharge.getPercentage()).divide(BigDecimal.valueOf(100), mc);
-            cumulative = cumulative.plus(loanChargeAmt);
-        } else {
-            cumulative = cumulative.plus(loanCharge.amountOrPercentage());
-        }
-        return cumulative;
-    }
-
-    private Money calculateSpecificDueDateChargeWithPercentage(final Money principalDisbursed,
-            final Money totalInterestChargedForFullLoanTerm, Money cumulative, final LoanCharge loanCharge, final MathContext mc) {
-        BigDecimal amount = BigDecimal.ZERO;
-        if (loanCharge.getChargeCalculation().isPercentageOfAmountAndInterest()) {
-            amount = amount.add(principalDisbursed.getAmount()).add(totalInterestChargedForFullLoanTerm.getAmount());
-        } else if (loanCharge.getChargeCalculation().isPercentageOfInterest()) {
-            amount = amount.add(totalInterestChargedForFullLoanTerm.getAmount());
-        } else {
-            amount = amount.add(principalDisbursed.getAmount());
-        }
-        BigDecimal loanChargeAmt = amount.multiply(loanCharge.getPercentage()).divide(BigDecimal.valueOf(100), mc);
-        cumulative = cumulative.plus(loanChargeAmt);
-        return cumulative;
-    }
-
-    private void updateAmountsWithEffectiveDate(final LoanApplicationTerms loanApplicationTerms, final HolidayDetailDTO holidayDetailDTO,
-            LoanScheduleParams scheduleParams, LocalDate scheduledDueDate, ScheduleCurrentPeriodParams currentPeriodParams,
-            LoanScheduleModelPeriod installment) {
-        LocalDate amountApplicableDate = installment.periodDueDate();
-        if (loanApplicationTerms.isInterestRecalculationEnabled()) {
-            amountApplicableDate = getNextRestScheduleDate(installment.periodDueDate().minusDays(1), loanApplicationTerms,
-                    holidayDetailDTO);
-        }
-        updateMapWithAmount(scheduleParams.getPrincipalPortionMap(),
-                currentPeriodParams.getPrincipalForThisPeriod().minus(currentPeriodParams.getReducedBalance()), amountApplicableDate);
-
-        // update outstanding balance for interest calculation
-        updateOutstandingBalanceAsPerRest(scheduleParams, scheduledDueDate);
-    }
-
-    private LocalDate getNextRestScheduleDate(LocalDate startDate, LoanApplicationTerms loanApplicationTerms,
-            final HolidayDetailDTO holidayDetailDTO) {
-        LocalDate nextScheduleDate;
-        if (loanApplicationTerms.getRecalculationFrequencyType().isSameAsRepayment()) {
-            nextScheduleDate = getScheduledDateGenerator().generateNextScheduleDateStartingFromDisburseDateOrRescheduleDate(startDate,
-                    loanApplicationTerms, holidayDetailDTO);
-        } else {
-            CalendarInstance calendarInstance = loanApplicationTerms.getRestCalendarInstance();
-            nextScheduleDate = CalendarUtils.getNextScheduleDate(calendarInstance.getCalendar(), startDate);
-        }
-
-        return nextScheduleDate;
-    }
-
-    private void updateMapWithAmount(final Map<LocalDate, Money> map, final Money amount, final LocalDate amountApplicableDate) {
-        Money principalPaid = amount;
-        if (map.containsKey(amountApplicableDate)) {
-            principalPaid = map.get(amountApplicableDate).plus(principalPaid);
-        }
-        map.put(amountApplicableDate, principalPaid);
-
-    }
-
-    private void updateOutstandingBalanceAsPerRest(final LoanScheduleParams scheduleParams, final LocalDate scheduledDueDate) {
-        scheduleParams.setOutstandingBalanceAsPerRest(updateBalanceForInterestCalculation(scheduleParams.getPrincipalPortionMap(),
-                scheduledDueDate, scheduleParams.getOutstandingBalanceAsPerRest(), false));
-    }
-
-    /**
-     * Identifies all the past date principal changes and apply them on outstanding balance for future calculations
-     */
-    private Money updateBalanceForInterestCalculation(final Map<LocalDate, Money> principalPortionMap, final LocalDate scheduledDueDate,
-            final Money outstandingBalanceAsPerRest, boolean addMapDetails) {
-        List<LocalDate> removeFromPrincipalPortionMap = new ArrayList<>();
-        Money outstandingBalance = outstandingBalanceAsPerRest;
-        for (Map.Entry<LocalDate, Money> principal : principalPortionMap.entrySet()) {
-            if (!principal.getKey().isAfter(scheduledDueDate)) {
-                if (addMapDetails) {
-                    outstandingBalance = outstandingBalance.plus(principal.getValue());
-                } else {
-                    outstandingBalance = outstandingBalance.minus(principal.getValue());
-                }
-                removeFromPrincipalPortionMap.add(principal.getKey());
-            }
-        }
-        for (LocalDate date : removeFromPrincipalPortionMap) {
-            principalPortionMap.remove(date);
-        }
-        return outstandingBalance;
-    }
-
-    private void updatePeriodsWithCharges(final MonetaryCurrency currency, LoanScheduleParams scheduleParams,
-            final Collection<LoanScheduleModelPeriod> periods, final Set<LoanCharge> nonCompoundingCharges, MathContext mc) {
-        for (LoanScheduleModelPeriod loanScheduleModelPeriod : periods) {
-            if (loanScheduleModelPeriod.isRepaymentPeriod()) {
-                PrincipalInterest principalInterest = new PrincipalInterest(Money.of(currency, loanScheduleModelPeriod.principalDue()),
-                        Money.of(currency, loanScheduleModelPeriod.interestDue()), null);
-                Money feeChargesForInstallment = cumulativeFeeChargesDueWithin(loanScheduleModelPeriod.periodFromDate(),
-                        loanScheduleModelPeriod.periodDueDate(), nonCompoundingCharges, currency, principalInterest,
-                        scheduleParams.getPrincipalToBeScheduled(), scheduleParams.getTotalCumulativeInterest(),
-                        !loanScheduleModelPeriod.isRecalculatedInterestComponent(), scheduleParams.isFirstPeriod(), mc);
-                Money penaltyChargesForInstallment = cumulativePenaltyChargesDueWithin(loanScheduleModelPeriod.periodFromDate(),
-                        loanScheduleModelPeriod.periodDueDate(), nonCompoundingCharges, currency, principalInterest,
-                        scheduleParams.getPrincipalToBeScheduled(), scheduleParams.getTotalCumulativeInterest(),
-                        !loanScheduleModelPeriod.isRecalculatedInterestComponent(), scheduleParams.isFirstPeriod(), mc);
-                scheduleParams.addTotalFeeChargesCharged(feeChargesForInstallment);
-                scheduleParams.addTotalPenaltyChargesCharged(penaltyChargesForInstallment);
-                scheduleParams.addTotalRepaymentExpected(feeChargesForInstallment.plus(penaltyChargesForInstallment));
-                loanScheduleModelPeriod.addLoanCharges(feeChargesForInstallment.getAmount(), penaltyChargesForInstallment.getAmount());
-            }
-        }
-    }
-
-    private Set<LoanCharge> separateTotalCompoundingPercentageCharges(final Set<LoanCharge> loanCharges) {
-        Set<LoanCharge> interestCharges = new HashSet<>();
-        for (final LoanCharge loanCharge : loanCharges) {
-            if (loanCharge.isSpecifiedDueDate() && (loanCharge.getChargeCalculation().isPercentageOfInterest()
-                    || loanCharge.getChargeCalculation().isPercentageOfAmountAndInterest())) {
-                interestCharges.add(loanCharge);
-            }
-        }
-        loanCharges.removeAll(interestCharges);
-        return interestCharges;
-    }
-
-    private void updateOutstandingBalance(LoanScheduleParams scheduleParams, ScheduleCurrentPeriodParams currentPeriodParams) {
-        // update outstandingLoanBlance using current period
-        // 'principalDue'
-        scheduleParams
-                .reduceOutstandingBalance(currentPeriodParams.getPrincipalForThisPeriod().minus(currentPeriodParams.getReducedBalance()));
-    }
-
-    /**
-     * @param loanApplicationTerms
-     * @param scheduleParams
-     * @param scheduledDueDate
-     * @return
-     */
-    private LoanTermVariationParams applyLoanTermVariations(final LoanApplicationTerms loanApplicationTerms,
-            final LoanScheduleParams scheduleParams, final LocalDate scheduledDueDate) {
-        boolean skipPeriod = false;
-        boolean recalculateAmounts = false;
-        LocalDate modifiedScheduledDueDate = scheduledDueDate;
-        ArrayList<LoanTermVariationsData> variationsData = null;
-
-        // due date changes should be applied only for that dueDate
-        if (loanApplicationTerms.getLoanTermVariations().hasDueDateVariation(scheduledDueDate)) {
-            LoanTermVariationsData loanTermVariationsData = loanApplicationTerms.getLoanTermVariations().nextDueDateVariation();
-            if (DateUtils.isEqual(modifiedScheduledDueDate, loanTermVariationsData.getTermVariationApplicableFrom())) {
-                modifiedScheduledDueDate = loanTermVariationsData.getDateValue();
-                loanApplicationTerms.updateVariationDays(DateUtils.getDifferenceInDays(scheduledDueDate, modifiedScheduledDueDate));
-                if (!loanTermVariationsData.isSpecificToInstallment()) {
-                    scheduleParams.setActualRepaymentDate(modifiedScheduledDueDate);
-                    loanApplicationTerms.setNewScheduledDueDateStart(modifiedScheduledDueDate);
-                }
-                loanTermVariationsData.setProcessed(true);
-            }
-        }
-
-        return new LoanTermVariationParams(skipPeriod, recalculateAmounts, modifiedScheduledDueDate, variationsData);
-    }
-}
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 4bafc7561..a53e0b4b7 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,30 +18,358 @@
  */
 package org.apache.fineract.portfolio.loanaccount.loanschedule.domain;
 
+import java.math.BigDecimal;
+import java.math.MathContext;
+import java.time.LocalDate;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
 import lombok.RequiredArgsConstructor;
+import org.apache.fineract.infrastructure.core.service.MathUtil;
+import org.apache.fineract.organisation.monetary.domain.ApplicationCurrency;
+import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
+import org.apache.fineract.organisation.monetary.domain.Money;
+import org.apache.fineract.portfolio.loanaccount.data.DisbursementData;
+import org.apache.fineract.portfolio.loanaccount.data.HolidayDetailDTO;
+import org.apache.fineract.portfolio.loanaccount.data.LoanTermVariationsData;
+import org.apache.fineract.portfolio.loanaccount.domain.Loan;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanCharge;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment;
+import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.LoanRepaymentScheduleTransactionProcessor;
+import org.apache.fineract.portfolio.loanaccount.loanschedule.data.LoanScheduleDTO;
+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.data.ProgressiveLoanInterestRepaymentModel;
+import org.apache.fineract.portfolio.loanaccount.loanschedule.data.ProgressiveLoanInterestScheduleModel;
+import org.apache.fineract.portfolio.loanaccount.loanschedule.exception.MultiDisbursementOutstandingAmoutException;
 import org.apache.fineract.portfolio.loanproduct.calc.EMICalculator;
+import org.apache.fineract.portfolio.loanproduct.domain.RepaymentStartDateType;
 import org.springframework.stereotype.Component;
 
 @Component
 @RequiredArgsConstructor
-public class ProgressiveLoanScheduleGenerator extends AbstractProgressiveLoanScheduleGenerator {
+public class ProgressiveLoanScheduleGenerator implements LoanScheduleGenerator {
 
     private final ScheduledDateGenerator scheduledDateGenerator;
-    private final PaymentPeriodsInOneYearCalculator paymentPeriodsInOneYearCalculator;
     private final EMICalculator emiCalculator;
 
     @Override
-    public ScheduledDateGenerator getScheduledDateGenerator() {
-        return scheduledDateGenerator;
+    public LoanScheduleModel generate(final MathContext mc, final LoanApplicationTerms loanApplicationTerms,
+            final Set<LoanCharge> loanCharges, final HolidayDetailDTO holidayDetailDTO) {
+
+        final ApplicationCurrency applicationCurrency = loanApplicationTerms.getApplicationCurrency();
+        // generate list of proposed schedule due dates
+        LocalDate loanEndDate = scheduledDateGenerator.getLastRepaymentDate(loanApplicationTerms, holidayDetailDTO);
+        LoanTermVariationsData lastDueDateVariation = loanApplicationTerms.getLoanTermVariations()
+                .fetchLoanTermDueDateVariationsData(loanEndDate);
+        if (lastDueDateVariation != null) {
+            loanEndDate = lastDueDateVariation.getDateValue();
+        }
+        loanApplicationTerms.updateLoanEndDate(loanEndDate);
+
+        // determine the total charges due at time of disbursement
+        final BigDecimal chargesDueAtTimeOfDisbursement = deriveTotalChargesDueAtTimeOfDisbursement(loanCharges);
+
+        // setup variables for tracking important facts required for loan
+        // schedule generation.
+
+        final MonetaryCurrency currency = loanApplicationTerms.getCurrency();
+        LocalDate periodStartDate = RepaymentStartDateType.DISBURSEMENT_DATE.equals(loanApplicationTerms.getRepaymentStartDateType())
+                ? loanApplicationTerms.getExpectedDisbursementDate()
+                : loanApplicationTerms.getSubmittedOnDate();
+
+        final LoanScheduleParams scheduleParams = LoanScheduleParams.createLoanScheduleParams(currency,
+                Money.of(currency, chargesDueAtTimeOfDisbursement), periodStartDate, Money.zero(currency));
+
+        // charges which depends on total loan interest will be added to this
+        // set and handled separately after all installments generated
+        final Set<LoanCharge> nonCompoundingCharges = separateTotalCompoundingPercentageCharges(loanCharges);
+
+        final List<LoanScheduleModelRepaymentPeriod> expectedRepaymentPeriods = scheduledDateGenerator
+                .generateRepaymentPeriods(periodStartDate, loanApplicationTerms, holidayDetailDTO);
+        final ProgressiveLoanInterestScheduleModel interestScheduleModel = emiCalculator.generateInterestScheduleModel(
+                expectedRepaymentPeriods, loanApplicationTerms.toLoanProductRelatedDetail(),
+                loanApplicationTerms.getInstallmentAmountInMultiplesOf(), mc);
+        final List<LoanScheduleModelPeriod> periods = new ArrayList<>(expectedRepaymentPeriods.size() + 2);
+
+        prepareDisbursementsOnLoanApplicationTerms(loanApplicationTerms);
+
+        for (LoanScheduleModelRepaymentPeriod repaymentPeriod : expectedRepaymentPeriods) {
+            scheduleParams.setPeriodStartDate(repaymentPeriod.getFromDate());
+            scheduleParams.setActualRepaymentDate(repaymentPeriod.getDueDate());
+
+            processDisbursements(loanApplicationTerms, scheduleParams, interestScheduleModel, periods, chargesDueAtTimeOfDisbursement);
+            repaymentPeriod.setPeriodNumber(scheduleParams.getInstalmentNumber());
+
+            emiCalculator.findInterestRepaymentPeriod(interestScheduleModel, repaymentPeriod.getDueDate())
+                    .ifPresent(interestRepaymentPeriod -> {
+                        final Money principalDue = interestRepaymentPeriod.getPrincipalDue();
+                        final Money interestDue = interestRepaymentPeriod.getInterestDue();
+
+                        repaymentPeriod.addPrincipalAmount(principalDue);
+                        repaymentPeriod.addInterestAmount(interestDue);
+                        repaymentPeriod.setOutstandingLoanBalance(interestRepaymentPeriod.getRemainingBalance());
+
+                        scheduleParams.addTotalCumulativePrincipal(principalDue);
+                        scheduleParams.addTotalCumulativeInterest(interestDue);
+                        // add everything
+                        scheduleParams.addTotalRepaymentExpected(principalDue.plus(interestDue));
+                    });
+
+            applyChargesForCurrentPeriod(repaymentPeriod, loanCharges, scheduleParams, currency, mc);
+            periods.add(repaymentPeriod);
+
+            scheduleParams.incrementInstalmentNumber();
+            scheduleParams.incrementPeriodNumber();
+        }
+
+        if (loanApplicationTerms.isMultiDisburseLoan()) {
+            processDisbursements(loanApplicationTerms, scheduleParams, null, periods, chargesDueAtTimeOfDisbursement);
+        }
+
+        // determine fees and penalties for charges which depends on total
+        // loan interest
+        updatePeriodsWithCharges(currency, scheduleParams, periods, nonCompoundingCharges, mc);
+
+        final BigDecimal totalPrincipalPaid = BigDecimal.ZERO;
+        final BigDecimal totalOutstanding = BigDecimal.ZERO;
+
+        return LoanScheduleModel.from(periods, applicationCurrency, interestScheduleModel.getLoanTermInDays(),
+                scheduleParams.getPrincipalToBeScheduled().plus(loanApplicationTerms.getDownPaymentAmount()),
+                scheduleParams.getTotalCumulativePrincipal().plus(loanApplicationTerms.getDownPaymentAmount()).getAmount(),
+                totalPrincipalPaid, scheduleParams.getTotalCumulativeInterest().getAmount(),
+                scheduleParams.getTotalFeeChargesCharged().getAmount(), scheduleParams.getTotalPenaltyChargesCharged().getAmount(),
+                scheduleParams.getTotalRepaymentExpected().getAmount(), totalOutstanding);
+    }
+
+    private void prepareDisbursementsOnLoanApplicationTerms(final LoanApplicationTerms loanApplicationTerms) {
+        if (loanApplicationTerms.getDisbursementDatas().isEmpty()) {
+            loanApplicationTerms.getDisbursementDatas()
+                    .add(new DisbursementData(1L, loanApplicationTerms.getExpectedDisbursementDate(),
+                            loanApplicationTerms.getExpectedDisbursementDate(), loanApplicationTerms.getPrincipal().getAmount(), null, null,
+                            null, null));
+        }
+    }
+
+    private void processDisbursements(final LoanApplicationTerms loanApplicationTerms, final LoanScheduleParams scheduleParams,
+            final ProgressiveLoanInterestScheduleModel interestScheduleModel, final List<LoanScheduleModelPeriod> periods,
+            final BigDecimal chargesDueAtTimeOfDisbursement) {
+
+        for (DisbursementData disbursementData : loanApplicationTerms.getDisbursementDatas()) {
+            final LocalDate disbursementDate = disbursementData.disbursementDate();
+            final LocalDate periodFromDate = scheduleParams.getPeriodStartDate();
+            final LocalDate periodDueDate = scheduleParams.getActualRepaymentDate();
+
+            boolean hasDisbursementAfterLastRepaymentPeriod = interestScheduleModel == null && !disbursementDate.isBefore(periodDueDate);
+            boolean hasDisbursementInCurrentRepaymentPeriod = interestScheduleModel != null && !disbursementDate.isBefore(periodFromDate)
+                    && disbursementDate.isBefore(periodDueDate);
+            if (!hasDisbursementAfterLastRepaymentPeriod && !hasDisbursementInCurrentRepaymentPeriod) {
+                continue;
+            }
+
+            Money outstandingBalance = emiCalculator.findInterestRepaymentPeriod(interestScheduleModel, periodDueDate)
+                    .map(ProgressiveLoanInterestRepaymentModel::getOutstandingBalance)
+                    .orElse(Money.zero(loanApplicationTerms.getCurrency()));
+
+            final Money disbursedAmount = Money.of(loanApplicationTerms.getCurrency(), disbursementData.getPrincipal());
+            final LoanScheduleModelDisbursementPeriod disbursementPeriod = LoanScheduleModelDisbursementPeriod
+                    .disbursement(disbursementData.disbursementDate(), disbursedAmount, chargesDueAtTimeOfDisbursement);
+            periods.add(disbursementPeriod);
+
+            // validation check for amount not exceeds specified max
+            // amount as per the configuration
+            if (loanApplicationTerms.isMultiDisburseLoan() && loanApplicationTerms.getMaxOutstandingBalance() != null) {
+                Money maxOutstandingBalance = loanApplicationTerms.getMaxOutstandingBalanceMoney();
+                if (outstandingBalance.plus(disbursedAmount).isGreaterThan(maxOutstandingBalance)) {
+                    String errorMsg = "Outstanding balance must not exceed the amount: " + maxOutstandingBalance;
+                    throw new MultiDisbursementOutstandingAmoutException(errorMsg, loanApplicationTerms.getMaxOutstandingBalance(),
+                            disbursedAmount);
+                }
+            }
+
+            Money downPaymentAmount = Money.zero(loanApplicationTerms.getCurrency());
+            if (loanApplicationTerms.isDownPaymentEnabled()) {
+                downPaymentAmount = Money.of(loanApplicationTerms.getCurrency(), MathUtil.percentageOf(disbursedAmount.getAmount(),
+                        loanApplicationTerms.getDisbursedAmountPercentageForDownPayment(), 19));
+                if (loanApplicationTerms.getInstallmentAmountInMultiplesOf() != null) {
+                    downPaymentAmount = Money.roundToMultiplesOf(downPaymentAmount,
+                            loanApplicationTerms.getInstallmentAmountInMultiplesOf());
+                }
+
+                LoanScheduleModelDownPaymentPeriod downPaymentPeriod = LoanScheduleModelDownPaymentPeriod.downPayment(
+                        scheduleParams.getInstalmentNumber(), disbursementDate, downPaymentAmount,
+                        outstandingBalance.plus(disbursedAmount).minus(downPaymentAmount));
+                periods.add(downPaymentPeriod);
+
+                scheduleParams.addTotalRepaymentExpected(downPaymentAmount);
+                scheduleParams.incrementInstalmentNumber();
+            }
+
+            final Money disbursementRemainingBalance = disbursedAmount.minus(downPaymentAmount);
+            scheduleParams.addPrincipalToBeScheduled(disbursementRemainingBalance);
+            emiCalculator.addDisbursement(interestScheduleModel, disbursementDate, disbursementRemainingBalance);
+        }
     }
 
     @Override
-    public PaymentPeriodsInOneYearCalculator getPaymentPeriodsInOneYearCalculator() {
-        return paymentPeriodsInOneYearCalculator;
+    public LoanScheduleDTO rescheduleNextInstallments(MathContext mc, LoanApplicationTerms loanApplicationTerms, Loan loan,
+            HolidayDetailDTO holidayDetailDTO, LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor,
+            LocalDate rescheduleFrom) {
+        LoanScheduleModel model = generate(mc, loanApplicationTerms, loan.getActiveCharges(), holidayDetailDTO);
+        return LoanScheduleDTO.from(null, model);
     }
 
     @Override
-    protected EMICalculator getEMICalculator() {
-        return emiCalculator;
+    public LoanRepaymentScheduleInstallment calculatePrepaymentAmount(MonetaryCurrency currency, LocalDate onDate,
+            LoanApplicationTerms loanApplicationTerms, MathContext mc, Loan loan, HolidayDetailDTO holidayDetailDTO,
+            LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor) {
+        return null;
+    }
+
+    // Private, internal methods
+    private BigDecimal deriveTotalChargesDueAtTimeOfDisbursement(final Set<LoanCharge> loanCharges) {
+        BigDecimal chargesDueAtTimeOfDisbursement = BigDecimal.ZERO;
+        for (final LoanCharge loanCharge : loanCharges) {
+            if (loanCharge.isDueAtDisbursement()) {
+                chargesDueAtTimeOfDisbursement = chargesDueAtTimeOfDisbursement.add(loanCharge.amount());
+            }
+        }
+        return chargesDueAtTimeOfDisbursement;
+    }
+
+    private void applyChargesForCurrentPeriod(final LoanScheduleModelRepaymentPeriod repaymentPeriod, final Set<LoanCharge> loanCharges,
+            final LoanScheduleParams scheduleParams, final MonetaryCurrency currency, final MathContext mc) {
+        final PrincipalInterest principalInterest = new PrincipalInterest(repaymentPeriod.getPrincipalDue(),
+                repaymentPeriod.getInterestDue(), null);
+
+        final Money fees = cumulativeFeeChargesDueWithin(repaymentPeriod.getFromDate(), repaymentPeriod.getDueDate(), loanCharges, currency,
+                principalInterest, scheduleParams.getPrincipalToBeScheduled(), scheduleParams.getTotalCumulativeInterest(), true,
+                scheduleParams.isFirstPeriod(), mc);
+        final Money penalties = cumulativePenaltyChargesDueWithin(repaymentPeriod.getFromDate(), repaymentPeriod.getDueDate(), loanCharges,
+                currency, principalInterest, scheduleParams.getPrincipalToBeScheduled(), scheduleParams.getTotalCumulativeInterest(), true,
+                scheduleParams.isFirstPeriod(), mc);
+
+        repaymentPeriod.addLoanCharges(fees.getAmount(), penalties.getAmount());
+        scheduleParams.addTotalFeeChargesCharged(fees);
+        scheduleParams.addTotalPenaltyChargesCharged(penalties);
+    }
+
+    private Money cumulativeFeeChargesDueWithin(final LocalDate periodStart, final LocalDate periodEnd, final Set<LoanCharge> loanCharges,
+            final MonetaryCurrency monetaryCurrency, final PrincipalInterest principalInterestForThisPeriod, final Money principalDisbursed,
+            final Money totalInterestChargedForFullLoanTerm, boolean isInstallmentChargeApplicable, final boolean isFirstPeriod,
+            final MathContext mc) {
+        Money cumulative = Money.zero(monetaryCurrency);
+        for (final LoanCharge loanCharge : loanCharges) {
+            if (!loanCharge.isDueAtDisbursement() && loanCharge.isFeeCharge()) {
+                cumulative = getCumulativeAmountOfCharge(periodStart, periodEnd, principalInterestForThisPeriod, principalDisbursed,
+                        totalInterestChargedForFullLoanTerm, isInstallmentChargeApplicable, isFirstPeriod, loanCharge, cumulative, mc);
+            }
+        }
+        return cumulative;
     }
+
+    private Money getCumulativeAmountOfCharge(LocalDate periodStart, LocalDate periodEnd, PrincipalInterest principalInterestForThisPeriod,
+            Money principalDisbursed, Money totalInterestChargedForFullLoanTerm, boolean isInstallmentChargeApplicable,
+            boolean isFirstPeriod, LoanCharge loanCharge, Money cumulative, MathContext mc) {
+        boolean isDue = isFirstPeriod ? loanCharge.isDueForCollectionFromIncludingAndUpToAndIncluding(periodStart, periodEnd)
+                : loanCharge.isDueForCollectionFromAndUpToAndIncluding(periodStart, periodEnd);
+        if (loanCharge.isInstalmentFee() && isInstallmentChargeApplicable) {
+            cumulative = calculateInstallmentCharge(principalInterestForThisPeriod, cumulative, loanCharge, mc);
+        } else if (loanCharge.isOverdueInstallmentCharge() && isDue && loanCharge.getChargeCalculation().isPercentageBased()) {
+            cumulative = cumulative.plus(loanCharge.chargeAmount());
+        } else if (isDue && loanCharge.getChargeCalculation().isPercentageBased()) {
+            cumulative = calculateSpecificDueDateChargeWithPercentage(principalDisbursed, totalInterestChargedForFullLoanTerm, cumulative,
+                    loanCharge, mc);
+        } else if (isDue) {
+            cumulative = cumulative.plus(loanCharge.amount());
+        }
+        return cumulative;
+    }
+
+    private Money cumulativePenaltyChargesDueWithin(final LocalDate periodStart, final LocalDate periodEnd,
+            final Set<LoanCharge> loanCharges, final MonetaryCurrency monetaryCurrency,
+            final PrincipalInterest principalInterestForThisPeriod, final Money principalDisbursed,
+            final Money totalInterestChargedForFullLoanTerm, boolean isInstallmentChargeApplicable, final boolean isFirstPeriod,
+            final MathContext mc) {
+        Money cumulative = Money.zero(monetaryCurrency);
+        for (final LoanCharge loanCharge : loanCharges) {
+            if (loanCharge.isPenaltyCharge()) {
+                cumulative = getCumulativeAmountOfCharge(periodStart, periodEnd, principalInterestForThisPeriod, principalDisbursed,
+                        totalInterestChargedForFullLoanTerm, isInstallmentChargeApplicable, isFirstPeriod, loanCharge, cumulative, mc);
+            }
+        }
+        return cumulative;
+    }
+
+    private Money calculateInstallmentCharge(final PrincipalInterest principalInterestForThisPeriod, Money cumulative,
+            final LoanCharge loanCharge, final MathContext mc) {
+        if (loanCharge.getChargeCalculation().isPercentageBased()) {
+            BigDecimal amount = BigDecimal.ZERO;
+            if (loanCharge.getChargeCalculation().isPercentageOfAmountAndInterest()) {
+                amount = amount.add(principalInterestForThisPeriod.principal().getAmount())
+                        .add(principalInterestForThisPeriod.interest().getAmount());
+            } else if (loanCharge.getChargeCalculation().isPercentageOfInterest()) {
+                amount = amount.add(principalInterestForThisPeriod.interest().getAmount());
+            } else {
+                amount = amount.add(principalInterestForThisPeriod.principal().getAmount());
+            }
+            BigDecimal loanChargeAmt = amount.multiply(loanCharge.getPercentage()).divide(BigDecimal.valueOf(100), mc);
+            cumulative = cumulative.plus(loanChargeAmt);
+        } else {
+            cumulative = cumulative.plus(loanCharge.amountOrPercentage());
+        }
+        return cumulative;
+    }
+
+    private Money calculateSpecificDueDateChargeWithPercentage(final Money principalDisbursed,
+            final Money totalInterestChargedForFullLoanTerm, Money cumulative, final LoanCharge loanCharge, final MathContext mc) {
+        BigDecimal amount = BigDecimal.ZERO;
+        if (loanCharge.getChargeCalculation().isPercentageOfAmountAndInterest()) {
+            amount = amount.add(principalDisbursed.getAmount()).add(totalInterestChargedForFullLoanTerm.getAmount());
+        } else if (loanCharge.getChargeCalculation().isPercentageOfInterest()) {
+            amount = amount.add(totalInterestChargedForFullLoanTerm.getAmount());
+        } else {
+            amount = amount.add(principalDisbursed.getAmount());
+        }
+        BigDecimal loanChargeAmt = amount.multiply(loanCharge.getPercentage()).divide(BigDecimal.valueOf(100), mc);
+        cumulative = cumulative.plus(loanChargeAmt);
+        return cumulative;
+    }
+
+    private void updatePeriodsWithCharges(final MonetaryCurrency currency, LoanScheduleParams scheduleParams,
+            final Collection<LoanScheduleModelPeriod> periods, final Set<LoanCharge> nonCompoundingCharges, MathContext mc) {
+        for (LoanScheduleModelPeriod loanScheduleModelPeriod : periods) {
+            if (loanScheduleModelPeriod.isRepaymentPeriod()) {
+                PrincipalInterest principalInterest = new PrincipalInterest(Money.of(currency, loanScheduleModelPeriod.principalDue()),
+                        Money.of(currency, loanScheduleModelPeriod.interestDue()), null);
+                Money feeChargesForInstallment = cumulativeFeeChargesDueWithin(loanScheduleModelPeriod.periodFromDate(),
+                        loanScheduleModelPeriod.periodDueDate(), nonCompoundingCharges, currency, principalInterest,
+                        scheduleParams.getPrincipalToBeScheduled(), scheduleParams.getTotalCumulativeInterest(),
+                        !loanScheduleModelPeriod.isRecalculatedInterestComponent(), scheduleParams.isFirstPeriod(), mc);
+                Money penaltyChargesForInstallment = cumulativePenaltyChargesDueWithin(loanScheduleModelPeriod.periodFromDate(),
+                        loanScheduleModelPeriod.periodDueDate(), nonCompoundingCharges, currency, principalInterest,
+                        scheduleParams.getPrincipalToBeScheduled(), scheduleParams.getTotalCumulativeInterest(),
+                        !loanScheduleModelPeriod.isRecalculatedInterestComponent(), scheduleParams.isFirstPeriod(), mc);
+                scheduleParams.addTotalFeeChargesCharged(feeChargesForInstallment);
+                scheduleParams.addTotalPenaltyChargesCharged(penaltyChargesForInstallment);
+                scheduleParams.addTotalRepaymentExpected(feeChargesForInstallment.plus(penaltyChargesForInstallment));
+                loanScheduleModelPeriod.addLoanCharges(feeChargesForInstallment.getAmount(), penaltyChargesForInstallment.getAmount());
+            }
+        }
+    }
+
+    private Set<LoanCharge> separateTotalCompoundingPercentageCharges(final Set<LoanCharge> loanCharges) {
+        Set<LoanCharge> interestCharges = new HashSet<>();
+        for (final LoanCharge loanCharge : loanCharges) {
+            if (loanCharge.isSpecifiedDueDate() && (loanCharge.getChargeCalculation().isPercentageOfInterest()
+                    || loanCharge.getChargeCalculation().isPercentageOfAmountAndInterest())) {
+                interestCharges.add(loanCharge);
+            }
+        }
+        loanCharges.removeAll(interestCharges);
+        return interestCharges;
+    }
+
 }
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 c59e1ef07..f21fe4def 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
@@ -19,19 +19,31 @@
 package org.apache.fineract.portfolio.loanproduct.calc;
 
 import java.math.MathContext;
+import java.time.LocalDate;
 import java.util.List;
+import java.util.Optional;
 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.loanaccount.domain.LoanRepaymentScheduleInstallment;
+import org.apache.fineract.portfolio.loanaccount.loanschedule.data.ProgressiveLoanInterestRepaymentModel;
+import org.apache.fineract.portfolio.loanaccount.loanschedule.data.ProgressiveLoanInterestScheduleModel;
+import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleModelRepaymentPeriod;
 import org.apache.fineract.portfolio.loanproduct.domain.LoanProductRelatedDetail;
 
 public interface EMICalculator {
 
-    EMICalculationResult calculateEMIValueAndRateFactors(Money outstandingBalanceAsPerRest,
-            LoanProductRelatedDetail loanProductRelatedDetail, List<? extends LoanScheduleModelPeriod> expectedRepaymentPeriods,
-            Integer actualPeriodNumber, Integer numberOfRepayments, MathContext mc);
+    ProgressiveLoanInterestScheduleModel generateInterestScheduleModel(List<LoanScheduleModelRepaymentPeriod> periods,
+            LoanProductRelatedDetail loanProductRelatedDetail, Integer installmentAmountInMultiplesOf, MathContext mc);
 
-    PrincipalInterest calculatePrincipalInterestComponentsForPeriod(EMICalculationResult emiCalculationResult,
-            Money outstandingBalanceAsPerRest, Integer installmentAmountInMultiplesOf, Integer actualPeriodNumber,
-            Integer actualNoOfRepayments, MathContext mc);
+    ProgressiveLoanInterestScheduleModel generateModel(LoanProductRelatedDetail loanProductRelatedDetail,
+            Integer installmentAmountInMultiplesOf, List<LoanRepaymentScheduleInstallment> repaymentPeriods, MathContext mc);
+
+    Optional<ProgressiveLoanInterestRepaymentModel> findInterestRepaymentPeriod(ProgressiveLoanInterestScheduleModel scheduleModel,
+            LocalDate dueDate);
+
+    void addDisbursement(ProgressiveLoanInterestScheduleModel scheduleModel, LocalDate disbursementDueDate, Money disbursedAmount);
+
+    ProgressiveLoanInterestScheduleModel makeScheduleModelDeepCopy(ProgressiveLoanInterestScheduleModel scheduleModel);
+
+    ProgressiveLoanInterestScheduleModel makeScheduleModelDeepCopy(ProgressiveLoanInterestScheduleModel scheduleModel,
+            LoanProductRelatedDetail loanProductRelatedDetail, Integer installmentAmountInMultiplesOf, 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 564cdfe2e..abdf61a7e 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
@@ -22,43 +22,208 @@ import java.math.BigDecimal;
 import java.math.MathContext;
 import java.time.LocalDate;
 import java.time.Year;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Iterator;
 import java.util.List;
-import java.util.Objects;
+import java.util.Optional;
+import lombok.RequiredArgsConstructor;
 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.domain.LoanScheduleModelPeriod;
-import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.PrincipalInterest;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment;
+import org.apache.fineract.portfolio.loanaccount.loanschedule.data.ProgressiveLoanInterestRepaymentInterestPeriod;
+import org.apache.fineract.portfolio.loanaccount.loanschedule.data.ProgressiveLoanInterestRepaymentModel;
+import org.apache.fineract.portfolio.loanaccount.loanschedule.data.ProgressiveLoanInterestScheduleModel;
+import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleModelRepaymentPeriod;
 import org.apache.fineract.portfolio.loanproduct.domain.LoanProductRelatedDetail;
+import org.apache.fineract.portfolio.loanproduct.mapper.ProgressiveLoanInterestRepaymentModelMapper;
 import org.springframework.stereotype.Component;
 
 @Component
+@RequiredArgsConstructor
 public final class ProgressiveEMICalculator implements EMICalculator {
 
+    private final ProgressiveLoanInterestRepaymentModelMapper progressiveLoanInterestRepaymentModelMapper;
+
     private static final BigDecimal DIVISOR_100 = new BigDecimal("100");
     private static final BigDecimal ONE_WEEK_IN_DAYS = BigDecimal.valueOf(7);
 
+    @Override
+    public ProgressiveLoanInterestScheduleModel generateInterestScheduleModel(final List<LoanScheduleModelRepaymentPeriod> periods,
+            final LoanProductRelatedDetail loanProductRelatedDetail, final Integer installmentAmountInMultiplesOf, final MathContext mc) {
+        final Money zeroAmount = Money.zero(loanProductRelatedDetail.getCurrency());
+        final ArrayList<ProgressiveLoanInterestRepaymentModel> interestRepaymentModelList = new ArrayList<>(periods.size());
+        for (final LoanScheduleModelRepaymentPeriod period : periods) {
+            interestRepaymentModelList
+                    .add(new ProgressiveLoanInterestRepaymentModel(period.periodFromDate(), period.periodDueDate(), zeroAmount));
+        }
+        return new ProgressiveLoanInterestScheduleModel(interestRepaymentModelList, loanProductRelatedDetail,
+                installmentAmountInMultiplesOf, mc);
+    }
+
+    @Override
+    public Optional<ProgressiveLoanInterestRepaymentModel> findInterestRepaymentPeriod(
+            final ProgressiveLoanInterestScheduleModel scheduleModel, final LocalDate repaymentPeriodDueDate) {
+        if (scheduleModel == null || repaymentPeriodDueDate == null) {
+            return Optional.empty();
+        }
+        return scheduleModel.repayments().stream()//
+                .filter(interestRepaymentPeriodItem -> interestRepaymentPeriodItem.getDueDate().isEqual(repaymentPeriodDueDate))//
+                .findFirst();
+    }
+
+    Optional<ProgressiveLoanInterestRepaymentModel> findInterestRepaymentPeriodForDisbursement(
+            final ProgressiveLoanInterestScheduleModel scheduleModel, final LocalDate disbursementDueDate) {
+        if (scheduleModel == null || disbursementDueDate == null) {
+            return Optional.empty();
+        }
+        return scheduleModel.repayments().stream()//
+                .filter(repaymentPeriod -> !disbursementDueDate.isBefore(repaymentPeriod.getFromDate())
+                        && disbursementDueDate.isBefore(repaymentPeriod.getDueDate()))//
+                .findFirst();
+    }
+
+    Optional<ProgressiveLoanInterestRepaymentInterestPeriod> findInterestPeriodForDisbursement(
+            final ProgressiveLoanInterestRepaymentModel repaymentPeriod, final LocalDate disbursementDueDate) {
+        if (repaymentPeriod == null || disbursementDueDate == null) {
+            return Optional.empty();
+        }
+        return repaymentPeriod.getInterestPeriods().stream()//
+                .filter(interestPeriod -> disbursementDueDate.isEqual(interestPeriod.getFromDate()))//
+                .findFirst();
+    }
+
     /**
-     * Calculate Equal Monthly Installment value and Rate Factor -1 values for calculate Interest
+     * Add disbursement to Interest Period
      */
     @Override
-    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 DaysInYearType daysInYearType = DaysInYearType.fromInt(loanProductRelatedDetail.getDaysInYearType());
-        final DaysInMonthType daysInMonthType = DaysInMonthType.fromInt(loanProductRelatedDetail.getDaysInMonthType());
-        final PeriodFrequencyType repaymentFrequency = loanProductRelatedDetail.getRepaymentPeriodFrequencyType();
-        final BigDecimal repaymentEvery = BigDecimal.valueOf(loanProductRelatedDetail.getRepayEvery());
+    public void addDisbursement(final ProgressiveLoanInterestScheduleModel scheduleModel, final LocalDate disbursementDueDate,
+            final Money disbursedAmount) {
+        final ProgressiveLoanInterestRepaymentModel repaymentPeriod = findInterestRepaymentPeriodForDisbursement(scheduleModel,
+                disbursementDueDate).orElse(null);
+        if (repaymentPeriod == null) {
+            return;
+        }
+
+        var interestPeriodOptional = findInterestPeriodForDisbursement(repaymentPeriod, disbursementDueDate);
+        if (interestPeriodOptional.isPresent()) {
+            interestPeriodOptional.get().addDisbursedAmount(disbursedAmount);
+        } else {
+            insertInterestPeriod(repaymentPeriod, disbursementDueDate, disbursedAmount);
+        }
+
+        calculateEMIValueAndRateFactors(repaymentPeriod.getDueDate(), scheduleModel);
+    }
+
+    void insertInterestPeriod(final ProgressiveLoanInterestRepaymentModel repaymentPeriod, final LocalDate disbursementDueDate,
+            final Money disbursedAmount) {
+        // disbursementDueDate is after disb.date because this case when disbursement date is different than interest
+        // period start date
+        final ProgressiveLoanInterestRepaymentInterestPeriod previousInterestPeriod = repaymentPeriod.getInterestPeriods().stream()
+                .filter(interestPeriod -> disbursementDueDate.isAfter(interestPeriod.getFromDate())
+                        && disbursementDueDate.isBefore(interestPeriod.getDueDate()))//
+                .findFirst()//
+                .get();//
+
+        final var interestPeriod = new ProgressiveLoanInterestRepaymentInterestPeriod(disbursementDueDate,
+                previousInterestPeriod.getDueDate(), BigDecimal.ZERO, disbursedAmount, Money.zero(disbursedAmount.getCurrency()));
+
+        previousInterestPeriod.setDueDate(disbursementDueDate);
 
-        final List<BigDecimal> rateFactorList = getRateFactorList(expectedRepaymentPeriods, nominalInterestRatePerPeriod, daysInYearType,
-                daysInMonthType, repaymentFrequency, repaymentEvery, mc);
+        repaymentPeriod.getInterestPeriods().add(interestPeriod);
+        Collections.sort(repaymentPeriod.getInterestPeriods());
+    }
+
+    @Override
+    public ProgressiveLoanInterestScheduleModel makeScheduleModelDeepCopy(final ProgressiveLoanInterestScheduleModel scheduleModel) {
+        return makeScheduleModelDeepCopy(scheduleModel, scheduleModel.loanProductRelatedDetail(),
+                scheduleModel.installmentAmountInMultiplesOf(), scheduleModel.mc());
+    }
 
-        return calculateEMI(rateFactorList, actualPeriodNumber, numberOfRepayments, outstandingBalanceAsPerRest, mc);
+    @Override
+    public ProgressiveLoanInterestScheduleModel makeScheduleModelDeepCopy(final ProgressiveLoanInterestScheduleModel scheduleModel,
+            final LoanProductRelatedDetail loanProductRelatedDetail, final Integer installmentAmountInMultiplesOf, final MathContext mc) {
+        List<ProgressiveLoanInterestRepaymentModel> repayments = new ArrayList<>(scheduleModel.repayments().size());
+        for (var repaymentModel : scheduleModel.repayments()) {
+            repayments.add(new ProgressiveLoanInterestRepaymentModel(repaymentModel));
+        }
+        return new ProgressiveLoanInterestScheduleModel(repayments, loanProductRelatedDetail, installmentAmountInMultiplesOf, mc);
+    }
+
+    /**
+     * Calculate Equal Monthly Installment value and Rate Factor -1 values for calculate Interest
+     */
+    void calculateEMIValueAndRateFactors(final LocalDate calculateFromRepaymentPeriodDueDate,
+            final ProgressiveLoanInterestScheduleModel scheduleModel) {
+        final List<ProgressiveLoanInterestRepaymentModel> relatedRepaymentPeriods = getRelatedRepaymentPeriods(
+                calculateFromRepaymentPeriodDueDate, scheduleModel);
+        calculateRateFactorMinus1ForPeriods(relatedRepaymentPeriods, scheduleModel);
+        calculateEMIOnPeriods(relatedRepaymentPeriods, scheduleModel);
+        calculatePrincipalInterestComponentsForPeriods(scheduleModel);
+        checkAndAdjustEmiIfNeededOnRelatedRepaymentPeriods(scheduleModel, relatedRepaymentPeriods);
+    }
+
+    private static List<ProgressiveLoanInterestRepaymentModel> getRelatedRepaymentPeriods(LocalDate calculateFromRepaymentPeriodDueDate,
+            ProgressiveLoanInterestScheduleModel scheduleModel) {
+        return calculateFromRepaymentPeriodDueDate == null ? scheduleModel.repayments()
+                : scheduleModel.repayments().stream().filter(period -> !period.getDueDate().isBefore(calculateFromRepaymentPeriodDueDate))
+                        .toList();
+    }
+
+    private void checkAndAdjustEmiIfNeededOnRelatedRepaymentPeriods(final ProgressiveLoanInterestScheduleModel scheduleModel,
+            final List<ProgressiveLoanInterestRepaymentModel> relatedRepaymentPeriods) {
+        final Money emiDifference = getDifferenceBetweenLastTwoPeriod(relatedRepaymentPeriods, scheduleModel);
+        final int numberOfRelatedPeriods = relatedRepaymentPeriods.size();
+        double lowerHalfOfRelatedPeriods = Math.floor(numberOfRelatedPeriods / 2.0);
+        if (emiDifference.isZero() || lowerHalfOfRelatedPeriods == 0.0) {
+            return;
+        }
+        final Money originalEmi = relatedRepaymentPeriods.get(numberOfRelatedPeriods - 2).getEqualMonthlyInstallment();
+        boolean shouldBeAdjusted = emiDifference.abs().multipliedBy(100)
+                .isGreaterThan(Money.of(originalEmi.getCurrency(), BigDecimal.valueOf(lowerHalfOfRelatedPeriods)));
+
+        final MathContext mc = scheduleModel.mc();
+        if (shouldBeAdjusted) {
+            Money adjustment = emiDifference.dividedBy(numberOfRelatedPeriods, mc.getRoundingMode());
+            Money adjustedEqualMonthlyInstallmentValue = applyInstallmentAmountInMultiplesOf(scheduleModel, originalEmi.plus(adjustment));
+            if (adjustedEqualMonthlyInstallmentValue.compareTo(originalEmi) == 0) {
+                return;
+            }
+            final LocalDate relatedPeriodsFirstDueDate = relatedRepaymentPeriods.get(0).getDueDate();
+            final var newScheduleModel = makeScheduleModelDeepCopy(scheduleModel);
+            newScheduleModel.repayments().forEach(period -> {
+                if (!period.getDueDate().isBefore(relatedPeriodsFirstDueDate)) {
+                    period.setEqualMonthlyInstallment(adjustedEqualMonthlyInstallmentValue);
+                }
+            });
+            calculatePrincipalInterestComponentsForPeriods(newScheduleModel);
+            final Money newEmiDifference = getDifferenceBetweenLastTwoPeriod(newScheduleModel.repayments(), scheduleModel);
+            final boolean newEmiHasLessDifference = newEmiDifference.abs().compareTo(emiDifference.abs()) < 0;
+            if (!newEmiHasLessDifference) {
+                return;
+            }
+
+            final Iterator<ProgressiveLoanInterestRepaymentModel> relatedPeriodFromNewModelIterator = newScheduleModel//
+                    .repayments().stream()//
+                    .filter(period -> !period.getDueDate().isBefore(relatedPeriodsFirstDueDate))//
+                    .toList().iterator();//
+
+            relatedRepaymentPeriods.forEach(relatedRepaymentPeriod -> {
+                if (!relatedPeriodFromNewModelIterator.hasNext()) {
+                    return;
+                }
+                final ProgressiveLoanInterestRepaymentModel newRepaymentPeriod = relatedPeriodFromNewModelIterator.next();
+                relatedRepaymentPeriod.setEqualMonthlyInstallment(newRepaymentPeriod.getEqualMonthlyInstallment());
+                relatedRepaymentPeriod.setPrincipalDue(newRepaymentPeriod.getInterestDue());
+                relatedRepaymentPeriod.setPrincipalDue(newRepaymentPeriod.getPrincipalDue());
+                relatedRepaymentPeriod.setRemainingBalance(newRepaymentPeriod.getRemainingBalance());
+                relatedRepaymentPeriod.setInterestPeriods(newRepaymentPeriod.getInterestPeriods());
+            });
+        }
     }
 
     /**
@@ -71,83 +236,73 @@ public final class ProgressiveEMICalculator implements EMICalculator {
      *
      * @return Rate Interest Rate in fraction format
      */
-    BigDecimal calcNominalInterestRatePerPeriod(final BigDecimal interestRate, MathContext mc) {
+    BigDecimal calcNominalInterestRatePercentage(final BigDecimal interestRate, MathContext mc) {
         return MathUtil.nullToZero(interestRate).divide(DIVISOR_100, mc);
     }
 
     /**
      * * Calculate rate factors from ONLY repayment periods
-     *
-     * @param expectedRepaymentPeriods
-     * @param interestRate
-     * @param daysInYearType
-     * @param daysInMonthType
-     * @param repaymentFrequency
-     * @param repaymentEvery
-     * @param mc
-     * @return
      */
-    List<BigDecimal> getRateFactorList(final List<? extends LoanScheduleModelPeriod> expectedRepaymentPeriods,
-            final BigDecimal interestRate, final DaysInYearType daysInYearType, final DaysInMonthType daysInMonthType,
-            final PeriodFrequencyType repaymentFrequency, final BigDecimal repaymentEvery, final MathContext mc) {
-        return expectedRepaymentPeriods.stream().filter(LoanScheduleModelPeriod::isRepaymentPeriod)
-                .map(period -> MathUtil.stripTrailingZeros(calculateRateFactorPerPeriod(period, interestRate, daysInYearType,
-                        daysInMonthType, repaymentFrequency, repaymentEvery, mc)))
-                .toList();
+    void calculateRateFactorMinus1ForPeriods(final List<ProgressiveLoanInterestRepaymentModel> repaymentPeriods,
+            final ProgressiveLoanInterestScheduleModel scheduleModel) {
+        for (var repaymentPeriod : repaymentPeriods) {
+            for (var interestPeriod : repaymentPeriod.getInterestPeriods()) {
+                interestPeriod.setRateFactorMinus1(calculateRateFactorMinus1PerPeriod(repaymentPeriod, interestPeriod, scheduleModel));
+            }
+        }
     }
 
     /**
-     * Calculate Rate Factor for an exact Period
-     *
-     * @param repaymentPeriod
-     * @param interestRate
-     * @param daysInYearType
-     * @param daysInMonthType
-     * @param repaymentFrequency
-     * @param repaymentEvery
-     * @param mc
-     * @return
+     * Calculate Rate Factor-1 for an exact Period
      */
-    BigDecimal calculateRateFactorPerPeriod(final LoanScheduleModelPeriod repaymentPeriod, final BigDecimal interestRate,
-            final DaysInYearType daysInYearType, final DaysInMonthType daysInMonthType, final PeriodFrequencyType repaymentFrequency,
-            final BigDecimal repaymentEvery, final MathContext mc) {
-        BigDecimal daysInMonth = BigDecimal.valueOf(daysInMonthType.getNumberOfDays(repaymentPeriod.periodFromDate()));
-        BigDecimal daysInYear = BigDecimal.valueOf(daysInYearType.getNumberOfDays(repaymentPeriod.periodFromDate()));
+    BigDecimal calculateRateFactorMinus1PerPeriod(final ProgressiveLoanInterestRepaymentModel repaymentPeriod,
+            final ProgressiveLoanInterestRepaymentInterestPeriod interestPeriod, final ProgressiveLoanInterestScheduleModel scheduleModel) {
+        final MathContext mc = scheduleModel.mc();
+        final LoanProductRelatedDetail loanProductRelatedDetail = scheduleModel.loanProductRelatedDetail();
+        final BigDecimal interestRate = calcNominalInterestRatePercentage(loanProductRelatedDetail.getNominalInterestRatePerPeriod(), mc);
+        final DaysInYearType daysInYearType = DaysInYearType.fromInt(loanProductRelatedDetail.getDaysInYearType());
+        final DaysInMonthType daysInMonthType = DaysInMonthType.fromInt(loanProductRelatedDetail.getDaysInMonthType());
+        final PeriodFrequencyType repaymentFrequency = loanProductRelatedDetail.getRepaymentPeriodFrequencyType();
+        final BigDecimal repaymentEvery = BigDecimal.valueOf(loanProductRelatedDetail.getRepayEvery());
+
+        final BigDecimal daysInMonth = BigDecimal.valueOf(daysInMonthType.getNumberOfDays(interestPeriod.getFromDate()));
+        final BigDecimal daysInYear = BigDecimal.valueOf(daysInYearType.getNumberOfDays(interestPeriod.getFromDate()));
         final BigDecimal actualDaysInPeriod = BigDecimal
-                .valueOf(DateUtils.getDifferenceInDays(repaymentPeriod.periodFromDate(), repaymentPeriod.periodDueDate()));
+                .valueOf(DateUtils.getDifferenceInDays(interestPeriod.getFromDate(), interestPeriod.getDueDate()));
         final BigDecimal calculatedDaysInPeriod = BigDecimal
-                .valueOf(DateUtils.getDifferenceInDays(repaymentPeriod.periodFromDate(), repaymentPeriod.periodDueDate()));
-        final int numberOfYearsDifferenceInPeriod = repaymentPeriod.periodDueDate().getYear() - repaymentPeriod.periodFromDate().getYear();
+                .valueOf(DateUtils.getDifferenceInDays(repaymentPeriod.getFromDate(), repaymentPeriod.getDueDate()));
+        final int numberOfYearsDifferenceInPeriod = interestPeriod.getDueDate().getYear() - interestPeriod.getFromDate().getYear();
         final boolean partialPeriodCalculationNeeded = daysInYearType == DaysInYearType.ACTUAL && numberOfYearsDifferenceInPeriod > 0;
 
         // TODO check: loanApplicationTerms.calculatePeriodsBetweenDates(startDate, endDate); // calculate period data
         // TODO review: (repayment frequency: days, weeks, years; validation day is month fix 30)
+        // TODO refactor this logic to represent in interest period
         if (partialPeriodCalculationNeeded) {
-            final BigDecimal cumulatedPeriodFractions = calculatePeriodFractions(repaymentPeriod, mc);
+            final BigDecimal cumulatedPeriodFractions = calculatePeriodFractions(interestPeriod, mc);
             return rateFactorByRepaymentPartialPeriod(interestRate, repaymentEvery, cumulatedPeriodFractions, BigDecimal.ONE,
-                    BigDecimal.ONE, mc);
+                    BigDecimal.ONE, mc).setScale(mc.getPrecision(), mc.getRoundingMode());
         }
 
-        return calculateRateFactorPerPeriodBasedOnRepaymentFrequency(interestRate, repaymentFrequency, repaymentEvery, daysInMonth,
-                daysInYear, actualDaysInPeriod, calculatedDaysInPeriod, mc);
+        return calculateRateFactorMinus1PerPeriodBasedOnRepaymentFrequency(interestRate, repaymentFrequency, repaymentEvery, daysInMonth,
+                daysInYear, actualDaysInPeriod, calculatedDaysInPeriod, mc).setScale(mc.getPrecision(), mc.getRoundingMode());
     }
 
     /**
      * Calculate Period fractions part based on how much year has in the period
      *
-     * @param repaymentPeriod
+     * @param interestPeriod
      * @param mc
      * @return
      */
-    BigDecimal calculatePeriodFractions(LoanScheduleModelPeriod repaymentPeriod, MathContext mc) {
+    BigDecimal calculatePeriodFractions(ProgressiveLoanInterestRepaymentInterestPeriod interestPeriod, MathContext mc) {
         BigDecimal cumulatedRateFactor = BigDecimal.ZERO;
-        int actualYear = repaymentPeriod.periodFromDate().getYear();
-        int endYear = repaymentPeriod.periodDueDate().getYear();
-        LocalDate actualDate = repaymentPeriod.periodFromDate();
+        int actualYear = interestPeriod.getFromDate().getYear();
+        int endYear = interestPeriod.getDueDate().getYear();
+        LocalDate actualDate = interestPeriod.getFromDate();
         LocalDate endOfActualYear;
 
         while (actualYear <= endYear) {
-            endOfActualYear = actualYear == endYear ? repaymentPeriod.periodDueDate() : LocalDate.of(actualYear, 12, 31);
+            endOfActualYear = actualYear == endYear ? interestPeriod.getDueDate() : LocalDate.of(actualYear, 12, 31);
             BigDecimal numberOfDaysInYear = BigDecimal.valueOf(Year.of(actualYear).length());
             BigDecimal calculatedDaysInActualYear = BigDecimal.valueOf(DateUtils.getDifferenceInDays(actualDate, endOfActualYear));
             cumulatedRateFactor = cumulatedRateFactor.add(calculatedDaysInActualYear.divide(numberOfDaysInYear, mc), mc);
@@ -170,127 +325,81 @@ public final class ProgressiveEMICalculator implements EMICalculator {
      * @param mc
      * @return
      */
-    BigDecimal calculateRateFactorPerPeriodBasedOnRepaymentFrequency(final BigDecimal interestRate,
+    BigDecimal calculateRateFactorMinus1PerPeriodBasedOnRepaymentFrequency(final BigDecimal interestRate,
             final PeriodFrequencyType repaymentFrequency, final BigDecimal repaymentEvery, final BigDecimal daysInMonth,
             final BigDecimal daysInYear, final BigDecimal actualDaysInPeriod, final BigDecimal calculatedDaysInPeriod,
             final MathContext mc) {
         return switch (repaymentFrequency) {
-            case DAYS ->
-                rateFactorByRepaymentEveryDay(interestRate, repaymentEvery, daysInYear, actualDaysInPeriod, calculatedDaysInPeriod, mc);
-            case WEEKS ->
-                rateFactorByRepaymentEveryWeek(interestRate, repaymentEvery, daysInYear, actualDaysInPeriod, calculatedDaysInPeriod, mc);
-            case MONTHS -> rateFactorByRepaymentEveryMonth(interestRate, repaymentEvery, daysInMonth, daysInYear, actualDaysInPeriod,
+            case DAYS -> rateFactorMinus1ByRepaymentEveryDay(interestRate, repaymentEvery, daysInYear, actualDaysInPeriod,
+                    calculatedDaysInPeriod, mc);
+            case WEEKS -> rateFactorMinus1ByRepaymentEveryWeek(interestRate, repaymentEvery, daysInYear, actualDaysInPeriod,
+                    calculatedDaysInPeriod, mc);
+            case MONTHS -> rateFactorMinus1ByRepaymentEveryMonth(interestRate, repaymentEvery, daysInMonth, daysInYear, actualDaysInPeriod,
                     calculatedDaysInPeriod, mc);
             default -> throw new UnsupportedOperationException("Invalid repayment frequency"); // not supported yet
         };
     }
 
-    /**
-     * Calculate EMI parts and return an EMI calculation result object with repayment installment rate factors
-     */
-    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(outstandingBalanceForRest.getCurrency(),
-                calculateEMIValue(rateFactorN, outstandingBalanceForRest.getAmount(), fnResult, mc));
-        final List<BigDecimal> rateFactorMinus1List = getRateFactorMinus1List(rateFactorList, mc);
-
-        final Money adjustedEqualMonthlyInstallmentValue = adjustEMIForMoreStreamlinedRepaymentSchedule(actualPeriodNumber,
-                numberOfRepayments, outstandingBalanceForRest, 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).
-     */
-    Money adjustEMIForMoreStreamlinedRepaymentSchedule(final Integer actualPeriodNumber, final Integer numberOfRepayments,
-            final Money outstandingBalanceAsPerRest, final Money equalMonthlyInstallmentValue, List<BigDecimal> rateFactorMinus1List,
-            final MathContext mc) {
-        int numberOfUpcomingPeriods = numberOfRepayments - actualPeriodNumber + 1;
-        if (numberOfUpcomingPeriods < 2) {
-            return equalMonthlyInstallmentValue;
+    void calculateEMIOnPeriods(final List<ProgressiveLoanInterestRepaymentModel> repaymentPeriods,
+            final ProgressiveLoanInterestScheduleModel scheduleModel) {
+        if (repaymentPeriods.isEmpty()) {
+            return;
         }
+        final MathContext mc = scheduleModel.mc();
+        final BigDecimal rateFactorN = MathUtil.stripTrailingZeros(calculateRateFactorN(repaymentPeriods, mc));
+        final BigDecimal fnResult = MathUtil.stripTrailingZeros(calculateFnResult(repaymentPeriods, mc));
+        final var startPeriod = repaymentPeriods.get(0);
+        final Money remainingBalanceFromPreviousPeriod = getRemainingBalanceFromPreviousPeriod(scheduleModel, startPeriod);
+        final Money outstandingBalance = remainingBalanceFromPreviousPeriod.add(startPeriod.getDisbursedAmountInPeriod());
+
+        final Money equalMonthlyInstallment = Money.of(outstandingBalance.getCurrency(),
+                calculateEMIValue(rateFactorN, outstandingBalance.getAmount(), fnResult, mc));
+        final Money finalEqualMonthlyInstallment = applyInstallmentAmountInMultiplesOf(scheduleModel, equalMonthlyInstallment);
+
+        repaymentPeriods.forEach(period -> period.setEqualMonthlyInstallment(finalEqualMonthlyInstallment));
+    }
 
-        RepaymentScheduleModel repaymentScheduleModel = generateRepaymentScheduleModel(numberOfRepayments, outstandingBalanceAsPerRest,
-                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(numberOfRepayments,
-                    outstandingBalanceAsPerRest, 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;
-        }
+    Money getRemainingBalanceFromPreviousPeriod(ProgressiveLoanInterestScheduleModel scheduleModel,
+            ProgressiveLoanInterestRepaymentModel startPeriod) {
+        return scheduleModel.repayments().stream().filter(period -> period.getDueDate().isEqual(startPeriod.getFromDate()))
+                .map(ProgressiveLoanInterestRepaymentModel::getRemainingBalance).findFirst()
+                .orElse(Money.zero(scheduleModel.loanProductRelatedDetail().getCurrency()));
     }
 
-    RepaymentScheduleModel generateRepaymentScheduleModel(final Integer numberOfRepayments, final Money outstandingBalanceAsPerRest,
-            Money equalMonthlyInstallmentValue, List<BigDecimal> rateFactorMinus1List) {
-        RepaymentScheduleModel repaymentScheduleModel = new RepaymentScheduleModel();
-        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 == numberOfRepayments - 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;
+    Money applyInstallmentAmountInMultiplesOf(final ProgressiveLoanInterestScheduleModel scheduleModel,
+            final Money equalMonthlyInstallment) {
+        return scheduleModel.installmentAmountInMultiplesOf() != null
+                ? Money.roundToMultiplesOf(equalMonthlyInstallment, scheduleModel.installmentAmountInMultiplesOf())
+                : equalMonthlyInstallment;
     }
 
-    /**
-     * Rate factor -1 values
-     *
-     */
-    List<BigDecimal> getRateFactorMinus1List(final List<BigDecimal> rateFactors, final MathContext mc) {
-        return rateFactors.stream().map(it -> it.subtract(BigDecimal.ONE, mc)).toList();
+    Money getDifferenceBetweenLastTwoPeriod(final List<ProgressiveLoanInterestRepaymentModel> repaymentPeriods,
+            final ProgressiveLoanInterestScheduleModel scheduleModel) {
+        int numberOfUpcomingPeriods = repaymentPeriods.size();
+        if (numberOfUpcomingPeriods < 2) {
+            return Money.zero(scheduleModel.loanProductRelatedDetail().getCurrency());
+        }
+        final var lastPeriod = repaymentPeriods.get(numberOfUpcomingPeriods - 1);
+        final var penultimatePeriod = repaymentPeriods.get(numberOfUpcomingPeriods - 2);
+        return lastPeriod.getEqualMonthlyInstallment().minus(penultimatePeriod.getEqualMonthlyInstallment());
     }
 
     /**
      * Calculate Rate Factor Product from rate factors
      */
-    BigDecimal calculateRateFactorN(final List<BigDecimal> rateFactors, final MathContext mc) {
-        return rateFactors.stream().reduce(BigDecimal.ONE, (BigDecimal acc, BigDecimal value) -> acc.multiply(value, mc));
+    BigDecimal calculateRateFactorN(final List<ProgressiveLoanInterestRepaymentModel> periods, final MathContext mc) {
+        return periods.stream().map(ProgressiveLoanInterestRepaymentModel::getRateFactor).reduce(BigDecimal.ONE,
+                (BigDecimal acc, BigDecimal value) -> acc.multiply(value, mc));
     }
 
     /**
      * Summarize Fn values
      */
-    BigDecimal calculateFnResult(final List<BigDecimal> rateFactors, final MathContext mc) {
-        return rateFactors.stream().skip(1).reduce(BigDecimal.ONE,
-                (BigDecimal previousValue, BigDecimal rateFactor) -> fnValue(previousValue, rateFactor, mc));
+    BigDecimal calculateFnResult(final List<ProgressiveLoanInterestRepaymentModel> periods, final MathContext mc) {
+        return periods.stream()//
+                .skip(1)//
+                .map(ProgressiveLoanInterestRepaymentModel::getRateFactor)//
+                .reduce(BigDecimal.ONE, (BigDecimal previousValue, BigDecimal rateFactor) -> fnValue(previousValue, rateFactor, mc));//
     }
 
     /**
@@ -326,9 +435,10 @@ public final class ProgressiveEMICalculator implements EMICalculator {
      * @param mc
      * @return Rate Factor for period
      */
-    BigDecimal rateFactorByRepaymentEveryDay(final BigDecimal interestRate, final BigDecimal repaymentEvery, final BigDecimal daysInYear,
-            final BigDecimal actualDaysInPeriod, final BigDecimal calculatedDaysInPeriod, final MathContext mc) {
-        return rateFactorByRepaymentPeriod(interestRate, BigDecimal.ONE, repaymentEvery, daysInYear, actualDaysInPeriod,
+    BigDecimal rateFactorMinus1ByRepaymentEveryDay(final BigDecimal interestRate, final BigDecimal repaymentEvery,
+            final BigDecimal daysInYear, final BigDecimal actualDaysInPeriod, final BigDecimal calculatedDaysInPeriod,
+            final MathContext mc) {
+        return rateFactorMinus1ByRepaymentPeriod(interestRate, BigDecimal.ONE, repaymentEvery, daysInYear, actualDaysInPeriod,
                 calculatedDaysInPeriod, mc);
     }
 
@@ -357,9 +467,10 @@ public final class ProgressiveEMICalculator implements EMICalculator {
      * @param mc
      * @return Rate Factor for period
      */
-    BigDecimal rateFactorByRepaymentEveryWeek(final BigDecimal interestRate, final BigDecimal repaymentEvery, final BigDecimal daysInYear,
-            final BigDecimal actualDaysInPeriod, final BigDecimal calculatedDaysInPeriod, final MathContext mc) {
-        return rateFactorByRepaymentPeriod(interestRate, ONE_WEEK_IN_DAYS, repaymentEvery, daysInYear, actualDaysInPeriod,
+    BigDecimal rateFactorMinus1ByRepaymentEveryWeek(final BigDecimal interestRate, final BigDecimal repaymentEvery,
+            final BigDecimal daysInYear, final BigDecimal actualDaysInPeriod, final BigDecimal calculatedDaysInPeriod,
+            final MathContext mc) {
+        return rateFactorMinus1ByRepaymentPeriod(interestRate, ONE_WEEK_IN_DAYS, repaymentEvery, daysInYear, actualDaysInPeriod,
                 calculatedDaysInPeriod, mc);
     }
 
@@ -391,10 +502,10 @@ public final class ProgressiveEMICalculator implements EMICalculator {
      * @param mc
      * @return Rate Factor for period
      */
-    BigDecimal rateFactorByRepaymentEveryMonth(final BigDecimal interestRate, final BigDecimal repaymentEvery, final BigDecimal daysInMonth,
-            final BigDecimal daysInYear, final BigDecimal actualDaysInPeriod, final BigDecimal calculatedDaysInPeriod,
-            final MathContext mc) {
-        return rateFactorByRepaymentPeriod(interestRate, daysInMonth, repaymentEvery, daysInYear, actualDaysInPeriod,
+    BigDecimal rateFactorMinus1ByRepaymentEveryMonth(final BigDecimal interestRate, final BigDecimal repaymentEvery,
+            final BigDecimal daysInMonth, final BigDecimal daysInYear, final BigDecimal actualDaysInPeriod,
+            final BigDecimal calculatedDaysInPeriod, final MathContext mc) {
+        return rateFactorMinus1ByRepaymentPeriod(interestRate, daysInMonth, repaymentEvery, daysInYear, actualDaysInPeriod,
                 calculatedDaysInPeriod, mc);
     }
 
@@ -426,13 +537,14 @@ public final class ProgressiveEMICalculator implements EMICalculator {
      * @param mc
      * @return Rate Factor for period
      */
-    BigDecimal rateFactorByRepaymentPeriod(final BigDecimal interestRate, final BigDecimal repaymentPeriodMultiplierInDays,
+    BigDecimal rateFactorMinus1ByRepaymentPeriod(final BigDecimal interestRate, final BigDecimal repaymentPeriodMultiplierInDays,
             final BigDecimal repaymentEvery, final BigDecimal daysInYear, final BigDecimal actualDaysInPeriod,
             final BigDecimal calculatedDaysInPeriod, final MathContext mc) {
         final BigDecimal interestFractionPerPeriod = repaymentPeriodMultiplierInDays.multiply(repaymentEvery, mc).divide(daysInYear, mc);
-        return BigDecimal.ONE.add(
-                interestRate.multiply(interestFractionPerPeriod, mc).multiply(actualDaysInPeriod, mc).divide(calculatedDaysInPeriod, mc),
-                mc);
+        return interestRate//
+                .multiply(interestFractionPerPeriod, mc)//
+                .multiply(actualDaysInPeriod, mc)//
+                .divide(calculatedDaysInPeriod, mc);//
     }
 
     /**
@@ -442,10 +554,11 @@ public final class ProgressiveEMICalculator implements EMICalculator {
     BigDecimal rateFactorByRepaymentPartialPeriod(final BigDecimal interestRate, final BigDecimal repaymentEvery,
             final BigDecimal cumulatedPeriodRatio, final BigDecimal actualDaysInPeriod, final BigDecimal calculatedDaysInPeriod,
             final MathContext mc) {
-        final BigDecimal interestFractionPerPeriod = repaymentEvery.multiply(cumulatedPeriodRatio);
-        return BigDecimal.ONE.add(
-                interestRate.multiply(interestFractionPerPeriod, mc).multiply(actualDaysInPeriod, mc).divide(calculatedDaysInPeriod, mc),
-                mc);
+        final BigDecimal interestFractionPerPeriod = repaymentEvery.multiply(cumulatedPeriodRatio, mc);
+        return interestRate//
+                .multiply(interestFractionPerPeriod, mc)//
+                .multiply(actualDaysInPeriod, mc)//
+                .divide(calculatedDaysInPeriod, mc);//
     }
 
     /**
@@ -464,30 +577,46 @@ public final class ProgressiveEMICalculator implements EMICalculator {
         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);
+    void calculatePrincipalInterestComponentsForPeriods(final ProgressiveLoanInterestScheduleModel scheduleModel) {
+        Money outstandingBalance = Money.zero(scheduleModel.loanProductRelatedDetail().getCurrency());
+        int numberOfPeriods = scheduleModel.repayments().size();
+        int period = 0;
+        for (var repaymentPeriod : scheduleModel.repayments()) {
+            final boolean isLastPeriod = ++period == numberOfPeriods;
+            outstandingBalance = calculatePrincipalInterestComponentsForPeriod(repaymentPeriod, outstandingBalance, isLastPeriod);
+        }
+    }
+
+    Money calculatePrincipalInterestComponentsForPeriod(final ProgressiveLoanInterestRepaymentModel repaymentPeriod,
+            final Money initBalance, final boolean isLastPeriod) {
+        Money outstandingBalance = initBalance;
+        for (ProgressiveLoanInterestRepaymentInterestPeriod interestPeriod : repaymentPeriod.getInterestPeriods()) {
+            outstandingBalance = outstandingBalance.plus(interestPeriod.getDisbursedAmount());
+            final Money calculatedInterest = outstandingBalance.multipliedBy(interestPeriod.getRateFactorMinus1());
+            interestPeriod.setInterestDue(calculatedInterest);
+        }
+
+        final Money cumulatedInterest = repaymentPeriod.getInterestDue();
+        final Money calculatedPrincipal = isLastPeriod ? outstandingBalance
+                : repaymentPeriod.getEqualMonthlyInstallment().minus(cumulatedInterest);
+
+        if (isLastPeriod) {
+            repaymentPeriod.setEqualMonthlyInstallment(calculatedPrincipal.add(cumulatedInterest));
         }
-        return calculatedPrincipal;
+
+        repaymentPeriod.setPrincipalDue(calculatedPrincipal);
+        repaymentPeriod.setRemainingBalance(outstandingBalance.minus(calculatedPrincipal));
+
+        return repaymentPeriod.getRemainingBalance();
+    }
+
+    @Override
+    public ProgressiveLoanInterestScheduleModel generateModel(LoanProductRelatedDetail loanProductRelatedDetail,
+            Integer installmentAmountInMultiplesOf, List<LoanRepaymentScheduleInstallment> repaymentPeriods, MathContext mc) {
+        List<LoanRepaymentScheduleInstallment> repaymentModelsWithoutDownPayment = repaymentPeriods.stream()
+                .filter(period -> !period.isDownPayment() && !period.isAdditional()).toList();
+        List<ProgressiveLoanInterestRepaymentModel> repaymentModels = progressiveLoanInterestRepaymentModelMapper
+                .map(repaymentModelsWithoutDownPayment);
+        return new ProgressiveLoanInterestScheduleModel(repaymentModels, loanProductRelatedDetail, installmentAmountInMultiplesOf, mc);
     }
 }
diff --git a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/calc/RepaymentPeriodModel.java b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/calc/RepaymentPeriodModel.java
deleted file mode 100644
index de99d69c0..000000000
--- a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/calc/RepaymentPeriodModel.java
+++ /dev/null
@@ -1,24 +0,0 @@
-/**
- * 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.portfolio.loanproduct.calc;
-
-import org.apache.fineract.organisation.monetary.domain.Money;
-
-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/mapper/ProgressiveLoanInterestRepaymentModelMapper.java b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/mapper/ProgressiveLoanInterestRepaymentModelMapper.java
new file mode 100644
index 000000000..a234197a3
--- /dev/null
+++ b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/mapper/ProgressiveLoanInterestRepaymentModelMapper.java
@@ -0,0 +1,37 @@
+/**
+ * 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.portfolio.loanproduct.mapper;
+
+import java.util.List;
+import org.apache.fineract.infrastructure.core.config.MapstructMapperConfig;
+import org.apache.fineract.organisation.monetary.domain.Money;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment;
+import org.apache.fineract.portfolio.loanaccount.loanschedule.data.ProgressiveLoanInterestRepaymentModel;
+import org.mapstruct.Mapper;
+
+@Mapper(config = MapstructMapperConfig.class)
+public interface ProgressiveLoanInterestRepaymentModelMapper {
+
+    List<ProgressiveLoanInterestRepaymentModel> map(List<LoanRepaymentScheduleInstallment> repaymentScheduleInstallments);
+
+    default ProgressiveLoanInterestRepaymentModel map(LoanRepaymentScheduleInstallment repaymentScheduleInstallment) {
+        return new ProgressiveLoanInterestRepaymentModel(repaymentScheduleInstallment.getFromDate(),
+                repaymentScheduleInstallment.getDueDate(), Money.zero(repaymentScheduleInstallment.getLoan().getCurrency()));
+    }
+}
diff --git a/fineract-progressive-loan/src/test/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/AdvancedPaymentScheduleTransactionProcessorTest.java b/fineract-progressive-loan/src/test/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/AdvancedPaymentScheduleTransactionProcessorTest.java
index 551ae9cbd..96afc9934 100644
--- a/fineract-progressive-loan/src/test/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/AdvancedPaymentScheduleTransactionProcessorTest.java
+++ b/fineract-progressive-loan/src/test/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/AdvancedPaymentScheduleTransactionProcessorTest.java
@@ -61,10 +61,10 @@ import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
 import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRelation;
 import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRelationTypeEnum;
 import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionType;
-import org.apache.fineract.portfolio.loanaccount.domain.reaging.LoanReAgingParameterRepository;
-import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.LoanRepaymentScheduleTransactionProcessor.TransactionCtx;
 import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.MoneyHolder;
+import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.TransactionCtx;
 import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleProcessingType;
+import org.apache.fineract.portfolio.loanproduct.calc.EMICalculator;
 import org.apache.fineract.portfolio.loanproduct.domain.AllocationType;
 import org.apache.fineract.portfolio.loanproduct.domain.CreditAllocationTransactionType;
 import org.apache.fineract.portfolio.loanproduct.domain.LoanProductRelatedDetail;
@@ -90,7 +90,7 @@ class AdvancedPaymentScheduleTransactionProcessorTest {
     private static final MonetaryCurrency MONETARY_CURRENCY = new MonetaryCurrency("USD", 2, 1);
     private static final MockedStatic<MoneyHelper> MONEY_HELPER = mockStatic(MoneyHelper.class);
     private AdvancedPaymentScheduleTransactionProcessor underTest;
-    private LoanReAgingParameterRepository reAgingParameterRepository = Mockito.mock(LoanReAgingParameterRepository.class);
+    private final EMICalculator emiCalculator = Mockito.mock(EMICalculator.class);
 
     @BeforeAll
     public static void init() {
@@ -104,7 +104,7 @@ class AdvancedPaymentScheduleTransactionProcessorTest {
 
     @BeforeEach
     public void setUp() {
-        underTest = new AdvancedPaymentScheduleTransactionProcessor(reAgingParameterRepository);
+        underTest = new AdvancedPaymentScheduleTransactionProcessor(emiCalculator);
 
         ThreadLocalContextUtil.setTenant(new FineractPlatformTenant(1L, "default", "Default", "Asia/Kolkata", null));
         ThreadLocalContextUtil.setActionContext(ActionContext.DEFAULT);
@@ -148,7 +148,7 @@ class AdvancedPaymentScheduleTransactionProcessorTest {
         when(loanTransaction.isPenaltyPayment()).thenReturn(false);
 
         underTest.processLatestTransaction(loanTransaction,
-                new TransactionCtx(currency, List.of(installment), Set.of(charge), new MoneyHolder(overpaidAmount)));
+                new TransactionCtx(currency, List.of(installment), Set.of(charge), new MoneyHolder(overpaidAmount), null));
 
         Mockito.verify(installment, times(1)).payFeeChargesComponent(eq(transactionDate), eq(chargeAmountMoney));
         Mockito.verify(loanTransaction, times(1)).updateComponents(refEq(zero), refEq(zero), refEq(chargeAmountMoney), refEq(zero));
@@ -192,7 +192,7 @@ class AdvancedPaymentScheduleTransactionProcessorTest {
         when(loanTransaction.isPenaltyPayment()).thenReturn(false);
 
         underTest.processLatestTransaction(loanTransaction,
-                new TransactionCtx(currency, List.of(installment), Set.of(charge), new MoneyHolder(overpaidAmount)));
+                new TransactionCtx(currency, List.of(installment), Set.of(charge), new MoneyHolder(overpaidAmount), null));
 
         Mockito.verify(installment, times(1)).payFeeChargesComponent(eq(transactionDate), eq(transactionAmountMoney));
         Mockito.verify(loanTransaction, times(1)).updateComponents(refEq(zero), refEq(zero), refEq(transactionAmountMoney), refEq(zero));
@@ -243,7 +243,7 @@ class AdvancedPaymentScheduleTransactionProcessorTest {
         when(loanTransaction.isOn(eq(transactionDate))).thenReturn(true);
 
         underTest.processLatestTransaction(loanTransaction,
-                new TransactionCtx(currency, List.of(installment), Set.of(charge), new MoneyHolder(overpaidAmount)));
+                new TransactionCtx(currency, List.of(installment), Set.of(charge), new MoneyHolder(overpaidAmount), null));
 
         Mockito.verify(installment, times(1)).payFeeChargesComponent(eq(transactionDate), eq(chargeAmountMoney));
         Mockito.verify(loanTransaction, times(1)).updateComponents(refEq(zero), refEq(zero), refEq(chargeAmountMoney), refEq(zero));
@@ -271,7 +271,7 @@ class AdvancedPaymentScheduleTransactionProcessorTest {
         installments.add(installment);
 
         // when
-        TransactionCtx ctx = new TransactionCtx(MONETARY_CURRENCY, installments, null, overpaymentHolder);
+        TransactionCtx ctx = new TransactionCtx(MONETARY_CURRENCY, installments, null, overpaymentHolder, null);
         underTest.processCreditTransaction(chargeBackTransaction, ctx);
 
         // verify principal
@@ -328,7 +328,7 @@ class AdvancedPaymentScheduleTransactionProcessorTest {
         installments.add(installment);
 
         // when
-        TransactionCtx ctx = new TransactionCtx(MONETARY_CURRENCY, installments, null, overpaymentHolder);
+        TransactionCtx ctx = new TransactionCtx(MONETARY_CURRENCY, installments, null, overpaymentHolder, null);
         underTest.processCreditTransaction(chargeBackTransaction, ctx);
 
         // verify charges on installment
@@ -380,7 +380,7 @@ class AdvancedPaymentScheduleTransactionProcessorTest {
         installments.add(installment2);
 
         // when
-        TransactionCtx ctx = new TransactionCtx(MONETARY_CURRENCY, installments, null, overpaymentHolder);
+        TransactionCtx ctx = new TransactionCtx(MONETARY_CURRENCY, installments, null, overpaymentHolder, null);
         underTest.processCreditTransaction(chargeBackTransaction, ctx);
 
         // verify principal
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 aebfdcf88..a837ef533 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
@@ -33,10 +33,8 @@ 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.domain.LoanRepaymentScheduleInstallment;
-import org.apache.fineract.portfolio.loanaccount.loanschedule.data.LoanScheduleModelDownPaymentPeriod;
-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;
+import org.apache.fineract.portfolio.loanaccount.loanschedule.data.ProgressiveLoanInterestScheduleModel;
+import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleModelRepaymentPeriod;
 import org.apache.fineract.portfolio.loanproduct.domain.LoanProductRelatedDetail;
 import org.jetbrains.annotations.NotNull;
 import org.junit.jupiter.api.Assertions;
@@ -50,7 +48,7 @@ import org.mockito.junit.jupiter.MockitoExtension;
 @ExtendWith(MockitoExtension.class)
 class ProgressiveEMICalculatorTest {
 
-    private static final ProgressiveEMICalculator emiCalculator = new ProgressiveEMICalculator();
+    private static final ProgressiveEMICalculator emiCalculator = new ProgressiveEMICalculator(null);
 
     private static MockedStatic<MoneyHelper> moneyHelper = Mockito.mockStatic(MoneyHelper.class);
     private static LoanProductRelatedDetail loanProductRelatedDetail = Mockito.mock(LoanProductRelatedDetail.class);
@@ -82,7 +80,7 @@ class ProgressiveEMICalculatorTest {
         final BigDecimal daysInPeriod = BigDecimal.valueOf(DateUtils.getDifferenceInDays(period.getFromDate(), period.getDueDate()));
         final BigDecimal daysInYear = BigDecimal.valueOf(daysInYearType.getNumberOfDays(period.getFromDate()));
         final BigDecimal daysInMonth = BigDecimal.valueOf(daysInMonthType.getNumberOfDays(period.getFromDate()));
-        return emiCalculator.rateFactorByRepaymentEveryMonth(interestRate, BigDecimal.ONE, daysInMonth, daysInYear, daysInPeriod,
+        return emiCalculator.rateFactorMinus1ByRepaymentEveryMonth(interestRate, BigDecimal.ONE, daysInMonth, daysInYear, daysInPeriod,
                 daysInPeriod, MoneyHelper.getMathContext());
     }
 
@@ -91,12 +89,13 @@ class ProgressiveEMICalculatorTest {
         // Given
         final DaysInYearType daysInYearType = DaysInYearType.DAYS_365;
         final DaysInMonthType daysInMonthType = DaysInMonthType.ACTUAL;
+        final MathContext mc = MoneyHelper.getMathContext();
         final String[] expectedValues = new String[] { "1.00805337534", "1.00753380274", "1.00805337534", "1.00779358904", "1.00805337534",
                 "1.00779358904" };
 
         // Then
         for (LoanRepaymentScheduleInstallment period : periods) {
-            BigDecimal rateFactor = getRateFactorsByMonth(daysInYearType, daysInMonthType, interestRate, period);
+            BigDecimal rateFactor = getRateFactorsByMonth(daysInYearType, daysInMonthType, interestRate, period).add(BigDecimal.ONE, mc);
             Assertions.assertEquals(expectedValues[period.getInstallmentNumber() - 1], rateFactor.toString());
         }
     }
@@ -106,12 +105,13 @@ class ProgressiveEMICalculatorTest {
         // Given
         final DaysInYearType daysInYearType = DaysInYearType.ACTUAL;
         final DaysInMonthType daysInMonthType = DaysInMonthType.ACTUAL;
+        final MathContext mc = MoneyHelper.getMathContext();
         final String[] expectedValues = new String[] { "1.00803137158", "1.00751321858", "1.00803137158", "1.00777229508", "1.00803137158",
                 "1.00777229508" };
 
         // Then
         for (LoanRepaymentScheduleInstallment period : periods) {
-            BigDecimal rateFactor = getRateFactorsByMonth(daysInYearType, daysInMonthType, interestRate, period);
+            BigDecimal rateFactor = getRateFactorsByMonth(daysInYearType, daysInMonthType, interestRate, period).add(BigDecimal.ONE, mc);
             Assertions.assertEquals(expectedValues[period.getInstallmentNumber() - 1], rateFactor.toString());
         }
     }
@@ -128,7 +128,7 @@ class ProgressiveEMICalculatorTest {
         final List<BigDecimal> fnValuesCalculated = new ArrayList<>();
         BigDecimal previousFnValue = BigDecimal.ZERO;
         for (LoanRepaymentScheduleInstallment period : periods) {
-            BigDecimal rateFactor = getRateFactorsByMonth(daysInYearType, daysInMonthType, interestRate, period);
+            BigDecimal rateFactor = getRateFactorsByMonth(daysInYearType, daysInMonthType, interestRate, period).add(BigDecimal.ONE, mc);
 
             final BigDecimal currentFnValue = emiCalculator.fnValue(previousFnValue, rateFactor, mc);
             fnValuesCalculated.add(currentFnValue);
@@ -143,457 +143,535 @@ class ProgressiveEMICalculatorTest {
     }
 
     @Test
-    public void testEMICalculation_principal100_dayInYearsActual_daysInMonthActual_repayEvery1Month() {
+    public void testEMICalculator_generateInterestScheduleModel() {
         final MathContext mc = MoneyHelper.getMathContext();
-        final List<LoanScheduleModelPeriod> expectedRepaymentPeriods = new ArrayList<>();
-        expectedRepaymentPeriods.add(new PreGeneratedLoanSchedulePeriod(1, LocalDate.of(2024, 1, 1), LocalDate.of(2024, 2, 1)));
-        expectedRepaymentPeriods.add(new PreGeneratedLoanSchedulePeriod(2, LocalDate.of(2024, 2, 1), LocalDate.of(2024, 3, 1)));
-        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)));
-        expectedRepaymentPeriods.add(new PreGeneratedLoanSchedulePeriod(5, LocalDate.of(2024, 5, 1), LocalDate.of(2024, 6, 1)));
-        expectedRepaymentPeriods.add(new PreGeneratedLoanSchedulePeriod(6, LocalDate.of(2024, 6, 1), LocalDate.of(2024, 7, 1)));
-
-        final BigDecimal interestRate = BigDecimal.valueOf(9.4822);
-        final BigDecimal principal = BigDecimal.valueOf(100);
-        final Money outstandingBalance = Money.of(monetaryCurrency, principal);
-
-        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(outstandingBalance, loanProductRelatedDetail,
-                expectedRepaymentPeriods, 1, 6, mc);
-
-        // 17.13
-        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());
-        Assertions.assertEquals(BigDecimal.valueOf(0.00803137158), result.getNextRepaymentPeriodRateFactorMinus1());
-        Assertions.assertEquals(BigDecimal.valueOf(0.00777229508), result.getNextRepaymentPeriodRateFactorMinus1());
-        Assertions.assertEquals(BigDecimal.valueOf(0.00803137158), result.getNextRepaymentPeriodRateFactorMinus1());
-        Assertions.assertEquals(BigDecimal.valueOf(0.00777229508), result.getNextRepaymentPeriodRateFactorMinus1());
-
-        // no more period, no more interest
-        Assertions.assertEquals(BigDecimal.valueOf(0), result.getNextRepaymentPeriodRateFactorMinus1());
-
-        result.reset();
-        Assertions.assertEquals(BigDecimal.valueOf(0.00803137158), result.getNextRepaymentPeriodRateFactorMinus1());
+        final List<LoanScheduleModelRepaymentPeriod> expectedRepaymentPeriods = new ArrayList<>();
+        final Integer installmentAmountInMultiplesOf = null;
+
+        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)));
+
+        Mockito.when(loanProductRelatedDetail.getCurrency()).thenReturn(monetaryCurrency);
+
+        final ProgressiveLoanInterestScheduleModel interestScheduleModel = emiCalculator
+                .generateInterestScheduleModel(expectedRepaymentPeriods, loanProductRelatedDetail, installmentAmountInMultiplesOf, mc);
+
+        Assertions.assertTrue(interestScheduleModel != null);
+        Assertions.assertTrue(interestScheduleModel.loanProductRelatedDetail() != null);
+        Assertions.assertTrue(interestScheduleModel.mc() != null);
+        Assertions.assertTrue(interestScheduleModel.installmentAmountInMultiplesOf() == null);
+        Assertions.assertTrue(interestScheduleModel.repayments() != null);
+        Assertions.assertEquals(4, interestScheduleModel.repayments().size());
+        Assertions.assertEquals(121, interestScheduleModel.getLoanTermInDays());
     }
 
     @Test
-    public void testEMICalculation_principal10000_dayInYearsActual_daysInMonthActual_repayEvery1Month_DisbursementInPreviousYear() {
+    public void testEMICalculation_disbursedAmt100_dayInYears360_daysInMonth30_repayEvery1Month() {
         final MathContext mc = MoneyHelper.getMathContext();
-        final List<LoanScheduleModelPeriod> expectedRepaymentPeriods = new ArrayList<>();
-        expectedRepaymentPeriods.add(new PreGeneratedLoanSchedulePeriod(1, LocalDate.of(2023, 12, 12), LocalDate.of(2024, 1, 12)));
-        expectedRepaymentPeriods.add(new PreGeneratedLoanSchedulePeriod(2, LocalDate.of(2024, 1, 12), LocalDate.of(2024, 2, 12)));
-        expectedRepaymentPeriods.add(new PreGeneratedLoanSchedulePeriod(3, LocalDate.of(2024, 2, 12), LocalDate.of(2024, 3, 12)));
-        expectedRepaymentPeriods.add(new PreGeneratedLoanSchedulePeriod(4, LocalDate.of(2024, 3, 12), LocalDate.of(2024, 4, 12)));
-        expectedRepaymentPeriods.add(new PreGeneratedLoanSchedulePeriod(5, LocalDate.of(2024, 4, 12), LocalDate.of(2024, 5, 12)));
-        expectedRepaymentPeriods.add(new PreGeneratedLoanSchedulePeriod(6, LocalDate.of(2024, 5, 12), LocalDate.of(2024, 6, 12)));
-
-        final BigDecimal interestRate = BigDecimal.valueOf(9.4822);
-        final BigDecimal principal = BigDecimal.valueOf(10_000);
-        final Money outstandingBalance = Money.of(monetaryCurrency, principal);
-
-        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 List<LoanScheduleModelRepaymentPeriod> expectedRepaymentPeriods = new ArrayList<>();
 
-        final EMICalculationResult result = emiCalculator.calculateEMIValueAndRateFactors(outstandingBalance, loanProductRelatedDetail,
-                expectedRepaymentPeriods, 1, 6, mc);
+        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)));
 
-        // 1713.12
-        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());
-        Assertions.assertEquals(BigDecimal.valueOf(0.00751321858), result.getNextRepaymentPeriodRateFactorMinus1());
-        Assertions.assertEquals(BigDecimal.valueOf(0.00803137158), result.getNextRepaymentPeriodRateFactorMinus1());
-        Assertions.assertEquals(BigDecimal.valueOf(0.00777229508), result.getNextRepaymentPeriodRateFactorMinus1());
-        Assertions.assertEquals(BigDecimal.valueOf(0.00803137158), result.getNextRepaymentPeriodRateFactorMinus1());
-
-        // no more period, no more interest
-        Assertions.assertEquals(BigDecimal.ZERO, result.getNextRepaymentPeriodRateFactorMinus1());
-    }
-
-    @Test
-    public void testEMICalculation_principal100_dayInYears365_daysInMonthActual_repayEvery1Month() {
-        final MathContext mc = MoneyHelper.getMathContext();
-        final List<LoanScheduleModelPeriod> expectedRepaymentPeriods = new ArrayList<>();
-        expectedRepaymentPeriods.add(new PreGeneratedLoanSchedulePeriod(1, LocalDate.of(2024, 1, 1), LocalDate.of(2024, 2, 1)));
-        expectedRepaymentPeriods.add(new PreGeneratedLoanSchedulePeriod(2, LocalDate.of(2024, 2, 1), LocalDate.of(2024, 3, 1)));
-        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)));
-        expectedRepaymentPeriods.add(new PreGeneratedLoanSchedulePeriod(5, LocalDate.of(2024, 5, 1), LocalDate.of(2024, 6, 1)));
-        expectedRepaymentPeriods.add(new PreGeneratedLoanSchedulePeriod(6, LocalDate.of(2024, 6, 1), LocalDate.of(2024, 7, 1)));
-
-        final BigDecimal interestRate = BigDecimal.valueOf(9.4822);
-        final BigDecimal principal = BigDecimal.valueOf(100);
-        final Money outstandingBalance = Money.of(monetaryCurrency, principal);
+        final BigDecimal interestRate = new BigDecimal("9.4822");
+        final Integer installmentAmountInMultiplesOf = null;
 
         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.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.getCurrency()).thenReturn(monetaryCurrency);
 
-        final EMICalculationResult result = emiCalculator.calculateEMIValueAndRateFactors(outstandingBalance, loanProductRelatedDetail,
-                expectedRepaymentPeriods, 1, 6, mc);
-
-        // 17.13
-        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());
-        Assertions.assertEquals(BigDecimal.valueOf(0.00805337534), result.getNextRepaymentPeriodRateFactorMinus1());
-        Assertions.assertEquals(BigDecimal.valueOf(0.00779358904), result.getNextRepaymentPeriodRateFactorMinus1());
-        Assertions.assertEquals(BigDecimal.valueOf(0.00805337534), result.getNextRepaymentPeriodRateFactorMinus1());
-        Assertions.assertEquals(BigDecimal.valueOf(0.00779358904), result.getNextRepaymentPeriodRateFactorMinus1());
+        final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generateInterestScheduleModel(expectedRepaymentPeriods,
+                loanProductRelatedDetail, installmentAmountInMultiplesOf, mc);
 
-        // no more period, no more interest
-        Assertions.assertEquals(BigDecimal.ZERO, result.getNextRepaymentPeriodRateFactorMinus1());
+        final Money disbursedAmount = Money.of(monetaryCurrency, BigDecimal.valueOf(100));
+        emiCalculator.addDisbursement(interestSchedule, LocalDate.of(2024, 1, 1), disbursedAmount);
 
-        result.reset();
-        // check reset
-        Assertions.assertEquals(BigDecimal.valueOf(0.00805337534), result.getNextRepaymentPeriodRateFactorMinus1());
+        checkDisbursementOnPeriod(interestSchedule, 0, disbursedAmount);
+        checkPeriod(interestSchedule, 0, 0, 17.13, 0.007901833333, 0.79, 16.34, 83.66);
+        checkPeriod(interestSchedule, 1, 0, 17.13, 0.007901833333, 0.66, 16.47, 67.19);
+        checkPeriod(interestSchedule, 2, 0, 17.13, 0.007901833333, 0.53, 16.60, 50.59);
+        checkPeriod(interestSchedule, 3, 0, 17.13, 0.007901833333, 0.40, 16.73, 33.86);
+        checkPeriod(interestSchedule, 4, 0, 17.13, 0.007901833333, 0.27, 16.86, 17.0);
+        checkPeriod(interestSchedule, 5, 0, 17.13, 0.007901833333, 0.13, 17.00, 0.0);
     }
 
     @Test
-    public void testEMICalculation_principal100_dayInYears360_daysInMonth30_repayEvery1Month() {
+    public void testEMICalculation_disbursedAmt100_dayInYears360_daysInMonth30_repayEvery1Month_reschedule() {
         final MathContext mc = MoneyHelper.getMathContext();
-        final List<LoanScheduleModelPeriod> expectedRepaymentPeriods = new ArrayList<>();
-        expectedRepaymentPeriods.add(new PreGeneratedLoanSchedulePeriod(1, LocalDate.of(2024, 1, 1), LocalDate.of(2024, 2, 1)));
-        expectedRepaymentPeriods.add(new PreGeneratedLoanSchedulePeriod(2, LocalDate.of(2024, 2, 1), LocalDate.of(2024, 3, 1)));
-        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)));
-        expectedRepaymentPeriods.add(new PreGeneratedLoanSchedulePeriod(5, LocalDate.of(2024, 5, 1), LocalDate.of(2024, 6, 1)));
-        expectedRepaymentPeriods.add(new PreGeneratedLoanSchedulePeriod(6, LocalDate.of(2024, 6, 1), LocalDate.of(2024, 7, 1)));
-
-        final BigDecimal interestRate = BigDecimal.valueOf(9.4822);
-        final BigDecimal principal = BigDecimal.valueOf(100);
-        final Money outstandingBalance = Money.of(monetaryCurrency, principal);
+        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, 15)));
+        expectedRepaymentPeriods.add(repayment(3, LocalDate.of(2024, 3, 15), LocalDate.of(2024, 4, 15)));
+        expectedRepaymentPeriods.add(repayment(4, LocalDate.of(2024, 4, 15), LocalDate.of(2024, 5, 15)));
+        expectedRepaymentPeriods.add(repayment(5, LocalDate.of(2024, 5, 15), LocalDate.of(2024, 6, 15)));
+        expectedRepaymentPeriods.add(repayment(6, LocalDate.of(2024, 6, 15), LocalDate.of(2024, 7, 15)));
+
+        final BigDecimal interestRate = new BigDecimal("9.4822");
+        final Integer installmentAmountInMultiplesOf = null;
 
         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);
+        Mockito.when(loanProductRelatedDetail.getCurrency()).thenReturn(monetaryCurrency);
 
-        final EMICalculationResult result = emiCalculator.calculateEMIValueAndRateFactors(outstandingBalance, loanProductRelatedDetail,
-                expectedRepaymentPeriods, 1, 6, mc);
-
-        // 17.13
-        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());
-        Assertions.assertEquals(BigDecimal.valueOf(0.00790183333), result.getNextRepaymentPeriodRateFactorMinus1());
-        Assertions.assertEquals(BigDecimal.valueOf(0.00790183333), result.getNextRepaymentPeriodRateFactorMinus1());
-        Assertions.assertEquals(BigDecimal.valueOf(0.00790183333), result.getNextRepaymentPeriodRateFactorMinus1());
-        Assertions.assertEquals(BigDecimal.valueOf(0.00790183333), result.getNextRepaymentPeriodRateFactorMinus1());
+        final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generateInterestScheduleModel(expectedRepaymentPeriods,
+                loanProductRelatedDetail, installmentAmountInMultiplesOf, mc);
 
-        // no more period, no more interest
-        Assertions.assertEquals(BigDecimal.ZERO, result.getNextRepaymentPeriodRateFactorMinus1());
+        final Money disbursedAmount = Money.of(monetaryCurrency, BigDecimal.valueOf(100));
+        emiCalculator.addDisbursement(interestSchedule, LocalDate.of(2024, 1, 1), disbursedAmount);
 
-        result.reset();
-        // check reset
-        Assertions.assertEquals(BigDecimal.valueOf(0.00790183333), result.getNextRepaymentPeriodRateFactorMinus1());
+        checkDisbursementOnPeriod(interestSchedule, 0, disbursedAmount);
+        checkPeriod(interestSchedule, 0, 0, 17.13, 0.007901833333, 0.79, 16.34, 83.66);
+        checkPeriod(interestSchedule, 1, 0, 17.13, 0.007901833333, 0.66, 16.47, 67.19);
+        checkPeriod(interestSchedule, 2, 0, 17.13, 0.007901833333, 0.53, 16.60, 50.59);
+        checkPeriod(interestSchedule, 3, 0, 17.13, 0.007901833333, 0.40, 16.73, 33.86);
+        checkPeriod(interestSchedule, 4, 0, 17.13, 0.007901833333, 0.27, 16.86, 17.0);
+        checkPeriod(interestSchedule, 5, 0, 17.13, 0.007901833333, 0.13, 17.00, 0.0);
     }
 
+    // @Test
+    // public void testEMICalculation_disbursedAmt100_dayInYearsActual_daysInMonthActual_repayEvery1Month_reschedule() {
+    // final MathContext mc = MoneyHelper.getMathContext();
+    // 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, 15)));
+    // expectedRepaymentPeriods.add(repayment(3, LocalDate.of(2024, 3, 15), LocalDate.of(2024, 4, 15)));
+    // expectedRepaymentPeriods.add(repayment(4, LocalDate.of(2024, 4, 15), LocalDate.of(2024, 5, 15)));
+    // expectedRepaymentPeriods.add(repayment(5, LocalDate.of(2024, 5, 15), LocalDate.of(2024, 6, 15)));
+    // expectedRepaymentPeriods.add(repayment(6, LocalDate.of(2024, 6, 15), LocalDate.of(2024, 7, 15)));
+    //
+    // final BigDecimal interestRate = new BigDecimal("9.4822");
+    // final Integer installmentAmountInMultiplesOf = null;
+    //
+    // 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);
+    // Mockito.when(loanProductRelatedDetail.getCurrency()).thenReturn(monetaryCurrency);
+    //
+    // final ProgressiveLoanInterestScheduleModel interestSchedule =
+    // emiCalculator.generateInterestScheduleModel(expectedRepaymentPeriods,
+    // loanProductRelatedDetail, installmentAmountInMultiplesOf, mc);
+    //
+    // final Money disbursedAmount = Money.of(monetaryCurrency, BigDecimal.valueOf(100));
+    // emiCalculator.addDisbursement(interestSchedule, LocalDate.of(2024, 1, 1), disbursedAmount);
+    //
+    // checkDisbursementOnPeriod(interestSchedule, 0, disbursedAmount);
+    // checkPeriod(interestSchedule, 0, 0, 17.18, 0.008053375342, 0.81, 16.37, 83.66);
+    // checkPeriod(interestSchedule, 1, 0, 17.18, 0.011170810959, 0.93, 16.25, 67.38);
+    // checkPeriod(interestSchedule, 2, 0, 17.18, 0.008053375342, 0.54, 16.64, 50.74);
+    // checkPeriod(interestSchedule, 3, 0, 17.18, 0.007793589041, 0.40, 16.78, 33.96);
+    // checkPeriod(interestSchedule, 4, 0, 17.18, 0.008053375342, 0.27, 16.91, 17.05);
+    // checkPeriod(interestSchedule, 5, 0, 17.18, 0.007793589041, 0.13, 17.05, 0.0);
+    // }
+
     @Test
-    public void testEMICalculation_principal100_dayInYears364_daysInMonthDoesntMatter_repayEvery1Week() {
+    public void testEMICalculation_multiDisbursedAmt300InSamePeriod_dayInYears360_daysInMonth30_repayEvery1Month() {
         final MathContext mc = MoneyHelper.getMathContext();
-        final List<LoanScheduleModelPeriod> expectedRepaymentPeriods = new ArrayList<>();
-        expectedRepaymentPeriods.add(new PreGeneratedLoanSchedulePeriod(1, LocalDate.of(2024, 1, 1), LocalDate.of(2024, 1, 8)));
-        expectedRepaymentPeriods.add(new PreGeneratedLoanSchedulePeriod(2, LocalDate.of(2024, 1, 8), LocalDate.of(2024, 1, 15)));
-        expectedRepaymentPeriods.add(new PreGeneratedLoanSchedulePeriod(3, LocalDate.of(2024, 1, 15), LocalDate.of(2024, 1, 22)));
-        expectedRepaymentPeriods.add(new PreGeneratedLoanSchedulePeriod(4, LocalDate.of(2024, 1, 22), LocalDate.of(2024, 1, 29)));
-        expectedRepaymentPeriods.add(new PreGeneratedLoanSchedulePeriod(5, LocalDate.of(2024, 1, 29), LocalDate.of(2024, 1, 5)));
-        expectedRepaymentPeriods.add(new PreGeneratedLoanSchedulePeriod(6, LocalDate.of(2024, 2, 5), LocalDate.of(2024, 2, 12)));
-
-        final BigDecimal interestRate = BigDecimal.valueOf(9.4822);
-        final BigDecimal principal = BigDecimal.valueOf(100);
-        final Money outstandingBalance = Money.of(monetaryCurrency, principal);
-
-        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(outstandingBalance, loanProductRelatedDetail,
-                expectedRepaymentPeriods, 1, 6, mc);
-
-        // 16.77
-        Assertions.assertEquals(BigDecimal.valueOf(16.77), result.getEqualMonthlyInstallmentValue().getAmount());
-
-        final BigDecimal fixValue = new BigDecimal("0.0018235");
-        Assertions.assertEquals(fixValue, result.getNextRepaymentPeriodRateFactorMinus1());
-        Assertions.assertEquals(fixValue, result.getNextRepaymentPeriodRateFactorMinus1());
-        Assertions.assertEquals(fixValue, result.getNextRepaymentPeriodRateFactorMinus1());
-        Assertions.assertEquals(fixValue, result.getNextRepaymentPeriodRateFactorMinus1());
-        Assertions.assertEquals(fixValue, result.getNextRepaymentPeriodRateFactorMinus1());
-        Assertions.assertEquals(fixValue, result.getNextRepaymentPeriodRateFactorMinus1());
-
-        // no more period, no more interest
-        Assertions.assertEquals(BigDecimal.ZERO, result.getNextRepaymentPeriodRateFactorMinus1());
+        final List<LoanScheduleModelRepaymentPeriod> expectedRepaymentPeriods = new ArrayList<>();
 
-        result.reset();
-        // check reset
-        Assertions.assertEquals(fixValue, result.getNextRepaymentPeriodRateFactorMinus1());
-    }
+        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)));
 
-    @Test
-    public void testEMICalculation_principal100_dayInYears360_daysInMonthDoesntMatter_repayEvery2Week() {
-        final MathContext mc = MoneyHelper.getMathContext();
-        final List<LoanScheduleModelPeriod> expectedRepaymentPeriods = new ArrayList<>();
-        expectedRepaymentPeriods.add(new PreGeneratedLoanSchedulePeriod(1, LocalDate.of(2024, 1, 1), LocalDate.of(2024, 1, 15)));
-        expectedRepaymentPeriods.add(new PreGeneratedLoanSchedulePeriod(2, LocalDate.of(2024, 1, 15), LocalDate.of(2024, 1, 29)));
-        expectedRepaymentPeriods.add(new PreGeneratedLoanSchedulePeriod(3, LocalDate.of(2024, 1, 29), LocalDate.of(2024, 2, 12)));
-        expectedRepaymentPeriods.add(new PreGeneratedLoanSchedulePeriod(4, LocalDate.of(2024, 2, 12), LocalDate.of(2024, 2, 26)));
-        expectedRepaymentPeriods.add(new PreGeneratedLoanSchedulePeriod(5, LocalDate.of(2024, 2, 26), LocalDate.of(2024, 3, 11)));
-        expectedRepaymentPeriods.add(new PreGeneratedLoanSchedulePeriod(6, LocalDate.of(2024, 3, 11), LocalDate.of(2024, 3, 25)));
-
-        final BigDecimal interestRate = BigDecimal.valueOf(9.4822);
-        final BigDecimal principal = BigDecimal.valueOf(100);
-        final Money outstandingBalance = Money.of(monetaryCurrency, principal);
+        final BigDecimal interestRate = new BigDecimal("9.4822");
+        final Integer installmentAmountInMultiplesOf = null;
 
         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(outstandingBalance, loanProductRelatedDetail,
-                expectedRepaymentPeriods, 1, 6, mc);
-
-        // 16.88
-        Assertions.assertEquals(new BigDecimal("16.88"), result.getEqualMonthlyInstallmentValue().getAmount());
-
-        final BigDecimal fixValue = new BigDecimal("0.00368752222");
-        Assertions.assertEquals(fixValue, result.getNextRepaymentPeriodRateFactorMinus1());
-        Assertions.assertEquals(fixValue, result.getNextRepaymentPeriodRateFactorMinus1());
-        Assertions.assertEquals(fixValue, result.getNextRepaymentPeriodRateFactorMinus1());
-        Assertions.assertEquals(fixValue, result.getNextRepaymentPeriodRateFactorMinus1());
-        Assertions.assertEquals(fixValue, result.getNextRepaymentPeriodRateFactorMinus1());
-        Assertions.assertEquals(fixValue, result.getNextRepaymentPeriodRateFactorMinus1());
-
-        // no more period, no more interest
-        Assertions.assertEquals(BigDecimal.ZERO, result.getNextRepaymentPeriodRateFactorMinus1());
-
-        result.reset();
-        // check reset
-        Assertions.assertEquals(fixValue, result.getNextRepaymentPeriodRateFactorMinus1());
+        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.getCurrency()).thenReturn(monetaryCurrency);
+
+        final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generateInterestScheduleModel(expectedRepaymentPeriods,
+                loanProductRelatedDetail, installmentAmountInMultiplesOf, mc);
+
+        Money disbursedAmount = Money.of(monetaryCurrency, BigDecimal.valueOf(100));
+        emiCalculator.addDisbursement(interestSchedule, LocalDate.of(2024, 1, 1), disbursedAmount);
+
+        checkDisbursementOnPeriod(interestSchedule, 0, disbursedAmount);
+        checkPeriod(interestSchedule, 0, 0, 17.13, 0.007901833333, 0.79, 16.34, 83.66);
+        checkPeriod(interestSchedule, 1, 0, 17.13, 0.007901833333, 0.66, 16.47, 67.19);
+        checkPeriod(interestSchedule, 2, 0, 17.13, 0.007901833333, 0.53, 16.60, 50.59);
+        checkPeriod(interestSchedule, 3, 0, 17.13, 0.007901833333, 0.40, 16.73, 33.86);
+        checkPeriod(interestSchedule, 4, 0, 17.13, 0.007901833333, 0.27, 16.86, 17.0);
+        checkPeriod(interestSchedule, 5, 0, 17.13, 0.007901833333, 0.13, 17.00, 0.0);
+
+        disbursedAmount = Money.of(monetaryCurrency, BigDecimal.valueOf(200));
+        emiCalculator.addDisbursement(interestSchedule, LocalDate.of(2024, 1, 8), disbursedAmount);
+
+        checkDisbursementOnPeriod(interestSchedule, 0, 300);
+        checkPeriod(interestSchedule, 0, 0, 51.33, 0.001784284946, 0.18, 2.02, 49.31, 250.69);
+        checkPeriod(interestSchedule, 0, 1, 51.33, 0.006117548387, 1.84, 2.02, 49.31, 250.69);
+        checkPeriod(interestSchedule, 1, 0, 51.33, 0.007901833333, 1.98, 49.35, 201.34);
+        checkPeriod(interestSchedule, 2, 0, 51.33, 0.007901833333, 1.59, 49.74, 151.60);
+        checkPeriod(interestSchedule, 3, 0, 51.33, 0.007901833333, 1.20, 50.13, 101.47);
+        checkPeriod(interestSchedule, 4, 0, 51.33, 0.007901833333, 0.80, 50.53, 50.94);
+        checkPeriod(interestSchedule, 5, 0, 51.34, 0.007901833333, 0.40, 50.94, 0.0);
     }
 
     @Test
-    public void testEMICalculation_principal100_dayInYears360_daysInMonthDoesntMatter_repayEvery15Days() {
+    public void testEMICalculation_multiDisbursedAmt200InDifferentPeriod_dayInYears360_daysInMonth30_repayEvery1Month() {
         final MathContext mc = MoneyHelper.getMathContext();
-        final List<LoanScheduleModelPeriod> expectedRepaymentPeriods = new ArrayList<>();
-        expectedRepaymentPeriods.add(new PreGeneratedLoanSchedulePeriod(1, LocalDate.of(2024, 1, 1), LocalDate.of(2024, 1, 16)));
-        expectedRepaymentPeriods.add(new PreGeneratedLoanSchedulePeriod(2, LocalDate.of(2024, 1, 16), LocalDate.of(2024, 1, 31)));
-        expectedRepaymentPeriods.add(new PreGeneratedLoanSchedulePeriod(3, LocalDate.of(2024, 1, 31), LocalDate.of(2024, 2, 15)));
-        expectedRepaymentPeriods.add(new PreGeneratedLoanSchedulePeriod(4, LocalDate.of(2024, 2, 15), LocalDate.of(2024, 3, 1)));
-        expectedRepaymentPeriods.add(new PreGeneratedLoanSchedulePeriod(5, LocalDate.of(2024, 3, 1), LocalDate.of(2024, 3, 16)));
-        expectedRepaymentPeriods.add(new PreGeneratedLoanSchedulePeriod(6, LocalDate.of(2024, 3, 16), LocalDate.of(2024, 3, 31)));
-
-        final BigDecimal interestRate = BigDecimal.valueOf(9.4822);
-        final BigDecimal principal = BigDecimal.valueOf(100);
-        final Money outstandingBalance = Money.of(monetaryCurrency, principal);
+        final List<LoanScheduleModelRepaymentPeriod> expectedRepaymentPeriods = new ArrayList<>();
 
-        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);
+        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)));
 
-        final EMICalculationResult result = emiCalculator.calculateEMIValueAndRateFactors(outstandingBalance, loanProductRelatedDetail,
-                expectedRepaymentPeriods, 1, 6, mc);
+        final BigDecimal interestRate = new BigDecimal("9.4822");
+        final Integer installmentAmountInMultiplesOf = null;
 
-        // 16.90
-        Assertions.assertEquals(new BigDecimal("16.90"), result.getEqualMonthlyInstallmentValue().getAmount());
-
-        final BigDecimal fixValue = new BigDecimal("0.00395091667");
-        Assertions.assertEquals(fixValue, result.getNextRepaymentPeriodRateFactorMinus1());
-        Assertions.assertEquals(fixValue, result.getNextRepaymentPeriodRateFactorMinus1());
-        Assertions.assertEquals(fixValue, result.getNextRepaymentPeriodRateFactorMinus1());
-        Assertions.assertEquals(fixValue, result.getNextRepaymentPeriodRateFactorMinus1());
-        Assertions.assertEquals(fixValue, result.getNextRepaymentPeriodRateFactorMinus1());
-        Assertions.assertEquals(fixValue, result.getNextRepaymentPeriodRateFactorMinus1());
-
-        // no more period, no more interest
-        Assertions.assertEquals(BigDecimal.ZERO, result.getNextRepaymentPeriodRateFactorMinus1());
-
-        result.reset();
-        // check reset
-        Assertions.assertEquals(fixValue, result.getNextRepaymentPeriodRateFactorMinus1());
+        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);
+        Mockito.when(loanProductRelatedDetail.getCurrency()).thenReturn(monetaryCurrency);
+
+        final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generateInterestScheduleModel(expectedRepaymentPeriods,
+                loanProductRelatedDetail, installmentAmountInMultiplesOf, mc);
+
+        Money disbursedAmount = Money.of(monetaryCurrency, BigDecimal.valueOf(100));
+        emiCalculator.addDisbursement(interestSchedule, LocalDate.of(2024, 1, 1), disbursedAmount);
+
+        checkDisbursementOnPeriod(interestSchedule, 0, disbursedAmount);
+        checkPeriod(interestSchedule, 0, 0, 17.13, 0.007901833333, 0.79, 16.34, 83.66);
+        checkPeriod(interestSchedule, 1, 0, 17.13, 0.007901833333, 0.66, 16.47, 67.19);
+        checkPeriod(interestSchedule, 2, 0, 17.13, 0.007901833333, 0.53, 16.60, 50.59);
+        checkPeriod(interestSchedule, 3, 0, 17.13, 0.007901833333, 0.40, 16.73, 33.86);
+        checkPeriod(interestSchedule, 4, 0, 17.13, 0.007901833333, 0.27, 16.86, 17.0);
+        checkPeriod(interestSchedule, 5, 0, 17.13, 0.007901833333, 0.13, 17.00, 0.0);
+
+        disbursedAmount = Money.of(monetaryCurrency, BigDecimal.valueOf(100));
+        emiCalculator.addDisbursement(interestSchedule, LocalDate.of(2024, 2, 15), disbursedAmount);
+
+        checkDisbursementOnPeriod(interestSchedule, 1, disbursedAmount);
+        checkPeriod(interestSchedule, 0, 0, 17.13, 0.007901833333, 0.79, 16.34, 83.66);
+        checkPeriod(interestSchedule, 1, 0, 37.53, 0.003814678161, 0.32, 1.07, 36.46, 147.20);
+        checkPeriod(interestSchedule, 1, 1, 37.53, 0.004087155172, 0.75, 1.07, 36.46, 147.20);
+        checkPeriod(interestSchedule, 2, 0, 37.53, 0.007901833333, 1.16, 36.37, 110.83);
+        checkPeriod(interestSchedule, 3, 0, 37.53, 0.007901833333, 0.88, 36.65, 74.18);
+        checkPeriod(interestSchedule, 4, 0, 37.53, 0.007901833333, 0.59, 36.94, 37.24);
+        checkPeriod(interestSchedule, 5, 0, 37.53, 0.007901833333, 0.29, 37.24, 0.0);
     }
 
     @Test
-    public void testEMICalculation_Disbursement_DownPayment_Principal100_dayInYears360_daysInMonth30_repayEvery1Month() {
+    public void testEMICalculation_disbursedAmt100_dayInYearsActual_daysInMonthActual_repayEvery1Month() {
         final MathContext mc = MoneyHelper.getMathContext();
+        final List<LoanScheduleModelRepaymentPeriod> expectedRepaymentPeriods = List.of(
+                repayment(1, LocalDate.of(2023, 12, 12), LocalDate.of(2024, 1, 12)),
+                repayment(2, LocalDate.of(2024, 1, 12), LocalDate.of(2024, 2, 12)),
+                repayment(3, LocalDate.of(2024, 2, 12), LocalDate.of(2024, 3, 1)),
+                repayment(4, LocalDate.of(2024, 3, 12), LocalDate.of(2024, 4, 1)),
+                repayment(5, LocalDate.of(2024, 4, 12), LocalDate.of(2024, 5, 1)),
+                repayment(6, LocalDate.of(2024, 5, 12), LocalDate.of(2024, 6, 1)));
 
-        final BigDecimal interestRate = BigDecimal.valueOf(9.4822);
-        final Money principal = Money.of(monetaryCurrency, BigDecimal.valueOf(100));
-        final Money downPaymentValue = Money.of(monetaryCurrency, BigDecimal.valueOf(25));
-        final Money outstandingBalance = principal.minus(downPaymentValue);
+        final BigDecimal interestRate = new BigDecimal("9.4822");
+        final Integer installmentAmountInMultiplesOf = null;
 
         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.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);
+        Mockito.when(loanProductRelatedDetail.getCurrency()).thenReturn(monetaryCurrency);
+
+        final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generateInterestScheduleModel(expectedRepaymentPeriods,
+                loanProductRelatedDetail, installmentAmountInMultiplesOf, mc);
 
-        final List<LoanScheduleModelPeriod> expectedRepaymentPeriods = new ArrayList<>();
-        expectedRepaymentPeriods
-                .add(LoanScheduleModelDisbursementPeriod.disbursement(LocalDate.of(2024, 1, 1), principal, BigDecimal.ZERO));
-        expectedRepaymentPeriods
-                .add(LoanScheduleModelDownPaymentPeriod.downPayment(1, LocalDate.of(2024, 1, 1), downPaymentValue, outstandingBalance));
-        expectedRepaymentPeriods.add(new PreGeneratedLoanSchedulePeriod(2, LocalDate.of(2024, 1, 1), LocalDate.of(2024, 2, 1)));
-        expectedRepaymentPeriods.add(new PreGeneratedLoanSchedulePeriod(3, LocalDate.of(2024, 2, 1), LocalDate.of(2024, 3, 1)));
-        expectedRepaymentPeriods.add(new PreGeneratedLoanSchedulePeriod(4, LocalDate.of(2024, 3, 1), LocalDate.of(2024, 4, 1)));
-        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(outstandingBalance, loanProductRelatedDetail,
-                expectedRepaymentPeriods, 1, 5, mc);
-
-        // 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());
-        Assertions.assertEquals(BigDecimal.valueOf(0.00790183333), result.getNextRepaymentPeriodRateFactorMinus1());
-        Assertions.assertEquals(BigDecimal.valueOf(0.00790183333), result.getNextRepaymentPeriodRateFactorMinus1());
-        Assertions.assertEquals(BigDecimal.valueOf(0.00790183333), result.getNextRepaymentPeriodRateFactorMinus1());
-
-        // no more period, no more interest
-        Assertions.assertEquals(BigDecimal.ZERO, result.getNextRepaymentPeriodRateFactorMinus1());
-
-        result.reset();
-        // check reset
-        Assertions.assertEquals(BigDecimal.valueOf(0.00790183333), result.getNextRepaymentPeriodRateFactorMinus1());
+        final Money disbursedAmount = Money.of(monetaryCurrency, BigDecimal.valueOf(100));
+        emiCalculator.addDisbursement(interestSchedule, LocalDate.of(2023, 12, 12), disbursedAmount);
+
+        checkDisbursementOnPeriod(interestSchedule, 0, disbursedAmount);
+        checkPeriod(interestSchedule, 0, 0, 17.13, 0.008044857759, 0.80, 16.33, 83.67);
+        checkPeriod(interestSchedule, 1, 0, 17.13, 0.008031371585, 0.67, 16.46, 67.21);
+        checkPeriod(interestSchedule, 2, 0, 17.13, 0.007513218579, 0.50, 16.63, 50.58);
+        checkPeriod(interestSchedule, 3, 0, 17.13, 0.008031371585, 0.41, 16.72, 33.86);
+        checkPeriod(interestSchedule, 4, 0, 17.13, 0.007772295082, 0.26, 16.87, 16.99);
+        checkPeriod(interestSchedule, 5, 0, 17.13, 0.008031371585, 0.14, 16.99, 0.0);
     }
 
     @Test
-    public void testEMICalculation_Principal1000_NoInterest_repayEvery1Month() {
+    public void testEMICalculation_disbursedAmt1000_NoInterest_repayEvery1Month() {
         final MathContext mc = MoneyHelper.getMathContext();
+        final List<LoanScheduleModelRepaymentPeriod> expectedRepaymentPeriods = List.of(
+                repayment(2, LocalDate.of(2024, 1, 1), LocalDate.of(2024, 2, 1)),
+                repayment(3, LocalDate.of(2024, 2, 1), LocalDate.of(2024, 3, 1)),
+                repayment(4, LocalDate.of(2024, 3, 1), LocalDate.of(2024, 4, 1)),
+                repayment(5, LocalDate.of(2024, 4, 1), LocalDate.of(2024, 5, 1)));
 
-        final BigDecimal interestRate = BigDecimal.valueOf(0);
-        final Money outstandingBalance = Money.of(monetaryCurrency, BigDecimal.valueOf(1000));
+        final BigDecimal interestRate = new BigDecimal("0");
+        final Integer installmentAmountInMultiplesOf = null;
 
         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.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);
+        Mockito.when(loanProductRelatedDetail.getCurrency()).thenReturn(monetaryCurrency);
 
-        final List<LoanScheduleModelPeriod> expectedRepaymentPeriods = new ArrayList<>();
-        expectedRepaymentPeriods.add(new PreGeneratedLoanSchedulePeriod(1, LocalDate.of(2024, 1, 1), LocalDate.of(2024, 2, 1)));
-        expectedRepaymentPeriods.add(new PreGeneratedLoanSchedulePeriod(2, LocalDate.of(2024, 2, 1), LocalDate.of(2024, 3, 1)));
-        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(outstandingBalance, loanProductRelatedDetail,
-                expectedRepaymentPeriods, 1, 4, mc);
+        final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generateInterestScheduleModel(expectedRepaymentPeriods,
+                loanProductRelatedDetail, installmentAmountInMultiplesOf, mc);
 
-        // 250.00
-        Assertions.assertEquals(new BigDecimal("250.00"), result.getEqualMonthlyInstallmentValue().getAmount());
+        final Money disbursedAmount = Money.of(monetaryCurrency, BigDecimal.valueOf(1000));
+        emiCalculator.addDisbursement(interestSchedule, LocalDate.of(2024, 1, 1), disbursedAmount);
 
-        Assertions.assertEquals(BigDecimal.ZERO, result.getNextRepaymentPeriodRateFactorMinus1());
-        Assertions.assertEquals(BigDecimal.ZERO, result.getNextRepaymentPeriodRateFactorMinus1());
-        Assertions.assertEquals(BigDecimal.ZERO, result.getNextRepaymentPeriodRateFactorMinus1());
-        Assertions.assertEquals(BigDecimal.ZERO, result.getNextRepaymentPeriodRateFactorMinus1());
-
-        // no more period, no more interest
-        Assertions.assertEquals(BigDecimal.ZERO, result.getNextRepaymentPeriodRateFactorMinus1());
-
-        result.reset();
-        // check reset
-        Assertions.assertEquals(BigDecimal.ZERO, result.getNextRepaymentPeriodRateFactorMinus1());
+        checkDisbursementOnPeriod(interestSchedule, 0, disbursedAmount);
+        checkPeriod(interestSchedule, 0, 0, 250.0, 0.0, 0.0, 250.0, 750.0);
+        checkPeriod(interestSchedule, 1, 0, 250.0, 0.0, 0.0, 250.0, 500.0);
+        checkPeriod(interestSchedule, 2, 0, 250.0, 0.0, 0.0, 250.0, 250.0);
+        checkPeriod(interestSchedule, 3, 0, 250.0, 0.0, 0.0, 250.0, 0.0);
     }
 
     @Test
-    public void testUnsupportedRepaymentEveryYear() {
+    public void testEMICalculation_disbursedAmt100_dayInYears364_daysInMonthActual_repayEvery1Week() {
         final MathContext mc = MoneyHelper.getMathContext();
-        final List<LoanScheduleModelPeriod> expectedRepaymentPeriods = new ArrayList<>();
+        final List<LoanScheduleModelRepaymentPeriod> expectedRepaymentPeriods = List.of(
+                repayment(1, LocalDate.of(2024, 1, 1), LocalDate.of(2024, 1, 8)),
+                repayment(2, LocalDate.of(2024, 1, 8), LocalDate.of(2024, 1, 15)),
+                repayment(3, LocalDate.of(2024, 1, 15), LocalDate.of(2024, 1, 22)),
+                repayment(4, LocalDate.of(2024, 1, 22), LocalDate.of(2024, 1, 29)),
+                repayment(5, LocalDate.of(2024, 1, 29), LocalDate.of(2024, 2, 5)),
+                repayment(6, LocalDate.of(2024, 2, 5), LocalDate.of(2024, 2, 12)));
 
-        final BigDecimal interestRate = BigDecimal.valueOf(9.4822);
-        final BigDecimal principal = BigDecimal.valueOf(100);
-        final Money outstandingBalance = Money.of(monetaryCurrency, principal);
+        final BigDecimal interestRate = new BigDecimal("9.4822");
+        final Integer installmentAmountInMultiplesOf = null;
 
         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.YEARS);
+        Mockito.when(loanProductRelatedDetail.getDaysInYearType()).thenReturn(DaysInYearType.DAYS_364.getValue());
+        Mockito.when(loanProductRelatedDetail.getDaysInMonthType()).thenReturn(DaysInMonthType.ACTUAL.getValue());
+        Mockito.when(loanProductRelatedDetail.getRepaymentPeriodFrequencyType()).thenReturn(PeriodFrequencyType.WEEKS);
         Mockito.when(loanProductRelatedDetail.getRepayEvery()).thenReturn(1);
+        Mockito.when(loanProductRelatedDetail.getCurrency()).thenReturn(monetaryCurrency);
 
-        expectedRepaymentPeriods.add(new PreGeneratedLoanSchedulePeriod(1, LocalDate.of(2024, 1, 1), LocalDate.of(2024, 1, 16)));
+        final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generateInterestScheduleModel(expectedRepaymentPeriods,
+                loanProductRelatedDetail, installmentAmountInMultiplesOf, mc);
 
-        try {
-            final EMICalculationResult result = emiCalculator.calculateEMIValueAndRateFactors(outstandingBalance, loanProductRelatedDetail,
-                    expectedRepaymentPeriods, 1, 6, mc);
-            Assertions.fail();
-        } catch (Exception e) {
-            Assertions.assertInstanceOf(UnsupportedOperationException.class, e);
-        }
+        final Money disbursedAmount = Money.of(monetaryCurrency, BigDecimal.valueOf(100));
+        emiCalculator.addDisbursement(interestSchedule, LocalDate.of(2024, 1, 1), disbursedAmount);
+
+        checkDisbursementOnPeriod(interestSchedule, 0, disbursedAmount);
+        checkPeriod(interestSchedule, 0, 0, 16.77, 0.001823500000, 0.18, 16.59, 83.41);
+        checkPeriod(interestSchedule, 1, 0, 16.77, 0.001823500000, 0.15, 16.62, 66.79);
+        checkPeriod(interestSchedule, 2, 0, 16.77, 0.001823500000, 0.12, 16.65, 50.14);
+        checkPeriod(interestSchedule, 3, 0, 16.77, 0.001823500000, 0.09, 16.68, 33.46);
+        checkPeriod(interestSchedule, 4, 0, 16.77, 0.001823500000, 0.06, 16.71, 16.75);
+        checkPeriod(interestSchedule, 5, 0, 16.78, 0.001823500000, 0.03, 16.75, 0.0);
     }
 
     @Test
-    public void testUnsupportedRepaymentEveryWholeTerm() {
+    public void testEMICalculation_disbursedAmt100_dayInYears364_daysInMonthActual_repayEvery2Week() {
         final MathContext mc = MoneyHelper.getMathContext();
-        final List<LoanScheduleModelPeriod> expectedRepaymentPeriods = new ArrayList<>();
+        final List<LoanScheduleModelRepaymentPeriod> expectedRepaymentPeriods = List.of(
+                repayment(1, LocalDate.of(2024, 1, 1), LocalDate.of(2024, 1, 15)),
+                repayment(2, LocalDate.of(2024, 1, 15), LocalDate.of(2024, 1, 29)),
+                repayment(3, LocalDate.of(2024, 1, 29), LocalDate.of(2024, 2, 12)));
 
-        final BigDecimal interestRate = BigDecimal.valueOf(9.4822);
-        final BigDecimal principal = BigDecimal.valueOf(100);
-        final Money outstandingBalance = Money.of(monetaryCurrency, principal);
+        final BigDecimal interestRate = new BigDecimal("9.4822");
+        final Integer installmentAmountInMultiplesOf = null;
 
         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.WHOLE_TERM);
-        Mockito.when(loanProductRelatedDetail.getRepayEvery()).thenReturn(1);
+        Mockito.when(loanProductRelatedDetail.getDaysInYearType()).thenReturn(DaysInYearType.DAYS_364.getValue());
+        Mockito.when(loanProductRelatedDetail.getDaysInMonthType()).thenReturn(DaysInMonthType.ACTUAL.getValue());
+        Mockito.when(loanProductRelatedDetail.getRepaymentPeriodFrequencyType()).thenReturn(PeriodFrequencyType.WEEKS);
+        Mockito.when(loanProductRelatedDetail.getRepayEvery()).thenReturn(2);
+        Mockito.when(loanProductRelatedDetail.getCurrency()).thenReturn(monetaryCurrency);
 
-        expectedRepaymentPeriods.add(new PreGeneratedLoanSchedulePeriod(1, LocalDate.of(2024, 1, 1), LocalDate.of(2024, 1, 16)));
+        final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generateInterestScheduleModel(expectedRepaymentPeriods,
+                loanProductRelatedDetail, installmentAmountInMultiplesOf, mc);
 
-        try {
-            final EMICalculationResult result = emiCalculator.calculateEMIValueAndRateFactors(outstandingBalance, loanProductRelatedDetail,
-                    expectedRepaymentPeriods, 1, 6, mc);
-            Assertions.fail();
-        } catch (Exception e) {
-            Assertions.assertInstanceOf(UnsupportedOperationException.class, e);
-        }
+        final Money disbursedAmount = Money.of(monetaryCurrency, BigDecimal.valueOf(100));
+        emiCalculator.addDisbursement(interestSchedule, LocalDate.of(2024, 1, 1), disbursedAmount);
+
+        checkDisbursementOnPeriod(interestSchedule, 0, disbursedAmount);
+        checkPeriod(interestSchedule, 0, 0, 33.57, 0.003647000000, 0.36, 33.21, 66.79);
+        checkPeriod(interestSchedule, 1, 0, 33.57, 0.003647000000, 0.24, 33.33, 33.46);
+        checkPeriod(interestSchedule, 2, 0, 33.58, 0.003647000000, 0.12, 33.46, 0.0);
     }
 
     @Test
-    public void testInvalidRepaymentEveryValue() {
+    public void testEMICalculation_disbursedAmt100_dayInYears360_daysInMonthDoesntMatter_repayEvery15Days() {
         final MathContext mc = MoneyHelper.getMathContext();
-        final List<LoanScheduleModelPeriod> expectedRepaymentPeriods = new ArrayList<>();
+        final List<LoanScheduleModelRepaymentPeriod> expectedRepaymentPeriods = List.of(
+                repayment(1, LocalDate.of(2024, 1, 1), LocalDate.of(2024, 1, 16)),
+                repayment(2, LocalDate.of(2024, 1, 16), LocalDate.of(2024, 1, 31)),
+                repayment(3, LocalDate.of(2024, 1, 31), LocalDate.of(2024, 2, 15)),
+                repayment(4, LocalDate.of(2024, 2, 15), LocalDate.of(2024, 3, 1)),
+                repayment(5, LocalDate.of(2024, 3, 1), LocalDate.of(2024, 3, 16)),
+                repayment(6, LocalDate.of(2024, 3, 16), LocalDate.of(2024, 3, 31)));
 
-        final BigDecimal interestRate = BigDecimal.valueOf(9.4822);
-        final BigDecimal principal = BigDecimal.valueOf(100);
-        final Money outstandingBalance = Money.of(monetaryCurrency, principal);
+        final BigDecimal interestRate = new BigDecimal("9.4822");
+        final Integer installmentAmountInMultiplesOf = null;
 
         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.INVALID);
-        Mockito.when(loanProductRelatedDetail.getRepayEvery()).thenReturn(1);
+        Mockito.when(loanProductRelatedDetail.getRepaymentPeriodFrequencyType()).thenReturn(PeriodFrequencyType.DAYS);
+        Mockito.when(loanProductRelatedDetail.getRepayEvery()).thenReturn(15);
+        Mockito.when(loanProductRelatedDetail.getCurrency()).thenReturn(monetaryCurrency);
 
-        expectedRepaymentPeriods.add(new PreGeneratedLoanSchedulePeriod(1, LocalDate.of(2024, 1, 1), LocalDate.of(2024, 1, 16)));
+        final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generateInterestScheduleModel(expectedRepaymentPeriods,
+                loanProductRelatedDetail, installmentAmountInMultiplesOf, mc);
 
-        try {
-            final EMICalculationResult result = emiCalculator.calculateEMIValueAndRateFactors(outstandingBalance, loanProductRelatedDetail,
-                    expectedRepaymentPeriods, 1, 6, mc);
-            Assertions.fail();
-        } catch (Exception e) {
-            Assertions.assertInstanceOf(UnsupportedOperationException.class, e);
-        }
+        final Money disbursedAmount = Money.of(monetaryCurrency, BigDecimal.valueOf(100));
+        emiCalculator.addDisbursement(interestSchedule, LocalDate.of(2024, 1, 1), disbursedAmount);
+
+        checkDisbursementOnPeriod(interestSchedule, 0, disbursedAmount);
+        checkPeriod(interestSchedule, 0, 0, 16.90, 0.003950916667, 0.40, 16.50, 83.50);
+        checkPeriod(interestSchedule, 1, 0, 16.90, 0.003950916667, 0.33, 16.57, 66.93);
+        checkPeriod(interestSchedule, 2, 0, 16.90, 0.003950916667, 0.26, 16.64, 50.29);
+        checkPeriod(interestSchedule, 3, 0, 16.90, 0.003950916667, 0.20, 16.70, 33.59);
+        checkPeriod(interestSchedule, 4, 0, 16.90, 0.003950916667, 0.13, 16.77, 16.82);
+        checkPeriod(interestSchedule, 5, 0, 16.89, 0.003950916667, 0.07, 16.82, 0.0);
+    }
+
+    //
+    // @Test
+    // public void testEMICalculation_Principal1000_NoInterest_repayEvery1Month() {
+    // final MathContext mc = MoneyHelper.getMathContext();
+    //
+    // final BigDecimal interestRate = BigDecimal.valueOf(0);
+    // final Money outstandingBalance = Money.of(monetaryCurrency, BigDecimal.valueOf(1000));
+    //
+    // 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 List<LoanScheduleModelPeriod> 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)));
+    //
+    // final EMICalculationResult result = emiCalculator.calculateEMIValueAndRateFactors(outstandingBalance,
+    // loanProductRelatedDetail,
+    // expectedRepaymentPeriods, 1, 4, mc);
+    //
+    // // 250.00
+    // Assertions.assertEquals(new BigDecimal("250.00"), result.getEqualMonthlyInstallmentValue().getAmount());
+    //
+    // Assertions.assertEquals(BigDecimal.ZERO, result.getNextRepaymentPeriodRateFactorMinus1());
+    // Assertions.assertEquals(BigDecimal.ZERO, result.getNextRepaymentPeriodRateFactorMinus1());
+    // Assertions.assertEquals(BigDecimal.ZERO, result.getNextRepaymentPeriodRateFactorMinus1());
+    // Assertions.assertEquals(BigDecimal.ZERO, result.getNextRepaymentPeriodRateFactorMinus1());
+    //
+    // // no more period, no more interest
+    // Assertions.assertEquals(BigDecimal.ZERO, result.getNextRepaymentPeriodRateFactorMinus1());
+    //
+    // result.reset();
+    // // check reset
+    // Assertions.assertEquals(BigDecimal.ZERO, result.getNextRepaymentPeriodRateFactorMinus1());
+    // }
+    //
+    // @Test
+    // public void testUnsupportedRepaymentEveryYear() {
+    // final MathContext mc = MoneyHelper.getMathContext();
+    // final List<LoanScheduleModelPeriod> expectedRepaymentPeriods = new ArrayList<>();
+    //
+    // final BigDecimal interestRate = BigDecimal.valueOf(9.4822);
+    // final BigDecimal principal = BigDecimal.valueOf(100);
+    // final Money outstandingBalance = Money.of(monetaryCurrency, principal);
+    //
+    // 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.YEARS);
+    // Mockito.when(loanProductRelatedDetail.getRepayEvery()).thenReturn(1);
+    //
+    // expectedRepaymentPeriods.add(repayment(1, LocalDate.of(2024, 1, 1), LocalDate.of(2024, 1, 16)));
+    //
+    // try {
+    // final EMICalculationResult result = emiCalculator.calculateEMIValueAndRateFactors(outstandingBalance,
+    // loanProductRelatedDetail,
+    // expectedRepaymentPeriods, 1, 6, mc);
+    // Assertions.fail();
+    // } catch (Exception e) {
+    // Assertions.assertInstanceOf(UnsupportedOperationException.class, e);
+    // }
+    // }
+    //
+    // @Test
+    // public void testUnsupportedRepaymentEveryWholeTerm() {
+    // final MathContext mc = MoneyHelper.getMathContext();
+    // final List<LoanScheduleModelPeriod> expectedRepaymentPeriods = new ArrayList<>();
+    //
+    // final BigDecimal interestRate = BigDecimal.valueOf(9.4822);
+    // final BigDecimal principal = BigDecimal.valueOf(100);
+    // final Money outstandingBalance = Money.of(monetaryCurrency, principal);
+    //
+    // 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.WHOLE_TERM);
+    // Mockito.when(loanProductRelatedDetail.getRepayEvery()).thenReturn(1);
+    //
+    // expectedRepaymentPeriods.add(repayment(1, LocalDate.of(2024, 1, 1), LocalDate.of(2024, 1, 16)));
+    //
+    // try {
+    // final EMICalculationResult result = emiCalculator.calculateEMIValueAndRateFactors(outstandingBalance,
+    // loanProductRelatedDetail,
+    // expectedRepaymentPeriods, 1, 6, mc);
+    // Assertions.fail();
+    // } catch (Exception e) {
+    // Assertions.assertInstanceOf(UnsupportedOperationException.class, e);
+    // }
+    // }
+    //
+    // @Test
+    // public void testInvalidRepaymentEveryValue() {
+    // final MathContext mc = MoneyHelper.getMathContext();
+    // final List<LoanScheduleModelPeriod> expectedRepaymentPeriods = new ArrayList<>();
+    //
+    // final BigDecimal interestRate = BigDecimal.valueOf(9.4822);
+    // final BigDecimal principal = BigDecimal.valueOf(100);
+    // final Money outstandingBalance = Money.of(monetaryCurrency, principal);
+    //
+    // 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.INVALID);
+    // Mockito.when(loanProductRelatedDetail.getRepayEvery()).thenReturn(1);
+    //
+    // expectedRepaymentPeriods.add(repayment(1, LocalDate.of(2024, 1, 1), LocalDate.of(2024, 1, 16)));
+    //
+    // try {
+    // final EMICalculationResult result = emiCalculator.calculateEMIValueAndRateFactors(outstandingBalance,
+    // loanProductRelatedDetail,
+    // expectedRepaymentPeriods, 1, 6, mc);
+    // Assertions.fail();
+    // } catch (Exception e) {
+    // Assertions.assertInstanceOf(UnsupportedOperationException.class, e);
+    // }
+    // }
+
+    private static LoanScheduleModelRepaymentPeriod repayment(int periodNumber, LocalDate fromDate, LocalDate dueDate) {
+        final Money zeroAmount = Money.zero(monetaryCurrency);
+        return LoanScheduleModelRepaymentPeriod.repayment(periodNumber, fromDate, dueDate, zeroAmount, zeroAmount, zeroAmount, zeroAmount,
+                zeroAmount, zeroAmount, false);
     }
 
     @NotNull
@@ -605,4 +683,41 @@ class ProgressiveEMICalculatorTest {
 
         return period;
     }
+
+    private static void checkPeriod(final ProgressiveLoanInterestScheduleModel interestScheduleModel, final int repaymentIdx,
+            final int interestIdx, final double emiValue, final double rateFactorMinus1, final double interestDue,
+            final double principalDue, final double remaingBalance) {
+        checkPeriod(interestScheduleModel, repaymentIdx, interestIdx, emiValue, rateFactorMinus1, interestDue, interestDue, principalDue,
+                remaingBalance);
+    }
+
+    private static void checkPeriod(final ProgressiveLoanInterestScheduleModel interestScheduleModel, final int repaymentIdx,
+            final int interestIdx, final double emiValue, final double rateFactorMinus1, final double interestDue,
+            final double interestDueCumulated, final double principalDue, final double remaingBalance) {
+        final var repaymentPeriod = interestScheduleModel.repayments().get(repaymentIdx);
+        final var interestPeriod = repaymentPeriod.getInterestPeriods().get(interestIdx);
+
+        Assertions.assertEquals(emiValue, toDouble(repaymentPeriod.getEqualMonthlyInstallment().getAmount()));
+        Assertions.assertEquals(rateFactorMinus1, toDouble(interestPeriod.getRateFactorMinus1()));
+        Assertions.assertEquals(interestDue, toDouble(interestPeriod.getInterestDue().getAmount()));
+        Assertions.assertEquals(interestDueCumulated, toDouble(repaymentPeriod.getInterestDue().getAmount()));
+        Assertions.assertEquals(principalDue, toDouble(repaymentPeriod.getPrincipalDue().getAmount()));
+        Assertions.assertEquals(remaingBalance, toDouble(repaymentPeriod.getRemainingBalance().getAmount()));
+    }
+
+    private static void checkDisbursementOnPeriod(final ProgressiveLoanInterestScheduleModel interestScheduleModel, final int repaymentIdx,
+            final Money disbursedAmount) {
+        checkDisbursementOnPeriod(interestScheduleModel, repaymentIdx, disbursedAmount.getAmount().doubleValue());
+    }
+
+    private static void checkDisbursementOnPeriod(final ProgressiveLoanInterestScheduleModel interestScheduleModel, final int repaymentIdx,
+            final double disbursedAmount) {
+        final var repaymentPeriod = interestScheduleModel.repayments().get(repaymentIdx);
+        Assertions.assertEquals(disbursedAmount, toDouble(repaymentPeriod.getDisbursedAmountInPeriod().getAmount()));
+    }
+
+    private static double toDouble(final BigDecimal value) {
+        return value == null ? 0 : value.doubleValue();
+    }
+
 }
diff --git a/fineract-provider/src/main/java/org/apache/fineract/cob/loan/ApplyChargeToOverdueLoansBusinessStep.java b/fineract-provider/src/main/java/org/apache/fineract/cob/loan/ApplyChargeToOverdueLoansBusinessStep.java
index 3b89896bc..86a26eceb 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/cob/loan/ApplyChargeToOverdueLoansBusinessStep.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/cob/loan/ApplyChargeToOverdueLoansBusinessStep.java
@@ -37,7 +37,6 @@ public class ApplyChargeToOverdueLoansBusinessStep implements LoanCOBBusinessSte
     public Loan execute(Loan loan) {
         final Collection<OverdueLoanScheduleData> overdueLoanScheduleDataList = loanReadPlatformService
                 .retrieveAllOverdueInstallmentsForLoan(loan);
-
         loanChargeWritePlatformService.applyOverdueChargesForLoan(loan.getId(), overdueLoanScheduleDataList);
         return loan;
     }
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/DefaultScheduledDateGenerator.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/DefaultScheduledDateGenerator.java
index 83245dfbb..eb077751e 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/DefaultScheduledDateGenerator.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/DefaultScheduledDateGenerator.java
@@ -27,6 +27,7 @@ import net.fortuna.ical4j.model.Recur;
 import org.apache.fineract.infrastructure.core.service.DateUtils;
 import org.apache.fineract.organisation.holiday.domain.Holiday;
 import org.apache.fineract.organisation.holiday.service.HolidayUtil;
+import org.apache.fineract.organisation.monetary.domain.Money;
 import org.apache.fineract.organisation.workingdays.data.AdjustedDateDetailsDTO;
 import org.apache.fineract.organisation.workingdays.domain.RepaymentRescheduleType;
 import org.apache.fineract.organisation.workingdays.service.WorkingDaysUtil;
@@ -36,6 +37,7 @@ import org.apache.fineract.portfolio.calendar.domain.CalendarHistory;
 import org.apache.fineract.portfolio.calendar.service.CalendarUtils;
 import org.apache.fineract.portfolio.common.domain.PeriodFrequencyType;
 import org.apache.fineract.portfolio.loanaccount.data.HolidayDetailDTO;
+import org.apache.fineract.portfolio.loanaccount.data.LoanTermVariationsData;
 import org.springframework.stereotype.Component;
 
 @Component
@@ -43,30 +45,46 @@ import org.springframework.stereotype.Component;
 public class DefaultScheduledDateGenerator implements ScheduledDateGenerator {
 
     @Override
-    public List<PreGeneratedLoanSchedulePeriod> generateRepaymentPeriods(final LocalDate scheduledDueDate,
+    public List<LoanScheduleModelRepaymentPeriod> generateRepaymentPeriods(final LocalDate scheduleStartDate,
             final LoanApplicationTerms loanApplicationTerms, final HolidayDetailDTO holidayDetailDTO) {
+        final Money zeroAmount = Money.zero(loanApplicationTerms.getCurrency());
         final int numberOfRepayments = loanApplicationTerms.getNumberOfRepayments();
-        final ArrayList<PreGeneratedLoanSchedulePeriod> repaymentPeriods = new ArrayList<>(numberOfRepayments);
-        LocalDate lastRepaymentDate = scheduledDueDate;
+        final ArrayList<LoanScheduleModelRepaymentPeriod> repaymentPeriods = new ArrayList<>(numberOfRepayments);
+        LocalDate lastRepaymentDate = scheduleStartDate;
         LocalDate nextRepaymentDate;
         boolean isFirstRepayment = true;
-        for (int repaymentPeriod = 1; repaymentPeriod <= numberOfRepayments; repaymentPeriod++) {
+        for (int repaymentPeriodNumber = 1; repaymentPeriodNumber <= numberOfRepayments; repaymentPeriodNumber++) {
             nextRepaymentDate = generateNextRepaymentDate(lastRepaymentDate, loanApplicationTerms, isFirstRepayment);
 
-            if (repaymentPeriod == numberOfRepayments) { // last period
+            if (repaymentPeriodNumber == numberOfRepayments) { // last period
                 if (loanApplicationTerms.getFixedLength() != null
                         && loanApplicationTerms.calculateMaxDateForFixedLength().compareTo(nextRepaymentDate) != 0) {
                     nextRepaymentDate = loanApplicationTerms.calculateMaxDateForFixedLength();
                 }
                 nextRepaymentDate = adjustRepaymentDate(nextRepaymentDate, loanApplicationTerms, holidayDetailDTO).getChangedScheduleDate();
             }
-            repaymentPeriods.add(new PreGeneratedLoanSchedulePeriod(repaymentPeriod, lastRepaymentDate, nextRepaymentDate));
+            nextRepaymentDate = applyLoanTermVariations(loanApplicationTerms, nextRepaymentDate);
+            repaymentPeriods.add(LoanScheduleModelRepaymentPeriod.repayment(repaymentPeriodNumber, lastRepaymentDate, nextRepaymentDate,
+                    zeroAmount, zeroAmount, zeroAmount, zeroAmount, zeroAmount, zeroAmount, false));
             lastRepaymentDate = nextRepaymentDate;
             isFirstRepayment = false;
         }
         return repaymentPeriods;
     }
 
+    private LocalDate applyLoanTermVariations(final LoanApplicationTerms loanApplicationTerms, final LocalDate scheduledDueDate) {
+        LocalDate modifiedScheduledDueDate = scheduledDueDate;
+        // due date changes should be applied only for that dueDate
+        if (loanApplicationTerms.getLoanTermVariations().hasDueDateVariation(scheduledDueDate)) {
+            LoanTermVariationsData loanTermVariationsData = loanApplicationTerms.getLoanTermVariations().nextDueDateVariation();
+            if (DateUtils.isEqual(modifiedScheduledDueDate, loanTermVariationsData.getTermVariationApplicableFrom())) {
+                modifiedScheduledDueDate = loanTermVariationsData.getDateValue();
+                loanApplicationTerms.updateVariationDays(DateUtils.getDifferenceInDays(scheduledDueDate, modifiedScheduledDueDate));
+            }
+        }
+        return modifiedScheduledDueDate;
+    }
+
     @Override
     public LocalDate getLastRepaymentDate(final LoanApplicationTerms loanApplicationTerms, final HolidayDetailDTO holidayDetailDTO) {
 
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanChargeWritePlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanChargeWritePlatformServiceImpl.java
index 4f14bd250..12295fd05 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanChargeWritePlatformServiceImpl.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanChargeWritePlatformServiceImpl.java
@@ -114,8 +114,8 @@ import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRelationT
 import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRepository;
 import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionType;
 import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.LoanRepaymentScheduleTransactionProcessor;
-import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.LoanRepaymentScheduleTransactionProcessor.TransactionCtx;
 import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.MoneyHolder;
+import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.TransactionCtx;
 import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.impl.AdvancedPaymentScheduleTransactionProcessor;
 import org.apache.fineract.portfolio.loanaccount.exception.InstallmentNotFoundException;
 import org.apache.fineract.portfolio.loanaccount.exception.InvalidLoanTransactionTypeException;
@@ -757,7 +757,6 @@ public class LoanChargeWritePlatformServiceImpl implements LoanChargeWritePlatfo
     @Transactional
     @Override
     public void applyOverdueChargesForLoan(final Long loanId, Collection<OverdueLoanScheduleData> overdueLoanScheduleDataList) {
-
         Loan loan = this.loanAssembler.assembleFrom(loanId);
         if (loan.isChargedOff()) {
             log.warn("Adding charge to Loan: {} is not allowed. Loan Account is Charged-off", loanId);
@@ -844,7 +843,7 @@ public class LoanChargeWritePlatformServiceImpl implements LoanChargeWritePlatfo
                 .determineProcessor(loan.transactionProcessingStrategy());
         loanRepaymentScheduleTransactionProcessor.processLatestTransaction(loanChargeAdjustmentTransaction,
                 new TransactionCtx(loan.getCurrency(), loan.getRepaymentScheduleInstallments(), loan.getActiveCharges(),
-                        new MoneyHolder(loan.getTotalOverpaidAsMoney())));
+                        new MoneyHolder(loan.getTotalOverpaidAsMoney()), null));
 
         loan.addLoanTransaction(loanChargeAdjustmentTransaction);
         loan.updateLoanSummaryAndStatus();
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java
index 823a99201..8e02a793c 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java
@@ -175,6 +175,7 @@ import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionToRepayme
 import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionType;
 import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.LoanRepaymentScheduleTransactionProcessor;
 import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.MoneyHolder;
+import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.TransactionCtx;
 import org.apache.fineract.portfolio.loanaccount.exception.DateMismatchException;
 import org.apache.fineract.portfolio.loanaccount.exception.ExceedingTrancheCountException;
 import org.apache.fineract.portfolio.loanaccount.exception.InvalidLoanTransactionTypeException;
@@ -1111,9 +1112,8 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
         if (isTransactionChronologicallyLatest && (!reprocess || !loan.getLoanRepaymentScheduleDetail().isInterestRecalculationEnabled())
                 && !loan.isForeclosure()) {
             loanRepaymentScheduleTransactionProcessor.processLatestTransaction(newInterestPaymentWaiverTransaction,
-                    new LoanRepaymentScheduleTransactionProcessor.TransactionCtx(loan.getCurrency(),
-                            loan.getRepaymentScheduleInstallments(), loan.getActiveCharges(),
-                            new MoneyHolder(loan.getTotalOverpaidAsMoney())));
+                    new TransactionCtx(loan.getCurrency(), loan.getRepaymentScheduleInstallments(), loan.getActiveCharges(),
+                            new MoneyHolder(loan.getTotalOverpaidAsMoney()), null));
             reprocess = false;
             if (loan.repaymentScheduleDetail().isInterestRecalculationEnabled()) {
                 if (currentInstallment == null || currentInstallment.isNotFullyPaidOff()) {
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/reaging/LoanReAgingServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/reaging/LoanReAgingServiceImpl.java
index 33909fa7d..5fccb83f9 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/reaging/LoanReAgingServiceImpl.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/reaging/LoanReAgingServiceImpl.java
@@ -50,6 +50,7 @@ import org.apache.fineract.portfolio.loanaccount.domain.reaging.LoanReAgeParamet
 import org.apache.fineract.portfolio.loanaccount.domain.reaging.LoanReAgingParameterRepository;
 import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.LoanRepaymentScheduleTransactionProcessor;
 import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.MoneyHolder;
+import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.TransactionCtx;
 import org.apache.fineract.portfolio.loanaccount.exception.LoanTransactionNotFoundException;
 import org.apache.fineract.portfolio.loanaccount.service.LoanAssembler;
 import org.apache.fineract.portfolio.note.domain.Note;
@@ -87,9 +88,8 @@ public class LoanReAgingServiceImpl {
 
         final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = loanRepaymentScheduleTransactionProcessorFactory
                 .determineProcessor(loan.transactionProcessingStrategy());
-        loanRepaymentScheduleTransactionProcessor.processLatestTransaction(reAgeTransaction,
-                new LoanRepaymentScheduleTransactionProcessor.TransactionCtx(loan.getCurrency(), loan.getRepaymentScheduleInstallments(),
-                        loan.getActiveCharges(), new MoneyHolder(loan.getTotalOverpaidAsMoney())));
+        loanRepaymentScheduleTransactionProcessor.processLatestTransaction(reAgeTransaction, new TransactionCtx(loan.getCurrency(),
+                loan.getRepaymentScheduleInstallments(), loan.getActiveCharges(), new MoneyHolder(loan.getTotalOverpaidAsMoney()), null));
 
         loan.updateLoanScheduleDependentDerivedFields();
         persistNote(loan, command, changes);
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/reamortization/LoanReAmortizationServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/reamortization/LoanReAmortizationServiceImpl.java
index 880b85663..1546f6c93 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/reamortization/LoanReAmortizationServiceImpl.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/reamortization/LoanReAmortizationServiceImpl.java
@@ -47,6 +47,7 @@ import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRepositor
 import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionType;
 import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.LoanRepaymentScheduleTransactionProcessor;
 import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.MoneyHolder;
+import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.TransactionCtx;
 import org.apache.fineract.portfolio.loanaccount.service.LoanAssembler;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
@@ -75,9 +76,8 @@ public class LoanReAmortizationServiceImpl {
 
         final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = loanRepaymentScheduleTransactionProcessorFactory
                 .determineProcessor(loan.transactionProcessingStrategy());
-        loanRepaymentScheduleTransactionProcessor.processLatestTransaction(reAmortizeTransaction,
-                new LoanRepaymentScheduleTransactionProcessor.TransactionCtx(loan.getCurrency(), loan.getRepaymentScheduleInstallments(),
-                        loan.getActiveCharges(), new MoneyHolder(loan.getTotalOverpaidAsMoney())));
+        loanRepaymentScheduleTransactionProcessor.processLatestTransaction(reAmortizeTransaction, new TransactionCtx(loan.getCurrency(),
+                loan.getRepaymentScheduleInstallments(), loan.getActiveCharges(), new MoneyHolder(loan.getTotalOverpaidAsMoney()), null));
 
         loanTransactionRepository.saveAndFlush(reAmortizeTransaction);
 
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/starter/LoanAccountAutoStarter.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/starter/LoanAccountAutoStarter.java
index 905f4f4bd..47b820906 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/starter/LoanAccountAutoStarter.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/starter/LoanAccountAutoStarter.java
@@ -20,7 +20,6 @@ package org.apache.fineract.portfolio.loanaccount.starter;
 
 import java.util.List;
 import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleTransactionProcessorFactory;
-import org.apache.fineract.portfolio.loanaccount.domain.reaging.LoanReAgingParameterRepository;
 import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.LoanRepaymentScheduleTransactionProcessor;
 import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.impl.AdvancedPaymentScheduleTransactionProcessor;
 import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.impl.CreocoreLoanRepaymentScheduleTransactionProcessor;
@@ -32,6 +31,7 @@ import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.imp
 import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.impl.InterestPrincipalPenaltyFeesOrderLoanRepaymentScheduleTransactionProcessor;
 import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.impl.PrincipalInterestPenaltyFeesOrderLoanRepaymentScheduleTransactionProcessor;
 import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.impl.RBILoanRepaymentScheduleTransactionProcessor;
+import org.apache.fineract.portfolio.loanproduct.calc.EMICalculator;
 import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Conditional;
@@ -104,9 +104,8 @@ public class LoanAccountAutoStarter {
 
     @Bean
     @Conditional(AdvancedPaymentScheduleTransactionProcessorCondition.class)
-    public AdvancedPaymentScheduleTransactionProcessor advancedPaymentScheduleTransactionProcessor(
-            LoanReAgingParameterRepository reAgingParameterRepository) {
-        return new AdvancedPaymentScheduleTransactionProcessor(reAgingParameterRepository);
+    public AdvancedPaymentScheduleTransactionProcessor advancedPaymentScheduleTransactionProcessor(EMICalculator emiCalculator) {
+        return new AdvancedPaymentScheduleTransactionProcessor(emiCalculator);
     }
 
 }
diff --git a/fineract-provider/src/test/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/DefaultScheduledDateGeneratorTest.java b/fineract-provider/src/test/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/DefaultScheduledDateGeneratorTest.java
index 442e4cd33..3dc81175d 100644
--- a/fineract-provider/src/test/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/DefaultScheduledDateGeneratorTest.java
+++ b/fineract-provider/src/test/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/DefaultScheduledDateGeneratorTest.java
@@ -98,7 +98,7 @@ public class DefaultScheduledDateGeneratorTest {
                 submittedOnDate, CUMULATIVE, LoanScheduleProcessingType.HORIZONTAL, null, false, null);
 
         // when
-        List<PreGeneratedLoanSchedulePeriod> result = underTest.generateRepaymentPeriods(expectedDisbursementDate, loanApplicationTerms,
+        List<? extends LoanScheduleModelPeriod> result = underTest.generateRepaymentPeriods(expectedDisbursementDate, loanApplicationTerms,
                 holidayDetailDTO);
 
         // then
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 be43cc313..9f5a5f150 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
@@ -4257,79 +4257,6 @@ public class AdvancedPaymentAllocationLoanRepaymentScheduleTest extends BaseLoan
         });
     }
 
-    // UC139c: Advanced payment allocation, with DownPayment enabled in Loan Product and change the auto downpayment and
-    // percentage amount
-    // ADVANCED_PAYMENT_ALLOCATION_STRATEGY
-    // 1. Create a Loan product with Adv. Pment. Alloc. and DownPayment
-    // 2. Submit Loan with DownPayment and approve
-    // 3. Modify Loan Application change the auto downpayment and percentage amount
-    // 4. Disburse only 10
-    // 5. Check the repayment schedule is correct, without DownPayment installment
-    @Test
-    public void uc139c() {
-        final String processDate = "23 March 2024";
-        final Double amount = 1000.0;
-        runAt(processDate, () -> {
-            BigDecimal percentageAmount = BigDecimal.valueOf(25);
-            PostLoanProductsRequest product = createOnePeriod30DaysLongNoInterestPeriodicAccrualProductWithAdvancedPaymentAllocation()
-                    .installmentAmountInMultiplesOf(null).numberOfRepayments(3).repaymentEvery(15).enableDownPayment(true)
-                    .enableAutoRepaymentForDownPayment(true).disbursedAmountPercentageForDownPayment(percentageAmount);
-            PostLoanProductsResponse loanProductResponse = loanProductHelper.createLoanProduct(product);
-            PostLoansRequest applicationRequest = applyLoanRequest(client.getClientId(), loanProductResponse.getResourceId(), processDate,
-                    amount, 4);
-
-            applicationRequest = applicationRequest.numberOfRepayments(3).loanTermFrequency(45)
-                    .transactionProcessingStrategyCode(LoanProductTestBuilder.ADVANCED_PAYMENT_ALLOCATION_STRATEGY).repaymentEvery(15)
-                    .enableDownPayment(true);
-
-            PostLoansResponse loanResponse = loanTransactionHelper.applyLoan(applicationRequest);
-
-            GetLoansLoanIdResponse loanDetails = loanTransactionHelper.getLoanDetails(loanResponse.getLoanId());
-            assertNotNull(loanDetails);
-            assertEquals(true, loanDetails.getEnableDownPayment());
-            assertEquals(true, loanDetails.getEnableAutoRepaymentForDownPayment());
-
-            // verify schedule (with DownPayment and AutoDownPayment to 25%)
-            verifyRepaymentSchedule(loanResponse.getLoanId(), //
-                    installment(1000, null, "23 March 2024"), //
-                    installment(250.0, 0, 0, 0, 250.0, false, "23 March 2024", 750.0), //
-                    installment(250.0, 0, 0, 0, 250.0, false, "07 April 2024", 500.0), //
-                    installment(250.0, 0, 0, 0, 250.0, false, "22 April 2024", 250.0), //
-                    installment(250.0, 0, 0, 0, 250.0, false, "07 May 2024", 0.0) //
-            );
-
-            percentageAmount = BigDecimal.valueOf(15);
-            loanTransactionHelper.modifyApplicationForLoan(loanResponse.getLoanId(), "modify",
-                    new PutLoansLoanIdRequest().clientId(client.getClientId()).productId(loanProductResponse.getResourceId())
-                            .transactionProcessingStrategyCode(LoanProductTestBuilder.ADVANCED_PAYMENT_ALLOCATION_STRATEGY)
-                            .repaymentEvery(15).interestCalculationPeriodType(1).interestType(0).expectedDisbursementDate(processDate)
-                            .principal(amount.longValue()).repaymentFrequencyType(0).numberOfRepayments(3).loanTermFrequency(45)
-                            .loanTermFrequencyType(0).enableDownPayment(true).loanType("individual").dateFormat(DATETIME_PATTERN)
-                            .locale("en").enableAutoRepaymentForDownPayment(true)
-                            .disbursedAmountPercentageForDownPayment(percentageAmount));
-
-            loanTransactionHelper.approveLoan(loanResponse.getLoanId(), new PostLoansLoanIdRequest()
-                    .approvedLoanAmount(BigDecimal.valueOf(1000)).dateFormat(DATETIME_PATTERN).approvedOnDate(processDate).locale("en"));
-
-            loanTransactionHelper.disburseLoan(loanResponse.getLoanId(), new PostLoansLoanIdRequest().actualDisbursementDate(processDate)
-                    .dateFormat(DATETIME_PATTERN).transactionAmount(BigDecimal.valueOf(1000.0)).locale("en"));
-
-            loanDetails = loanTransactionHelper.getLoanDetails(loanResponse.getLoanId());
-            assertNotNull(loanDetails);
-            assertEquals(true, loanDetails.getEnableDownPayment());
-            assertEquals(true, loanDetails.getEnableAutoRepaymentForDownPayment());
-
-            // verify schedule (with DownPayment and AutoDownPayment to 15%)
-            verifyRepaymentSchedule(loanResponse.getLoanId(), //
-                    installment(1000, null, "23 March 2024"), //
-                    installment(150, 0, 0, 0, 0.0, true, "23 March 2024", 850), //
-                    installment(283.33, 0, 0, 0, 283.33, false, "07 April 2024", 566.67), //
-                    installment(283.33, 0, 0, 0, 283.33, false, "22 April 2024", 283.34), //
-                    installment(283.34, 0, 0, 0, 283.34, false, "07 May 2024", 0.0) //
-            );
-        });
-    }
-
     // uc140: Negative Test: Advanced payment allocation, with DownPayment disabled in Loan Product but try to enable in
     // Loan application
     // ADVANCED_PAYMENT_ALLOCATION_STRATEGY
@@ -4996,6 +4923,178 @@ public class AdvancedPaymentAllocationLoanRepaymentScheduleTest extends BaseLoan
         });
     }
 
+    // UC14-: Advanced payment allocation with Interest, originally calculated EMI, need adjustment, adjusted EMI is not
+    // better than original, use original EMI and apply with multi disbursement
+    // ADVANCED_PAYMENT_ALLOCATION_STRATEGY
+    // 1. Create a Loan product with Adv. Payment. 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 uc147a() {
+        final String operationDate = "1 January 2024";
+        runAt(operationDate, () -> {
+            BigDecimal interestRatePerPeriod = BigDecimal.valueOf(9.4822);
+
+            Long clientId = clientHelper.createClient(ClientHelper.defaultClientCreationRequest()).getClientId();
+            PostLoanProductsRequest product = createOnePeriod30DaysLongNoInterestPeriodicAccrualProductWithAdvancedPaymentAllocation()
+                    .interestRatePerPeriod(interestRatePerPeriod.doubleValue()).interestCalculationPeriodType(RepaymentFrequencyType.DAYS)
+                    .interestRateFrequencyType(YEARS).daysInMonthType(DaysInMonthType.DAYS_30.getValue())
+                    .daysInYearType(DaysInYearType.DAYS_360.getValue()).numberOfRepayments(6)//
+                    .repaymentEvery(1)//
+                    .repaymentFrequencyType(2L)//
+                    .enableDownPayment(false)//
+                    .allowPartialPeriodInterestCalcualtion(false)//
+                    .enableAutoRepaymentForDownPayment(null)//
+                    .multiDisburseLoan(true)//
+                    .disallowExpectedDisbursements(true)//
+                    .allowApprovedDisbursedAmountsOverApplied(true)//
+                    .overAppliedCalculationType("flat")//
+                    .overAppliedNumber(10000)//
+                    .maxTrancheCount(10)//
+                    .outstandingLoanBalance(10000.0)//
+                    .installmentAmountInMultiplesOf(null)//
+            ;//
+            PostLoanProductsResponse loanProductResponse = loanProductHelper.createLoanProduct(product);
+            PostLoansRequest applicationRequest = applyLoanRequest(clientId, loanProductResponse.getResourceId(), operationDate, 100.0, 6);
+
+            applicationRequest = applicationRequest.numberOfRepayments(6)//
+                    .loanTermFrequency(6)//
+                    .loanTermFrequencyType(2)//
+                    .interestRatePerPeriod(interestRatePerPeriod).interestCalculationPeriodType(DAYS)//
+                    .transactionProcessingStrategyCode(LoanProductTestBuilder.ADVANCED_PAYMENT_ALLOCATION_STRATEGY)//
+                    .repaymentEvery(1)//
+                    .repaymentFrequencyType(2)//
+                    .maxOutstandingLoanBalance(BigDecimal.valueOf(10000.0))//
+            ;//
+
+            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.78, 0.0, 100.0, 0.0, null);
+
+            String disbursementDate2nd = "8 January 2024";
+            updateBusinessDate(disbursementDate2nd);
+            loanTransactionHelper.disburseLoan(loanResponse.getLoanId(),
+                    new PostLoansLoanIdRequest().actualDisbursementDate(disbursementDate2nd).dateFormat(DATETIME_PATTERN)
+                            .transactionAmount(BigDecimal.valueOf(200.0)).locale("en"));
+
+            loanDetails = loanTransactionHelper.getLoanDetails(loanResponse.getLoanId());
+            validateLoanSummaryBalances(loanDetails, 307.99, 0.0, 300.0, 0.0, null);
+
+            validateRepaymentPeriod(loanDetails, 1, LocalDate.of(2024, 2, 1), 49.31, 0.0, 49.31, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 2.02, 0.0,
+                    2.02, 0.0, 0.0);
+            validateRepaymentPeriod(loanDetails, 2, LocalDate.of(2024, 3, 1), 49.35, 0.0, 49.35, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.98, 0.0,
+                    1.98, 0.0, 0.0);
+            validateRepaymentPeriod(loanDetails, 3, LocalDate.of(2024, 4, 1), 49.74, 0.0, 49.74, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.59, 0.0,
+                    1.59, 0.0, 0.0);
+            validateRepaymentPeriod(loanDetails, 4, LocalDate.of(2024, 5, 1), 50.13, 0.0, 50.13, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.20, 0.0,
+                    1.20, 0.0, 0.0);
+            validateRepaymentPeriod(loanDetails, 5, LocalDate.of(2024, 6, 1), 50.53, 0.0, 50.53, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.80, 0.0,
+                    0.80, 0.0, 0.0);
+            validateRepaymentPeriod(loanDetails, 6, LocalDate.of(2024, 7, 1), 50.94, 0.0, 50.94, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.40, 0.0,
+                    0.40, 0.0, 0.0);
+            assertTrue(loanDetails.getStatus().getActive());
+            assertEquals(loanDetails.getNumberOfRepayments(), 6);
+        });
+    }
+
+    // UC14-: Advanced payment allocation with Interest, originally calculated EMI, need adjustment, adjusted EMI is not
+    // better than original, use original EMI and apply with multi disbursement in two different periods
+    // ADVANCED_PAYMENT_ALLOCATION_STRATEGY
+    // 1. Create a Loan product with Adv. Payment. 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 uc147b() {
+        final String operationDate = "1 January 2024";
+        runAt(operationDate, () -> {
+            BigDecimal interestRatePerPeriod = BigDecimal.valueOf(9.4822);
+
+            Long clientId = clientHelper.createClient(ClientHelper.defaultClientCreationRequest()).getClientId();
+            PostLoanProductsRequest product = createOnePeriod30DaysLongNoInterestPeriodicAccrualProductWithAdvancedPaymentAllocation()
+                    .interestRatePerPeriod(interestRatePerPeriod.doubleValue()).interestCalculationPeriodType(RepaymentFrequencyType.DAYS)
+                    .interestRateFrequencyType(YEARS).daysInMonthType(DaysInMonthType.DAYS_30.getValue())
+                    .daysInYearType(DaysInYearType.DAYS_360.getValue()).numberOfRepayments(6)//
+                    .repaymentEvery(1)//
+                    .repaymentFrequencyType(2L)//
+                    .enableDownPayment(false)//
+                    .allowPartialPeriodInterestCalcualtion(false)//
+                    .enableAutoRepaymentForDownPayment(null)//
+                    .multiDisburseLoan(true)//
+                    .disallowExpectedDisbursements(true)//
+                    .allowApprovedDisbursedAmountsOverApplied(true)//
+                    .overAppliedCalculationType("flat")//
+                    .overAppliedNumber(10000)//
+                    .maxTrancheCount(10)//
+                    .outstandingLoanBalance(10000.0)//
+                    .installmentAmountInMultiplesOf(null)//
+            ;//
+            PostLoanProductsResponse loanProductResponse = loanProductHelper.createLoanProduct(product);
+            PostLoansRequest applicationRequest = applyLoanRequest(clientId, loanProductResponse.getResourceId(), operationDate, 100.0, 5);
+
+            applicationRequest = applicationRequest.numberOfRepayments(6)//
+                    .loanTermFrequency(6)//
+                    .loanTermFrequencyType(2)//
+                    .interestRatePerPeriod(interestRatePerPeriod).interestCalculationPeriodType(DAYS)//
+                    .transactionProcessingStrategyCode(LoanProductTestBuilder.ADVANCED_PAYMENT_ALLOCATION_STRATEGY)//
+                    .repaymentEvery(1)//
+                    .repaymentFrequencyType(2)//
+                    .maxOutstandingLoanBalance(BigDecimal.valueOf(10000.0))//
+            ;//
+
+            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.78, 0.0, 100.0, 0.0, null);
+
+            final String repaymentDate = "1 February 2024";
+            updateBusinessDate(repaymentDate);
+            loanTransactionHelper.makeRepayment(repaymentDate, 17.13f, loanResponse.getLoanId().intValue());
+
+            loanDetails = loanTransactionHelper.getLoanDetails(loanResponse.getLoanId());
+
+            validateLoanSummaryBalances(loanDetails, 85.65, 17.13, 83.66, 16.34, null);
+
+            final String dateOf2ndDisbursement = "15 February 2024";
+            updateBusinessDate(dateOf2ndDisbursement);
+            loanTransactionHelper.disburseLoan(loanResponse.getLoanId(),
+                    new PostLoansLoanIdRequest().actualDisbursementDate(dateOf2ndDisbursement).dateFormat(DATETIME_PATTERN)
+                            .transactionAmount(BigDecimal.valueOf(100.0)).locale("en"));
+
+            loanDetails = loanTransactionHelper.getLoanDetails(loanResponse.getLoanId());
+            validateLoanSummaryBalances(loanDetails, 187.65, 17.13, 183.66, 16.34, null);
+
+            validateRepaymentPeriod(loanDetails, 1, LocalDate.of(2024, 2, 1), 16.34, 16.34, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.79, 0.79,
+                    0.0, 0.0, 0.0);
+            validateRepaymentPeriod(loanDetails, 2, LocalDate.of(2024, 3, 1), 36.46, 0.0, 36.46, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.07, 0.0,
+                    1.07, 0.0, 0.0);
+            validateRepaymentPeriod(loanDetails, 3, LocalDate.of(2024, 4, 1), 36.37, 0.0, 36.37, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.16, 0.0,
+                    1.16, 0.0, 0.0);
+            validateRepaymentPeriod(loanDetails, 4, LocalDate.of(2024, 5, 1), 36.65, 0.0, 36.65, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.88, 0.0,
+                    0.88, 0.0, 0.0);
+            validateRepaymentPeriod(loanDetails, 5, LocalDate.of(2024, 6, 1), 36.94, 0.0, 36.94, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.59, 0.0,
+                    0.59, 0.0, 0.0);
+            validateRepaymentPeriod(loanDetails, 6, LocalDate.of(2024, 7, 1), 37.24, 0.0, 37.24, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.29, 0.0,
+                    0.29, 0.0, 0.0);
+            assertTrue(loanDetails.getStatus().getActive());
+            assertEquals(loanDetails.getNumberOfRepayments(), 6);
+        });
+    }
+
     private Long applyAndApproveLoanProgressiveAdvancedPaymentAllocationStrategyMonthlyRepayments(Long clientId, Long loanProductId,
             Integer numberOfRepayments, String loanDisbursementDate, double amount) {
         LOG.info("------------------------------APPLY AND APPROVE LOAN ---------------------------------------");