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 3456e1eb8 FINERACT-2114: Reschedule validations enhancement
3456e1eb8 is described below

commit 3456e1eb81134f28f0649ab0e3b136422da08487
Author: Adam Saghy <[email protected]>
AuthorDate: Wed Aug 21 19:57:14 2024 +0200

    FINERACT-2114: Reschedule validations enhancement
---
 .../test/stepdef/loan/LoanRescheduleStepDef.java   |  16 +-
 .../resources/features/LoanDownPayment.feature     |   8 +-
 .../test/resources/features/LoanReschedule.feature |  38 +-
 .../data/LoanRescheduleRequestDataValidator.java   | 379 +------------------
 ...=> LoanRescheduleRequestDataValidatorImpl.java} | 403 ++++++++++-----------
 .../domain/LoanRescheduleRequest.java              |  17 +-
 ...gressiveLoanRescheduleRequestDataValidator.java | 188 ++++++++++
 ...nRescheduleRequestWritePlatformServiceImpl.java |   4 +-
 .../integrationtests/BaseLoanIntegrationTest.java  |   2 +-
 ...ChargeOffWithAdvancedPaymentAllocationTest.java |  29 --
 .../LoanRescheduleRequestTest.java                 | 162 ++++++++-
 .../LoanTransactionAccrualActivityPostingTest.java |   4 +-
 .../common/LoanRescheduleRequestHelper.java        |   4 +
 13 files changed, 590 insertions(+), 664 deletions(-)

diff --git 
a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/loan/LoanRescheduleStepDef.java
 
b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/loan/LoanRescheduleStepDef.java
index a71454903..8ae467cf9 100644
--- 
a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/loan/LoanRescheduleStepDef.java
+++ 
b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/loan/LoanRescheduleStepDef.java
@@ -62,10 +62,10 @@ public class LoanRescheduleStepDef extends AbstractStepDef {
         String rescheduleFromDate = rescheduleData.get(0);
         String submittedOnDate = rescheduleData.get(1);
         String adjustedDueDate = rescheduleData.get(2);
-        Integer graceOfPrincipal = Integer.parseInt(rescheduleData.get(3)) == 
0 ? null : Integer.valueOf(rescheduleData.get(3));
-        Integer graceOnInterest = Integer.parseInt(rescheduleData.get(4)) == 0 
? null : Integer.valueOf(rescheduleData.get(4));
-        Integer extraTerms = Integer.parseInt(rescheduleData.get(5)) == 0 ? 
null : Integer.valueOf(rescheduleData.get(5));
-        BigDecimal newInterestRate = new BigDecimal(rescheduleData.get(6));
+        Integer graceOfPrincipal = rescheduleData.get(3) != null ? 
Integer.parseInt(rescheduleData.get(3)) : null;
+        Integer graceOnInterest = rescheduleData.get(4) != null ? 
Integer.parseInt(rescheduleData.get(4)) : null;
+        Integer extraTerms = rescheduleData.get(5) != null ? 
Integer.parseInt(rescheduleData.get(5)) : null;
+        BigDecimal newInterestRate = rescheduleData.get(6) != null ? new 
BigDecimal(rescheduleData.get(6)) : null;
 
         PostCreateRescheduleLoansRequest request = new 
PostCreateRescheduleLoansRequest()//
                 .loanId(loanId)//
@@ -105,10 +105,10 @@ public class LoanRescheduleStepDef extends 
AbstractStepDef {
         String rescheduleFromDate = rescheduleData.get(0);
         String submittedOnDate = rescheduleData.get(1);
         String adjustedDueDate = rescheduleData.get(2);
-        Integer graceOfPrincipal = Integer.parseInt(rescheduleData.get(3)) == 
0 ? null : Integer.valueOf(rescheduleData.get(3));
-        Integer graceOnInterest = Integer.parseInt(rescheduleData.get(4)) == 0 
? null : Integer.valueOf(rescheduleData.get(4));
-        Integer extraTerms = Integer.parseInt(rescheduleData.get(5)) == 0 ? 
null : Integer.valueOf(rescheduleData.get(5));
-        BigDecimal newInterestRate = new BigDecimal(rescheduleData.get(6));
+        Integer graceOfPrincipal = rescheduleData.get(3) != null ? 
Integer.parseInt(rescheduleData.get(3)) : null;
+        Integer graceOnInterest = rescheduleData.get(4) != null ? 
Integer.parseInt(rescheduleData.get(4)) : null;
+        Integer extraTerms = rescheduleData.get(5) != null ? 
Integer.parseInt(rescheduleData.get(5)) : null;
+        BigDecimal newInterestRate = rescheduleData.get(6) != null ? new 
BigDecimal(rescheduleData.get(6)) : null;
 
         PostCreateRescheduleLoansRequest request = new 
PostCreateRescheduleLoansRequest()//
                 .loanId(loanId)//
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 a2f316a2b..1c62109de 100644
--- 
a/fineract-e2e-tests-runner/src/test/resources/features/LoanDownPayment.feature
+++ 
b/fineract-e2e-tests-runner/src/test/resources/features/LoanDownPayment.feature
@@ -434,7 +434,7 @@ Feature: Loan DownPayment
     When Admin sets the business date to "10 January 2022"
     When Admin creates and approves Loan reschedule with the following data:
       | rescheduleFromDate | submittedOnDate | adjustedDueDate | 
graceOnPrincipal | graceOnInterest | extraTerms | newInterestRate |
-      | 01 February 2022   | 10 January 2022 | 01 April 2022   | 0             
   | 0               | 0          | 0               |
+      | 01 February 2022   | 10 January 2022 | 01 April 2022   |               
   |                 |            |                 |
     When Admin sets the business date to "01 April 2022"
     When Admin successfully disburse the loan on "01 April 2022" with "400" 
EUR transaction amount
     Then Loan Repayment schedule has 5 periods, with the following data for 
periods:
@@ -490,7 +490,7 @@ Feature: Loan DownPayment
     When Admin sets the business date to "10 January 2022"
     When Admin creates and approves Loan reschedule with the following data:
       | rescheduleFromDate | submittedOnDate | adjustedDueDate | 
graceOnPrincipal | graceOnInterest | extraTerms | newInterestRate |
-      | 01 February 2022   | 10 January 2022 | 01 April 2022   | 0             
   | 0               | 0          | 0               |
+      | 01 February 2022   | 10 January 2022 | 01 April 2022   |               
   |                 |            |                 |
     Then Loan Repayment schedule has 4 periods, with the following data for 
periods:
       | Nr | Days | Date            | Paid date       | Balance of loan | 
Principal due | Interest | Fees | Penalties | Due   | Paid  | In advance | Late 
| Outstanding |
       |    |      | 01 January 2022 |                 | 1000.0          |      
         |          | 0.0  |           | 0.0   | 0.0   |            |      |    
         |
@@ -544,7 +544,7 @@ Feature: Loan DownPayment
       | 1000.0        | 0        | 0    | 50        | 1050.0 | 250.0 | 0       
   | 0    | 800         |
     When Admin creates and approves Loan reschedule with the following data:
       | rescheduleFromDate | submittedOnDate | adjustedDueDate | 
graceOnPrincipal | graceOnInterest | extraTerms | newInterestRate |
-      | 10 February 2022   | 10 January 2022 | 10 April 2022   | 0             
   | 0               | 0          | 0               |
+      | 10 February 2022   | 10 January 2022 | 10 April 2022   |               
   |                 |            |                 |
     Then Loan Repayment schedule has 4 periods, with the following data for 
periods:
       | Nr | Days | Date            | Paid date       | Balance of loan | 
Principal due | Interest | Fees | Penalties | Due   | Paid  | In advance | Late 
| Outstanding |
       |    |      | 10 January 2022 |                 | 1000.0          |      
         |          | 0.0  |           | 0.0   | 0.0   |            |      |    
         |
@@ -610,7 +610,7 @@ Feature: Loan DownPayment
       | 1000.0        | 0        | 0    | 50        | 1050.0 | 250.0 | 0       
   | 0    | 800         |
     When Admin creates and approves Loan reschedule with the following data:
       | rescheduleFromDate | submittedOnDate | adjustedDueDate | 
graceOnPrincipal | graceOnInterest | extraTerms | newInterestRate |
-      | 10 February 2022   | 10 January 2022 | 10 April 2022   | 0             
   | 0               | 0          | 0               |
+      | 10 February 2022   | 10 January 2022 | 10 April 2022   |               
   |                 |            |                 |
     Then Loan Repayment schedule has 4 periods, with the following data for 
periods:
       | Nr | Days | Date            | Paid date       | Balance of loan | 
Principal due | Interest | Fees | Penalties | Due   | Paid  | In advance | Late 
| Outstanding |
       |    |      | 10 January 2022 |                 | 1000.0          |      
         |          | 0.0  |           | 0.0   | 0.0   |            |      |    
         |
diff --git 
a/fineract-e2e-tests-runner/src/test/resources/features/LoanReschedule.feature 
b/fineract-e2e-tests-runner/src/test/resources/features/LoanReschedule.feature
index 0dd01cd35..28d7e9e6a 100644
--- 
a/fineract-e2e-tests-runner/src/test/resources/features/LoanReschedule.feature
+++ 
b/fineract-e2e-tests-runner/src/test/resources/features/LoanReschedule.feature
@@ -114,7 +114,7 @@ Feature: LoanReschedule
     When Admin sets the business date to "05 July 2023"
     When Admin creates and approves Loan reschedule with the following data:
       | rescheduleFromDate | submittedOnDate | adjustedDueDate | 
graceOnPrincipal | graceOnInterest | extraTerms | newInterestRate |
-      | 31 July 2023       | 05 July 2023    | 31 August 2023  | 0             
   | 0               | 0          | 0               |
+      | 31 July 2023       | 05 July 2023    | 31 August 2023  |               
   |                 |            |                 |
     Then Loan Repayment schedule has 1 periods, with the following data for 
periods:
       | Nr | Days | Date           | Paid date | Balance of loan | Principal 
due | Interest | Fees | Penalties | Due    | Paid | In advance | Late | 
Outstanding |
       |    |      | 01 July 2023   |           | 1000.0          |             
  |          | 0.0  |           | 0.0    | 0.0  |            |      |           
  |
@@ -144,7 +144,7 @@ Feature: LoanReschedule
     When Admin sets the business date to "05 July 2023"
     When Admin creates and approves Loan reschedule with the following data:
       | rescheduleFromDate | submittedOnDate | adjustedDueDate   | 
graceOnPrincipal | graceOnInterest | extraTerms | newInterestRate |
-      | 01 September 2023  | 05 July 2023    | 15 September 2023 | 0           
     | 0               | 0          | 0               |
+      | 01 September 2023  | 05 July 2023    | 15 September 2023 |             
     |                 |            |                 |
     Then Loan Repayment schedule has 3 periods, with the following data for 
periods:
       | Nr | Days | Date              | Paid date | Balance of loan | 
Principal due | Interest | Fees | Penalties | Due    | Paid | In advance | Late 
| Outstanding |
       |    |      | 01 July 2023      |           | 3000.0          |          
     |          | 0.0  |           | 0.0    | 0.0  |            |      |        
     |
@@ -176,7 +176,7 @@ Feature: LoanReschedule
     When Admin sets the business date to "05 July 2023"
     When Admin creates and approves Loan reschedule with the following data:
       | rescheduleFromDate | submittedOnDate | adjustedDueDate | 
graceOnPrincipal | graceOnInterest | extraTerms | newInterestRate |
-      | 01 September 2023  | 05 July 2023    |                 | 0             
   | 0               | 2          | 0               |
+      | 01 September 2023  | 05 July 2023    |                 |               
   |                 | 2          |                 |
     Then Loan Repayment schedule has 5 periods, with the following data for 
periods:
       | Nr | Days | Date              | Paid date | Balance of loan | 
Principal due | Interest | Fees | Penalties | Due    | Paid | In advance | Late 
| Outstanding |
       |    |      | 01 July 2023      |           | 3000.0          |          
     |          | 0.0  |           | 0.0    | 0.0  |            |      |        
     |
@@ -210,7 +210,7 @@ Feature: LoanReschedule
     When Admin sets the business date to "05 July 2023"
     When Admin creates and approves Loan reschedule with the following data:
       | rescheduleFromDate | submittedOnDate | adjustedDueDate | 
graceOnPrincipal | graceOnInterest | extraTerms | newInterestRate |
-      | 01 September 2023  | 05 July 2023    |                 | 1             
   | 0               | 0          | 0               |
+      | 01 September 2023  | 05 July 2023    |                 | 1             
   |                 |            |                 |
     Then Loan Repayment schedule has 4 periods, with the following data for 
periods:
       | Nr | Days | Date              | Paid date    | Balance of loan | 
Principal due | Interest | Fees | Penalties | Due    | Paid | In advance | Late 
| Outstanding |
       |    |      | 01 July 2023      |              | 3000.0          |       
        |          | 0.0  |           | 0.0    | 0.0  |            |      |     
        |
@@ -244,7 +244,7 @@ Feature: LoanReschedule
     When Admin sets the business date to "05 July 2023"
     When Admin creates and approves Loan reschedule with the following data:
       | rescheduleFromDate | submittedOnDate | adjustedDueDate | 
graceOnPrincipal | graceOnInterest | extraTerms | newInterestRate |
-      | 01 September 2023  | 05 July 2023    |                 | 0             
   | 2               | 0          | 0               |
+      | 01 September 2023  | 05 July 2023    |                 |               
   | 2               |            |                 |
 #    Then Loan Repayment schedule has 3 periods, with the following data for 
periods:
 #      | Nr | Days | Date              | Paid date | Balance of loan | 
Principal due | Interest | Fees | Penalties | Due    | Paid | In advance | Late 
| Outstanding |
 #      |    |      | 01 July 2023      |           | 3000.0          |         
      |          | 0.0  |           | 0.0    | 0.0  |            |      |       
      |
@@ -276,7 +276,7 @@ Feature: LoanReschedule
     When Admin sets the business date to "05 July 2023"
     When Admin creates and approves Loan reschedule with the following data:
       | rescheduleFromDate | submittedOnDate | adjustedDueDate | 
graceOnPrincipal | graceOnInterest | extraTerms | newInterestRate |
-      | 01 September 2023  | 05 July 2023    |                 | 0             
   | 0               | 0          | 6               |
+      | 01 September 2023  | 05 July 2023    |                 |               
   |                 |            | 6               |
     Then Loan Repayment schedule has 3 periods, with the following data for 
periods:
       | Nr | Days | Date              | Paid date | Balance of loan | 
Principal due | Interest | Fees | Penalties | Due    | Paid | In advance | Late 
| Outstanding |
       |    |      | 01 July 2023      |           | 3000.0          |          
     |          | 0.0  |           | 0.0    | 0.0  |            |      |        
     |
@@ -298,7 +298,7 @@ Feature: LoanReschedule
     When Admin successfully disburse the loan on "01 October 2023" with "1000" 
EUR transaction amount
     When Admin creates and approves Loan reschedule with the following data:
       | rescheduleFromDate | submittedOnDate | adjustedDueDate | 
graceOnPrincipal | graceOnInterest | extraTerms | newInterestRate |
-      | 16 October 2023    | 01 October 2023 |                 | 0             
   | 0               | 2          | 0               |
+      | 16 October 2023    | 01 October 2023 |                 |               
   |                 | 2          |                 |
     Then Loan Repayment schedule has 6 periods, with the following data for 
periods:
       | Nr | Days | Date             | Paid date       | Balance of loan | 
Principal due | Interest | Fees | Penalties | Due   | Paid  | In advance | Late 
| Outstanding |
       |    |      | 01 October 2023  |                 | 1000.0          |     
          |          | 0.0  |           | 0.0   | 0.0   |            |      |   
          |
@@ -326,7 +326,7 @@ Feature: LoanReschedule
     When Admin sets the business date to "11 October 2023"
     When Admin creates and approves Loan reschedule with the following data:
       | rescheduleFromDate | submittedOnDate | adjustedDueDate | 
graceOnPrincipal | graceOnInterest | extraTerms | newInterestRate |
-      | 16 October 2023    | 01 October 2023 |                 | 0             
   | 0               | 2          | 0               |
+      | 16 October 2023    | 01 October 2023 |                 |               
   |                 | 2          |                 |
     Then Loan Repayment schedule has 7 periods, with the following data for 
periods:
       | Nr | Days | Date             | Paid date       | Balance of loan | 
Principal due | Interest | Fees | Penalties | Due   | Paid  | In advance | Late 
| Outstanding |
       |    |      | 01 October 2023  |                 | 1000.0          |     
          |          | 0.0  |           | 0.0   | 0.0   |            |      |   
          |
@@ -356,7 +356,7 @@ Feature: LoanReschedule
     When Admin sets the business date to "11 October 2023"
     When Admin creates and approves Loan reschedule with the following data:
       | rescheduleFromDate | submittedOnDate | adjustedDueDate | 
graceOnPrincipal | graceOnInterest | extraTerms | newInterestRate |
-      | 16 October 2023    | 01 October 2023 |                 | 0             
   | 0               | 2          | 0               |
+      | 16 October 2023    | 01 October 2023 |                 |               
   |                 | 2          |                 |
     When Admin sets the business date to "20 October 2023"
     When Admin successfully disburse the loan on "20 October 2023" with "400" 
EUR transaction amount
     Then Loan Repayment schedule has 7 periods, with the following data for 
periods:
@@ -386,7 +386,7 @@ Feature: LoanReschedule
     When Admin sets the business date to "05 October 2023"
     When Admin creates and approves Loan reschedule with the following data:
       | rescheduleFromDate | submittedOnDate | adjustedDueDate | 
graceOnPrincipal | graceOnInterest | extraTerms | newInterestRate |
-      | 16 October 2023    | 05 October 2023 | 31 October 2023 | 0             
   | 0               | 0          | 0               |
+      | 16 October 2023    | 05 October 2023 | 31 October 2023 |               
   |                 |            |                 |
     Then Loan Repayment schedule has 4 periods, with the following data for 
periods:
       | Nr | Days | Date             | Paid date       | Balance of loan | 
Principal due | Interest | Fees | Penalties | Due   | Paid  | In advance | Late 
| Outstanding |
       |    |      | 01 October 2023  |                 | 1000.0          |     
          |          | 0.0  |           | 0.0   | 0.0   |            |      |   
          |
@@ -410,7 +410,7 @@ Feature: LoanReschedule
     When Admin sets the business date to "05 October 2023"
     When Admin creates and approves Loan reschedule with the following data:
       | rescheduleFromDate | submittedOnDate | adjustedDueDate  | 
graceOnPrincipal | graceOnInterest | extraTerms | newInterestRate |
-      | 16 October 2023    | 05 October 2023 | 15 November 2023 | 0            
    | 0               | 0          | 0               |
+      | 16 October 2023    | 05 October 2023 | 15 November 2023 |              
    |                 |            |                 |
     Then Loan Repayment schedule has 4 periods, with the following data for 
periods:
       | Nr | Days | Date             | Paid date       | Balance of loan | 
Principal due | Interest | Fees | Penalties | Due   | Paid  | In advance | Late 
| Outstanding |
       |    |      | 01 October 2023  |                 | 1000.0          |     
          |          | 0.0  |           | 0.0   | 0.0   |            |      |   
          |
@@ -434,7 +434,7 @@ Feature: LoanReschedule
     When Admin sets the business date to "05 October 2023"
     When Admin creates and approves Loan reschedule with the following data:
       | rescheduleFromDate | submittedOnDate | adjustedDueDate  | 
graceOnPrincipal | graceOnInterest | extraTerms | newInterestRate |
-      | 16 October 2023    | 05 October 2023 | 15 November 2023 | 0            
    | 0               | 0          | 0               |
+      | 16 October 2023    | 05 October 2023 | 15 November 2023 |              
    |                 |            |                 |
     Then Loan Repayment schedule has 4 periods, with the following data for 
periods:
       | Nr | Days | Date             | Paid date | Balance of loan | Principal 
due | Interest | Fees | Penalties | Due   | Paid | In advance | Late | 
Outstanding |
       |    |      | 01 October 2023  |           | 1000.0          |           
    |          | 0.0  |           | 0.0   | 0.0  |            |      |          
   |
@@ -464,7 +464,7 @@ Feature: LoanReschedule
     When Admin sets the business date to "05 October 2023"
     When Admin creates and approves Loan reschedule with the following data:
       | rescheduleFromDate | submittedOnDate | adjustedDueDate | 
graceOnPrincipal | graceOnInterest | extraTerms | newInterestRate |
-      | 16 October 2023    | 05 October 2023 | 31 October 2023 | 0             
   | 0               | 0          | 0               |
+      | 16 October 2023    | 05 October 2023 | 31 October 2023 |               
   |                 |            |                 |
     Then Loan Repayment schedule has 4 periods, with the following data for 
periods:
       | Nr | Days | Date             | Paid date       | Balance of loan | 
Principal due | Interest | Fees | Penalties | Due   | Paid  | In advance | Late 
 | Outstanding |
       |    |      | 01 October 2023  |                 | 1000.0          |     
          |          | 0.0  |           | 0.0   | 0.0   |            |       |  
           |
@@ -492,7 +492,7 @@ Feature: LoanReschedule
     When Admin successfully disburse the loan on "01 October 2023" with "1000" 
EUR transaction amount
     When Admin creates and approves Loan reschedule with the following data:
       | rescheduleFromDate | submittedOnDate | adjustedDueDate | 
graceOnPrincipal | graceOnInterest | extraTerms | newInterestRate |
-      | 16 October 2023    | 01 October 2023 | 31 October 2023 | 0             
   | 0               | 0          | 0               |
+      | 16 October 2023    | 01 October 2023 | 31 October 2023 |               
   |                 |            |                 |
     Then Loan Repayment schedule has 4 periods, with the following data for 
periods:
       | Nr | Days | Date             | Paid date       | Balance of loan | 
Principal due | Interest | Fees | Penalties | Due   | Paid  | In advance | Late 
| Outstanding |
       |    |      | 01 October 2023  |                 | 1000.0          |     
          |          | 0.0  |           | 0.0   | 0.0   |            |      |   
          |
@@ -518,7 +518,7 @@ Feature: LoanReschedule
     When Admin sets the business date to "11 October 2023"
     When Admin creates and approves Loan reschedule with the following data:
       | rescheduleFromDate | submittedOnDate | adjustedDueDate | 
graceOnPrincipal | graceOnInterest | extraTerms | newInterestRate |
-      | 16 October 2023    | 01 October 2023 | 31 October 2023 | 0             
   | 0               | 0          | 0               |
+      | 16 October 2023    | 01 October 2023 | 31 October 2023 |               
   |                 |            |                 |
     Then Loan Repayment schedule has 5 periods, with the following data for 
periods:
       | Nr | Days | Date             | Paid date       | Balance of loan | 
Principal due | Interest | Fees | Penalties | Due   | Paid  | In advance | Late 
| Outstanding |
       |    |      | 01 October 2023  |                 | 1000.0          |     
          |          | 0.0  |           | 0.0   | 0.0   |            |      |   
          |
@@ -544,7 +544,7 @@ Feature: LoanReschedule
     When Admin sets the business date to "05 October 2023"
     When Admin creates and approves Loan reschedule with the following data:
       | rescheduleFromDate | submittedOnDate | adjustedDueDate | 
graceOnPrincipal | graceOnInterest | extraTerms | newInterestRate |
-      | 16 October 2023    | 05 October 2023 | 31 October 2023 | 0             
   | 0               | 2          | 0               |
+      | 16 October 2023    | 05 October 2023 | 31 October 2023 |               
   |                 | 2          |                 |
     Then Loan Repayment schedule has 6 periods, with the following data for 
periods:
       | Nr | Days | Date             | Paid date       | Balance of loan | 
Principal due | Interest | Fees | Penalties | Due   | Paid  | In advance | Late 
| Outstanding |
       |    |      | 01 October 2023  |                 | 1000.0          |     
          |          | 0.0  |           | 0.0   | 0.0   |            |      |   
          |
@@ -570,7 +570,7 @@ Feature: LoanReschedule
     When Admin sets the business date to "05 October 2023"
     When Admin creates and approves Loan reschedule with the following data:
       | rescheduleFromDate | submittedOnDate | adjustedDueDate | 
graceOnPrincipal | graceOnInterest | extraTerms | newInterestRate |
-      | 16 October 2023    | 05 October 2023 | 31 October 2023 | 1             
   | 0               | 0          | 0               |
+      | 16 October 2023    | 05 October 2023 | 31 October 2023 | 1             
   |                 |            |                 |
     Then Loan Repayment schedule has 5 periods, with the following data for 
periods:
       | Nr | Days | Date             | Paid date       | Balance of loan | 
Principal due | Interest | Fees | Penalties | Due   | Paid  | In advance | Late 
| Outstanding |
       |    |      | 01 October 2023  |                 | 1000.0          |     
          |          | 0.0  |           | 0.0   | 0.0   |            |      |   
          |
@@ -597,7 +597,7 @@ Feature: LoanReschedule
     When Admin sets the business date to "05 October 2023"
     Then Loan reschedule with the following data results a 403 error and 
"LOAN_CHARGED_OFF" error message
       | rescheduleFromDate | submittedOnDate | adjustedDueDate | 
graceOnPrincipal | graceOnInterest | extraTerms | newInterestRate |
-      | 16 October 2023    | 05 October 2023 | 31 October 2023 | 0             
   | 0               | 0          | 0               |
+      | 16 October 2023    | 05 October 2023 | 31 October 2023 |               
   |                 |            |                 |
 
 
   Scenario: Verify that reschedule keeps the N+1 installment
@@ -623,7 +623,7 @@ Feature: LoanReschedule
     When Admin sets the business date to "05 October 2023"
     When Admin creates and approves Loan reschedule with the following data:
       | rescheduleFromDate | submittedOnDate | adjustedDueDate | 
graceOnPrincipal | graceOnInterest | extraTerms | newInterestRate |
-      | 16 October 2023    | 05 October 2023 | 31 October 2023 | 0             
   | 0               | 0          | 0               |
+      | 16 October 2023    | 05 October 2023 | 31 October 2023 |               
   |                 |            |                 |
     Then Loan Repayment schedule has 5 periods, with the following data for 
periods:
       | Nr | Days | Date             | Paid date | Balance of loan | Principal 
due | Interest | Fees | Penalties | Due   | Paid | In advance | Late | 
Outstanding |
       |    |      | 01 October 2023  |           | 1000.0          |           
    |          | 0.0  |           | 0.0   | 0.0  |            |      |          
   |
diff --git 
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/data/LoanRescheduleRequestDataValidator.java
 
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/data/LoanRescheduleRequestDataValidator.java
index be174c450..c41fb4da6 100644
--- 
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/data/LoanRescheduleRequestDataValidator.java
+++ 
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/data/LoanRescheduleRequestDataValidator.java
@@ -18,386 +18,15 @@
  */
 package org.apache.fineract.portfolio.loanaccount.rescheduleloan.data;
 
-import com.google.gson.JsonElement;
-import com.google.gson.reflect.TypeToken;
-import java.lang.reflect.Type;
-import java.math.BigDecimal;
-import java.time.LocalDate;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import lombok.AllArgsConstructor;
-import org.apache.commons.lang3.StringUtils;
 import org.apache.fineract.infrastructure.core.api.JsonCommand;
-import org.apache.fineract.infrastructure.core.data.ApiParameterError;
-import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder;
-import org.apache.fineract.infrastructure.core.exception.InvalidJsonException;
-import 
org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
-import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
-import org.apache.fineract.infrastructure.core.service.DateUtils;
 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.loanschedule.domain.LoanScheduleType;
-import 
org.apache.fineract.portfolio.loanaccount.rescheduleloan.RescheduleLoansApiConstants;
 import 
org.apache.fineract.portfolio.loanaccount.rescheduleloan.domain.LoanRescheduleRequest;
-import org.springframework.stereotype.Component;
 
-@Component
-@AllArgsConstructor
-public class LoanRescheduleRequestDataValidator {
+public interface LoanRescheduleRequestDataValidator {
 
-    private final FromJsonHelper fromJsonHelper;
-    private static final Set<String> CREATE_REQUEST_DATA_PARAMETERS = new 
HashSet<>(
-            Arrays.asList(RescheduleLoansApiConstants.localeParamName, 
RescheduleLoansApiConstants.dateFormatParamName,
-                    RescheduleLoansApiConstants.graceOnPrincipalParamName,
-                    
RescheduleLoansApiConstants.recurringMoratoriumOnPrincipalPeriodsParamName,
-                    RescheduleLoansApiConstants.graceOnInterestParamName, 
RescheduleLoansApiConstants.extraTermsParamName,
-                    RescheduleLoansApiConstants.rescheduleFromDateParamName, 
RescheduleLoansApiConstants.newInterestRateParamName,
-                    RescheduleLoansApiConstants.rescheduleReasonIdParamName, 
RescheduleLoansApiConstants.rescheduleReasonCommentParamName,
-                    RescheduleLoansApiConstants.submittedOnDateParamName, 
RescheduleLoansApiConstants.loanIdParamName,
-                    RescheduleLoansApiConstants.adjustedDueDateParamName, 
RescheduleLoansApiConstants.recalculateInterestParamName,
-                    RescheduleLoansApiConstants.endDateParamName, 
RescheduleLoansApiConstants.emiParamName));
+    void validateForCreateAction(JsonCommand jsonCommand, Loan loan);
 
-    private static final Set<String> REJECT_REQUEST_DATA_PARAMETERS = new 
HashSet<>(
-            Arrays.asList(RescheduleLoansApiConstants.localeParamName, 
RescheduleLoansApiConstants.dateFormatParamName,
-                    RescheduleLoansApiConstants.rejectedOnDateParam));
+    void validateForApproveAction(JsonCommand jsonCommand, 
LoanRescheduleRequest loanRescheduleRequest);
 
-    private static final Set<String> APPROVE_REQUEST_DATA_PARAMETERS = new 
HashSet<>(
-            Arrays.asList(RescheduleLoansApiConstants.localeParamName, 
RescheduleLoansApiConstants.dateFormatParamName,
-                    RescheduleLoansApiConstants.approvedOnDateParam));
-
-    /**
-     * Validates the request to create a new loan reschedule entry
-     *
-     * @param jsonCommand
-     *            the JSON command object (instance of the JsonCommand class)
-     *
-     **/
-    public void validateForCreateAction(final JsonCommand jsonCommand, final 
Loan loan) {
-
-        final String jsonString = jsonCommand.json();
-
-        if (StringUtils.isBlank(jsonString)) {
-            throw new InvalidJsonException();
-        }
-
-        final Type typeToken = new TypeToken<Map<String, Object>>() 
{}.getType();
-        this.fromJsonHelper.checkForUnsupportedParameters(typeToken, 
jsonString, CREATE_REQUEST_DATA_PARAMETERS);
-
-        final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
-        final DataValidatorBuilder dataValidatorBuilder = new 
DataValidatorBuilder(dataValidationErrors)
-                
.resource(StringUtils.lowerCase(RescheduleLoansApiConstants.ENTITY_NAME));
-
-        final JsonElement jsonElement = jsonCommand.parsedJson();
-
-        if (!loan.getStatus().isActive()) {
-            
dataValidatorBuilder.reset().failWithCodeNoParameterAddedToErrorCode("loan.is.not.active",
 "Loan is not active");
-        }
-
-        final Long loanId = 
this.fromJsonHelper.extractLongNamed(RescheduleLoansApiConstants.loanIdParamName,
 jsonElement);
-        
dataValidatorBuilder.reset().parameter(RescheduleLoansApiConstants.loanIdParamName).value(loanId).notNull()
-                .integerGreaterThanZero();
-
-        final LocalDate submittedOnDate = 
this.fromJsonHelper.extractLocalDateNamed(RescheduleLoansApiConstants.submittedOnDateParamName,
-                jsonElement);
-        
dataValidatorBuilder.reset().parameter(RescheduleLoansApiConstants.submittedOnDateParamName).value(submittedOnDate).notNull();
-
-        if (submittedOnDate != null && 
DateUtils.isAfter(loan.getDisbursementDate(), submittedOnDate)) {
-            
dataValidatorBuilder.reset().parameter(RescheduleLoansApiConstants.submittedOnDateParamName)
-                    .failWithCode("before.loan.disbursement.date", "Submission 
date cannot be before the loan disbursement date");
-        }
-
-        final LocalDate rescheduleFromDate = this.fromJsonHelper
-                
.extractLocalDateNamed(RescheduleLoansApiConstants.rescheduleFromDateParamName, 
jsonElement);
-        
dataValidatorBuilder.reset().parameter(RescheduleLoansApiConstants.rescheduleFromDateParamName).value(rescheduleFromDate).notNull();
-
-        final Integer graceOnPrincipal = this.fromJsonHelper
-                
.extractIntegerWithLocaleNamed(RescheduleLoansApiConstants.graceOnPrincipalParamName,
 jsonElement);
-        
dataValidatorBuilder.reset().parameter(RescheduleLoansApiConstants.graceOnPrincipalParamName).value(graceOnPrincipal).ignoreIfNull()
-                .integerGreaterThanZero();
-
-        final Integer graceOnInterest = this.fromJsonHelper
-                
.extractIntegerWithLocaleNamed(RescheduleLoansApiConstants.graceOnInterestParamName,
 jsonElement);
-        
dataValidatorBuilder.reset().parameter(RescheduleLoansApiConstants.graceOnInterestParamName).value(graceOnInterest).ignoreIfNull()
-                .integerGreaterThanZero();
-
-        final Integer extraTerms = 
this.fromJsonHelper.extractIntegerWithLocaleNamed(RescheduleLoansApiConstants.extraTermsParamName,
-                jsonElement);
-        
dataValidatorBuilder.reset().parameter(RescheduleLoansApiConstants.extraTermsParamName).value(extraTerms).ignoreIfNull()
-                .integerGreaterThanZero();
-
-        final Long rescheduleReasonId = 
this.fromJsonHelper.extractLongNamed(RescheduleLoansApiConstants.rescheduleReasonIdParamName,
-                jsonElement);
-        
dataValidatorBuilder.reset().parameter(RescheduleLoansApiConstants.rescheduleReasonIdParamName).value(rescheduleReasonId).notNull()
-                .integerGreaterThanZero();
-
-        final String rescheduleReasonComment = this.fromJsonHelper
-                
.extractStringNamed(RescheduleLoansApiConstants.rescheduleReasonCommentParamName,
 jsonElement);
-        
dataValidatorBuilder.reset().parameter(RescheduleLoansApiConstants.rescheduleReasonCommentParamName).value(rescheduleReasonComment)
-                .ignoreIfNull().notExceedingLengthOf(500);
-
-        final LocalDate adjustedDueDate = 
this.fromJsonHelper.extractLocalDateNamed(RescheduleLoansApiConstants.adjustedDueDateParamName,
-                jsonElement);
-
-        if (adjustedDueDate != null && DateUtils.isBefore(adjustedDueDate, 
rescheduleFromDate)) {
-            
dataValidatorBuilder.reset().parameter(RescheduleLoansApiConstants.rescheduleFromDateParamName).failWithCode(
-                    "adjustedDueDate.before.rescheduleFromDate", "Adjusted due 
date cannot be before the reschedule from date");
-        }
-
-        if 
(loan.getLoanProduct().getLoanProductRelatedDetail().getLoanScheduleType() == 
LoanScheduleType.CUMULATIVE) {
-            final LocalDate endDate = 
jsonCommand.localDateValueOfParameterNamed(RescheduleLoansApiConstants.endDateParamName);
-            final BigDecimal emi = 
jsonCommand.bigDecimalValueOfParameterNamed(RescheduleLoansApiConstants.emiParamName);
-            validateForCumulativeLoan(dataValidatorBuilder, loan, jsonElement, 
rescheduleFromDate, endDate, emi);
-        } else {
-            validateForProgressiveLoan(dataValidatorBuilder, loan, 
jsonElement, rescheduleFromDate, adjustedDueDate);
-        }
-
-        if (!dataValidationErrors.isEmpty()) {
-            throw new PlatformApiDataValidationException(dataValidationErrors);
-        }
-    }
-
-    private void validateForProgressiveLoan(final DataValidatorBuilder 
dataValidatorBuilder, final Loan loan, final JsonElement jsonElement,
-            final LocalDate rescheduleFromDate, final LocalDate 
adjustedDueDate) {
-        final var unsupportedFields = 
List.of(RescheduleLoansApiConstants.graceOnPrincipalParamName, //
-                RescheduleLoansApiConstants.graceOnInterestParamName, //
-                RescheduleLoansApiConstants.extraTermsParamName, //
-                RescheduleLoansApiConstants.emiParamName//
-        );
-
-        for (var unsupportedField : unsupportedFields) {
-            if (this.fromJsonHelper.parameterHasValue(unsupportedField, 
jsonElement)) {
-                
dataValidatorBuilder.reset().parameter(unsupportedField).failWithCode(
-                        
RescheduleLoansApiConstants.rescheduleSelectedOperationNotSupportedErrorCode,
-                        "Selected operation is not supported by Progressive 
Loan at a time during Loan Rescheduling");
-                return;
-            }
-        }
-
-        final LocalDate businessDate = DateUtils.getBusinessLocalDate();
-        LoanRepaymentScheduleInstallment installment = null;
-        if (rescheduleFromDate != null) {
-            boolean hasInterestRateChange = 
this.fromJsonHelper.parameterHasValue(RescheduleLoansApiConstants.newInterestRateParamName,
-                    jsonElement);
-            if (hasInterestRateChange && adjustedDueDate != null) {
-                
dataValidatorBuilder.reset().parameter(RescheduleLoansApiConstants.adjustedDueDateParamName).failWithCode(
-                        
RescheduleLoansApiConstants.rescheduleMultipleOperationsNotSupportedErrorCode,
-                        "Only one operation is supported at a time during Loan 
Rescheduling");
-                return;
-            }
-
-            if (hasInterestRateChange && 
!rescheduleFromDate.isAfter(businessDate)) {
-                
dataValidatorBuilder.reset().parameter(RescheduleLoansApiConstants.rescheduleFromDateParamName).failWithCode(
-                        
"loan.reschedule.interestratechange.reschedulefrom.shouldbefuture",
-                        "Loan Reschedule From date should be in the future.");
-            }
-
-            if (adjustedDueDate != null) {
-                installment = 
loan.getRepaymentScheduleInstallment(rescheduleFromDate);
-            } else if (hasInterestRateChange) {
-                installment = 
loan.getRelatedRepaymentScheduleInstallment(rescheduleFromDate);
-            }
-
-            validateReschedulingInstallment(dataValidatorBuilder, installment);
-        }
-
-        validateForOverdueCharges(dataValidatorBuilder, loan, installment);
-    }
-
-    private void validateForCumulativeLoan(final DataValidatorBuilder 
dataValidatorBuilder, final Loan loan, final JsonElement jsonElement,
-            final LocalDate rescheduleFromDate, final LocalDate endDate, final 
BigDecimal emi) {
-        if (emi != null || endDate != null) {
-            
dataValidatorBuilder.reset().parameter(RescheduleLoansApiConstants.endDateParamName).value(endDate).notNull();
-            
dataValidatorBuilder.reset().parameter(RescheduleLoansApiConstants.emiParamName).value(emi).notNull().positiveAmount();
-
-            if (endDate != null) {
-                LoanRepaymentScheduleInstallment endInstallment = 
loan.getRepaymentScheduleInstallment(endDate);
-
-                if (endInstallment == null) {
-                    
dataValidatorBuilder.reset().parameter(RescheduleLoansApiConstants.endDateParamName)
-                            
.failWithCode("repayment.schedule.installment.does.not.exist", "Repayment 
schedule installment does not exist");
-                }
-            }
-        }
-
-        // at least one of the following must be provided => graceOnPrincipal,
-        // graceOnInterest, extraTerms, newInterestRate
-        if 
(!this.fromJsonHelper.parameterExists(RescheduleLoansApiConstants.graceOnPrincipalParamName,
 jsonElement)
-                && 
!this.fromJsonHelper.parameterExists(RescheduleLoansApiConstants.graceOnInterestParamName,
 jsonElement)
-                && 
!this.fromJsonHelper.parameterExists(RescheduleLoansApiConstants.extraTermsParamName,
 jsonElement)
-                && 
!this.fromJsonHelper.parameterExists(RescheduleLoansApiConstants.newInterestRateParamName,
 jsonElement)
-                && 
!this.fromJsonHelper.parameterExists(RescheduleLoansApiConstants.adjustedDueDateParamName,
 jsonElement)
-                && 
!this.fromJsonHelper.parameterExists(RescheduleLoansApiConstants.emiParamName, 
jsonElement)) {
-            
dataValidatorBuilder.reset().parameter(RescheduleLoansApiConstants.graceOnPrincipalParamName).notNull();
-        }
-
-        final LoanRepaymentScheduleInstallment installment = 
loan.getRepaymentScheduleInstallment(rescheduleFromDate);
-
-        if (rescheduleFromDate != null) {
-            validateReschedulingInstallment(dataValidatorBuilder, installment);
-        }
-
-        if (loan.isMultiDisburmentLoan()) {
-            if (!loan.loanProduct().isDisallowExpectedDisbursements()) {
-                
dataValidatorBuilder.reset().failWithCodeNoParameterAddedToErrorCode(
-                        
RescheduleLoansApiConstants.rescheduleForMultiDisbursementNotSupportedErrorCode,
-                        "Loan rescheduling is not supported for 
multidisbursement tranche loans");
-            }
-        }
-
-        validateForOverdueCharges(dataValidatorBuilder, loan, installment);
-    }
-
-    private static void validateReschedulingInstallment(DataValidatorBuilder 
dataValidatorBuilder,
-            LoanRepaymentScheduleInstallment installment) {
-        if (installment == null) {
-            
dataValidatorBuilder.reset().parameter(RescheduleLoansApiConstants.rescheduleFromDateParamName)
-                    
.failWithCode("repayment.schedule.installment.does.not.exist", "Repayment 
schedule installment does not exist");
-        }
-
-        if (installment != null && installment.isObligationsMet()) {
-            
dataValidatorBuilder.reset().parameter(RescheduleLoansApiConstants.rescheduleFromDateParamName)
-                    
.failWithCode("repayment.schedule.installment.obligation.met", "Repayment 
schedule installment obligation met");
-        }
-    }
-
-    private void validateForOverdueCharges(final DataValidatorBuilder 
dataValidatorBuilder, final Loan loan,
-            final LoanRepaymentScheduleInstallment installment) {
-        if (installment != null) {
-            LocalDate rescheduleFromDate = installment.getFromDate();
-            Collection<LoanCharge> charges = loan.getLoanCharges();
-            for (LoanCharge loanCharge : charges) {
-                if (loanCharge.isOverdueInstallmentCharge() && 
DateUtils.isAfter(loanCharge.getDueLocalDate(), rescheduleFromDate)) {
-                    
dataValidatorBuilder.failWithCodeNoParameterAddedToErrorCode("not.allowed.due.to.overdue.charges");
-                    break;
-                }
-            }
-        }
-    }
-
-    /**
-     * Validates a user request to approve a loan reschedule request
-     *
-     * @param jsonCommand
-     *            the JSON command object (instance of the JsonCommand class)
-     *
-     **/
-    public void validateForApproveAction(final JsonCommand jsonCommand, 
LoanRescheduleRequest loanRescheduleRequest) {
-        final String jsonString = jsonCommand.json();
-
-        if (StringUtils.isBlank(jsonString)) {
-            throw new InvalidJsonException();
-        }
-
-        final Type typeToken = new TypeToken<Map<String, Object>>() 
{}.getType();
-        this.fromJsonHelper.checkForUnsupportedParameters(typeToken, 
jsonString, APPROVE_REQUEST_DATA_PARAMETERS);
-
-        final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
-        final DataValidatorBuilder dataValidatorBuilder = new 
DataValidatorBuilder(dataValidationErrors)
-                
.resource(StringUtils.lowerCase(RescheduleLoansApiConstants.ENTITY_NAME));
-
-        final JsonElement jsonElement = jsonCommand.parsedJson();
-
-        final LocalDate approvedOnDate = 
this.fromJsonHelper.extractLocalDateNamed(RescheduleLoansApiConstants.approvedOnDateParam,
-                jsonElement);
-        
dataValidatorBuilder.reset().parameter(RescheduleLoansApiConstants.approvedOnDateParam).value(approvedOnDate).notNull();
-
-        if (approvedOnDate != null && 
DateUtils.isAfter(loanRescheduleRequest.getSubmittedOnDate(), approvedOnDate)) {
-            
dataValidatorBuilder.reset().parameter(RescheduleLoansApiConstants.approvedOnDateParam).failWithCode("before.submission.date",
-                    "Approval date cannot be before the request submission 
date.");
-        }
-
-        LoanRescheduleRequestStatusEnumData 
loanRescheduleRequestStatusEnumData = LoanRescheduleRequestEnumerations
-                .status(loanRescheduleRequest.getStatusEnum());
-
-        if (!loanRescheduleRequestStatusEnumData.isPendingApproval()) {
-            
dataValidatorBuilder.reset().failWithCodeNoParameterAddedToErrorCode("request.is.not.in.submitted.and.pending.state",
-                    "Loan reschedule request approval is not allowed. "
-                            + "Loan reschedule request is not in submitted and 
pending approval state.");
-        }
-
-        LocalDate rescheduleFromDate = 
loanRescheduleRequest.getRescheduleFromDate();
-        final Loan loan = loanRescheduleRequest.getLoan();
-        LoanRepaymentScheduleInstallment installment = null;
-        if (loan != null) {
-
-            if (!loan.getStatus().isActive()) {
-                
dataValidatorBuilder.reset().failWithCodeNoParameterAddedToErrorCode("loan.is.not.active",
 "Loan is not active");
-            }
-
-            if (rescheduleFromDate != null) {
-                final boolean isProgressiveLoanSchedule = 
loan.getLoanProductRelatedDetail()
-                        .getLoanScheduleType() == LoanScheduleType.PROGRESSIVE;
-                installment = isProgressiveLoanSchedule ? 
loan.getRelatedRepaymentScheduleInstallment(rescheduleFromDate)
-                        : 
loan.getRepaymentScheduleInstallment(rescheduleFromDate);
-
-                if (installment == null) {
-                    
dataValidatorBuilder.reset().failWithCodeNoParameterAddedToErrorCode(
-                            
"loan.repayment.schedule.installment.does.not.exist", "Repayment schedule 
installment does not exist");
-                }
-
-                if (installment != null && installment.isObligationsMet()) {
-                    
dataValidatorBuilder.reset().failWithCodeNoParameterAddedToErrorCode(
-                            "loan.repayment.schedule.installment." + 
"obligation.met", "Repayment schedule installment obligation met");
-                }
-            }
-        }
-
-        validateForOverdueCharges(dataValidatorBuilder, loan, installment);
-
-        if (!dataValidationErrors.isEmpty()) {
-            throw new PlatformApiDataValidationException(dataValidationErrors);
-        }
-    }
-
-    /**
-     * Validates a user request to reject a loan reschedule request
-     *
-     * @param jsonCommand
-     *            the JSON command object (instance of the JsonCommand class)
-     *
-     **/
-    public void validateForRejectAction(final JsonCommand jsonCommand, 
LoanRescheduleRequest loanRescheduleRequest) {
-        final String jsonString = jsonCommand.json();
-
-        if (StringUtils.isBlank(jsonString)) {
-            throw new InvalidJsonException();
-        }
-
-        final Type typeToken = new TypeToken<Map<String, Object>>() 
{}.getType();
-        this.fromJsonHelper.checkForUnsupportedParameters(typeToken, 
jsonString, REJECT_REQUEST_DATA_PARAMETERS);
-
-        final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
-        final DataValidatorBuilder dataValidatorBuilder = new 
DataValidatorBuilder(dataValidationErrors)
-                
.resource(StringUtils.lowerCase(RescheduleLoansApiConstants.ENTITY_NAME));
-
-        final JsonElement jsonElement = jsonCommand.parsedJson();
-
-        final LocalDate rejectedOnDate = 
this.fromJsonHelper.extractLocalDateNamed(RescheduleLoansApiConstants.rejectedOnDateParam,
-                jsonElement);
-        
dataValidatorBuilder.reset().parameter(RescheduleLoansApiConstants.rejectedOnDateParam).value(rejectedOnDate).notNull();
-
-        if (rejectedOnDate != null && 
DateUtils.isAfter(loanRescheduleRequest.getSubmittedOnDate(), rejectedOnDate)) {
-            
dataValidatorBuilder.reset().parameter(RescheduleLoansApiConstants.rejectedOnDateParam).failWithCode("before.submission.date",
-                    "Rejection date cannot be before the request submission 
date.");
-        }
-
-        LoanRescheduleRequestStatusEnumData 
loanRescheduleRequestStatusEnumData = LoanRescheduleRequestEnumerations
-                .status(loanRescheduleRequest.getStatusEnum());
-
-        if (!loanRescheduleRequestStatusEnumData.isPendingApproval()) {
-            
dataValidatorBuilder.reset().failWithCodeNoParameterAddedToErrorCode("request.is.not.in.submitted.and.pending.state",
-                    "Loan reschedule request rejection is not allowed. "
-                            + "Loan reschedule request is not in submitted and 
pending approval state.");
-        }
-
-        if (!dataValidationErrors.isEmpty()) {
-            throw new PlatformApiDataValidationException(dataValidationErrors);
-        }
-    }
+    void validateForRejectAction(JsonCommand jsonCommand, 
LoanRescheduleRequest loanRescheduleRequest);
 }
diff --git 
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/data/LoanRescheduleRequestDataValidator.java
 
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/data/LoanRescheduleRequestDataValidatorImpl.java
similarity index 57%
copy from 
fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/data/LoanRescheduleRequestDataValidator.java
copy to 
fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/data/LoanRescheduleRequestDataValidatorImpl.java
index be174c450..4d22bde43 100644
--- 
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/data/LoanRescheduleRequestDataValidator.java
+++ 
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/data/LoanRescheduleRequestDataValidatorImpl.java
@@ -45,14 +45,14 @@ import 
org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleIns
 import 
org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleType;
 import 
org.apache.fineract.portfolio.loanaccount.rescheduleloan.RescheduleLoansApiConstants;
 import 
org.apache.fineract.portfolio.loanaccount.rescheduleloan.domain.LoanRescheduleRequest;
+import org.springframework.beans.factory.annotation.Qualifier;
 import org.springframework.stereotype.Component;
 
-@Component
+@Component("loanRescheduleRequestDataValidator")
 @AllArgsConstructor
-public class LoanRescheduleRequestDataValidator {
+public class LoanRescheduleRequestDataValidatorImpl implements 
LoanRescheduleRequestDataValidator {
 
-    private final FromJsonHelper fromJsonHelper;
-    private static final Set<String> CREATE_REQUEST_DATA_PARAMETERS = new 
HashSet<>(
+    public static final Set<String> CREATE_REQUEST_DATA_PARAMETERS = new 
HashSet<>(
             Arrays.asList(RescheduleLoansApiConstants.localeParamName, 
RescheduleLoansApiConstants.dateFormatParamName,
                     RescheduleLoansApiConstants.graceOnPrincipalParamName,
                     
RescheduleLoansApiConstants.recurringMoratoriumOnPrincipalPeriodsParamName,
@@ -62,198 +62,144 @@ public class LoanRescheduleRequestDataValidator {
                     RescheduleLoansApiConstants.submittedOnDateParamName, 
RescheduleLoansApiConstants.loanIdParamName,
                     RescheduleLoansApiConstants.adjustedDueDateParamName, 
RescheduleLoansApiConstants.recalculateInterestParamName,
                     RescheduleLoansApiConstants.endDateParamName, 
RescheduleLoansApiConstants.emiParamName));
-
     private static final Set<String> REJECT_REQUEST_DATA_PARAMETERS = new 
HashSet<>(
             Arrays.asList(RescheduleLoansApiConstants.localeParamName, 
RescheduleLoansApiConstants.dateFormatParamName,
                     RescheduleLoansApiConstants.rejectedOnDateParam));
-
-    private static final Set<String> APPROVE_REQUEST_DATA_PARAMETERS = new 
HashSet<>(
+    public static final Set<String> APPROVE_REQUEST_DATA_PARAMETERS = new 
HashSet<>(
             Arrays.asList(RescheduleLoansApiConstants.localeParamName, 
RescheduleLoansApiConstants.dateFormatParamName,
                     RescheduleLoansApiConstants.approvedOnDateParam));
+    private final FromJsonHelper fromJsonHelper;
+    @Qualifier("progressiveLoanRescheduleRequestDataValidatorImpl")
+    private final LoanRescheduleRequestDataValidator 
progressiveLoanRescheduleRequestDataValidatorDelegate;
+
+    public static BigDecimal validateInterestRate(FromJsonHelper 
fromJsonHelper, JsonElement jsonElement,
+            DataValidatorBuilder dataValidatorBuilder) {
+        final BigDecimal interestRate = fromJsonHelper
+                
.extractBigDecimalWithLocaleNamed(RescheduleLoansApiConstants.newInterestRateParamName,
 jsonElement);
+        
dataValidatorBuilder.reset().parameter(RescheduleLoansApiConstants.newInterestRateParamName).value(interestRate).ignoreIfNull()
+                .positiveAmount();
+        return interestRate;
+    }
 
-    /**
-     * Validates the request to create a new loan reschedule entry
-     *
-     * @param jsonCommand
-     *            the JSON command object (instance of the JsonCommand class)
-     *
-     **/
-    public void validateForCreateAction(final JsonCommand jsonCommand, final 
Loan loan) {
-
-        final String jsonString = jsonCommand.json();
-
-        if (StringUtils.isBlank(jsonString)) {
-            throw new InvalidJsonException();
+    private static void validateMultiDisburseLoan(Loan loan, 
DataValidatorBuilder dataValidatorBuilder) {
+        if (loan.isMultiDisburmentLoan()) {
+            if (!loan.loanProduct().isDisallowExpectedDisbursements()) {
+                
dataValidatorBuilder.reset().failWithCodeNoParameterAddedToErrorCode(
+                        
RescheduleLoansApiConstants.rescheduleForMultiDisbursementNotSupportedErrorCode,
+                        "Loan rescheduling is not supported for 
multidisbursement tranche loans");
+            }
         }
+    }
 
-        final Type typeToken = new TypeToken<Map<String, Object>>() 
{}.getType();
-        this.fromJsonHelper.checkForUnsupportedParameters(typeToken, 
jsonString, CREATE_REQUEST_DATA_PARAMETERS);
-
-        final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
-        final DataValidatorBuilder dataValidatorBuilder = new 
DataValidatorBuilder(dataValidationErrors)
-                
.resource(StringUtils.lowerCase(RescheduleLoansApiConstants.ENTITY_NAME));
+    public static void validateEMIAndEndDate(FromJsonHelper fromJsonHelper, 
Loan loan, JsonElement jsonElement,
+            DataValidatorBuilder dataValidatorBuilder) {
+        final LocalDate endDate = 
fromJsonHelper.extractLocalDateNamed(RescheduleLoansApiConstants.endDateParamName,
 jsonElement);
+        final BigDecimal emi = 
fromJsonHelper.extractBigDecimalWithLocaleNamed(RescheduleLoansApiConstants.emiParamName,
 jsonElement);
+        if (emi != null || endDate != null) {
+            
dataValidatorBuilder.reset().parameter(RescheduleLoansApiConstants.endDateParamName).value(endDate).notNull();
+            
dataValidatorBuilder.reset().parameter(RescheduleLoansApiConstants.emiParamName).value(emi).notNull().positiveAmount();
 
-        final JsonElement jsonElement = jsonCommand.parsedJson();
+            if (endDate != null) {
+                LoanRepaymentScheduleInstallment endInstallment = 
loan.getRepaymentScheduleInstallment(endDate);
 
-        if (!loan.getStatus().isActive()) {
-            
dataValidatorBuilder.reset().failWithCodeNoParameterAddedToErrorCode("loan.is.not.active",
 "Loan is not active");
+                if (endInstallment == null) {
+                    
dataValidatorBuilder.reset().parameter(RescheduleLoansApiConstants.endDateParamName)
+                            
.failWithCode("repayment.schedule.installment.does.not.exist", "Repayment 
schedule installment does not exist");
+                }
+            }
         }
+    }
 
-        final Long loanId = 
this.fromJsonHelper.extractLongNamed(RescheduleLoansApiConstants.loanIdParamName,
 jsonElement);
-        
dataValidatorBuilder.reset().parameter(RescheduleLoansApiConstants.loanIdParamName).value(loanId).notNull()
-                .integerGreaterThanZero();
-
-        final LocalDate submittedOnDate = 
this.fromJsonHelper.extractLocalDateNamed(RescheduleLoansApiConstants.submittedOnDateParamName,
+    public static LocalDate validateAndRetrieveAdjustedDate(FromJsonHelper 
fromJsonHelper, JsonElement jsonElement,
+            LocalDate rescheduleFromDate, DataValidatorBuilder 
dataValidatorBuilder) {
+        final LocalDate adjustedDueDate = 
fromJsonHelper.extractLocalDateNamed(RescheduleLoansApiConstants.adjustedDueDateParamName,
                 jsonElement);
-        
dataValidatorBuilder.reset().parameter(RescheduleLoansApiConstants.submittedOnDateParamName).value(submittedOnDate).notNull();
 
-        if (submittedOnDate != null && 
DateUtils.isAfter(loan.getDisbursementDate(), submittedOnDate)) {
-            
dataValidatorBuilder.reset().parameter(RescheduleLoansApiConstants.submittedOnDateParamName)
-                    .failWithCode("before.loan.disbursement.date", "Submission 
date cannot be before the loan disbursement date");
+        if (adjustedDueDate != null && DateUtils.isBefore(adjustedDueDate, 
rescheduleFromDate)) {
+            
dataValidatorBuilder.reset().parameter(RescheduleLoansApiConstants.rescheduleFromDateParamName).failWithCode(
+                    "adjustedDueDate.before.rescheduleFromDate", "Adjusted due 
date cannot be before the reschedule from date");
         }
+        return adjustedDueDate;
+    }
 
-        final LocalDate rescheduleFromDate = this.fromJsonHelper
-                
.extractLocalDateNamed(RescheduleLoansApiConstants.rescheduleFromDateParamName, 
jsonElement);
-        
dataValidatorBuilder.reset().parameter(RescheduleLoansApiConstants.rescheduleFromDateParamName).value(rescheduleFromDate).notNull();
-
-        final Integer graceOnPrincipal = this.fromJsonHelper
-                
.extractIntegerWithLocaleNamed(RescheduleLoansApiConstants.graceOnPrincipalParamName,
 jsonElement);
-        
dataValidatorBuilder.reset().parameter(RescheduleLoansApiConstants.graceOnPrincipalParamName).value(graceOnPrincipal).ignoreIfNull()
-                .integerGreaterThanZero();
+    public static void validateRescheduleReasonComment(FromJsonHelper 
fromJsonHelper, JsonElement jsonElement,
+            DataValidatorBuilder dataValidatorBuilder) {
+        final String rescheduleReasonComment = fromJsonHelper
+                
.extractStringNamed(RescheduleLoansApiConstants.rescheduleReasonCommentParamName,
 jsonElement);
+        
dataValidatorBuilder.reset().parameter(RescheduleLoansApiConstants.rescheduleReasonCommentParamName).value(rescheduleReasonComment)
+                .ignoreIfNull().notExceedingLengthOf(500);
+    }
 
-        final Integer graceOnInterest = this.fromJsonHelper
-                
.extractIntegerWithLocaleNamed(RescheduleLoansApiConstants.graceOnInterestParamName,
 jsonElement);
-        
dataValidatorBuilder.reset().parameter(RescheduleLoansApiConstants.graceOnInterestParamName).value(graceOnInterest).ignoreIfNull()
+    public static void validateRescheduleReasonId(FromJsonHelper 
fromJsonHelper, JsonElement jsonElement,
+            DataValidatorBuilder dataValidatorBuilder) {
+        final Long rescheduleReasonId = 
fromJsonHelper.extractLongNamed(RescheduleLoansApiConstants.rescheduleReasonIdParamName,
+                jsonElement);
+        
dataValidatorBuilder.reset().parameter(RescheduleLoansApiConstants.rescheduleReasonIdParamName).value(rescheduleReasonId).notNull()
                 .integerGreaterThanZero();
+    }
 
-        final Integer extraTerms = 
this.fromJsonHelper.extractIntegerWithLocaleNamed(RescheduleLoansApiConstants.extraTermsParamName,
+    public static void validateExtraTerms(FromJsonHelper fromJsonHelper, 
JsonElement jsonElement,
+            DataValidatorBuilder dataValidatorBuilder) {
+        final Integer extraTerms = 
fromJsonHelper.extractIntegerWithLocaleNamed(RescheduleLoansApiConstants.extraTermsParamName,
                 jsonElement);
         
dataValidatorBuilder.reset().parameter(RescheduleLoansApiConstants.extraTermsParamName).value(extraTerms).ignoreIfNull()
                 .integerGreaterThanZero();
+    }
 
-        final Long rescheduleReasonId = 
this.fromJsonHelper.extractLongNamed(RescheduleLoansApiConstants.rescheduleReasonIdParamName,
+    public static void validateGraceOnInterest(FromJsonHelper fromJsonHelper, 
JsonElement jsonElement,
+            DataValidatorBuilder dataValidatorBuilder) {
+        final Integer graceOnInterest = 
fromJsonHelper.extractIntegerWithLocaleNamed(RescheduleLoansApiConstants.graceOnInterestParamName,
                 jsonElement);
-        
dataValidatorBuilder.reset().parameter(RescheduleLoansApiConstants.rescheduleReasonIdParamName).value(rescheduleReasonId).notNull()
+        
dataValidatorBuilder.reset().parameter(RescheduleLoansApiConstants.graceOnInterestParamName).value(graceOnInterest).ignoreIfNull()
                 .integerGreaterThanZero();
+    }
 
-        final String rescheduleReasonComment = this.fromJsonHelper
-                
.extractStringNamed(RescheduleLoansApiConstants.rescheduleReasonCommentParamName,
 jsonElement);
-        
dataValidatorBuilder.reset().parameter(RescheduleLoansApiConstants.rescheduleReasonCommentParamName).value(rescheduleReasonComment)
-                .ignoreIfNull().notExceedingLengthOf(500);
-
-        final LocalDate adjustedDueDate = 
this.fromJsonHelper.extractLocalDateNamed(RescheduleLoansApiConstants.adjustedDueDateParamName,
+    public static void validateGraceOnPrincipal(FromJsonHelper fromJsonHelper, 
JsonElement jsonElement,
+            DataValidatorBuilder dataValidatorBuilder) {
+        final Integer graceOnPrincipal = 
fromJsonHelper.extractIntegerWithLocaleNamed(RescheduleLoansApiConstants.graceOnPrincipalParamName,
                 jsonElement);
-
-        if (adjustedDueDate != null && DateUtils.isBefore(adjustedDueDate, 
rescheduleFromDate)) {
-            
dataValidatorBuilder.reset().parameter(RescheduleLoansApiConstants.rescheduleFromDateParamName).failWithCode(
-                    "adjustedDueDate.before.rescheduleFromDate", "Adjusted due 
date cannot be before the reschedule from date");
-        }
-
-        if 
(loan.getLoanProduct().getLoanProductRelatedDetail().getLoanScheduleType() == 
LoanScheduleType.CUMULATIVE) {
-            final LocalDate endDate = 
jsonCommand.localDateValueOfParameterNamed(RescheduleLoansApiConstants.endDateParamName);
-            final BigDecimal emi = 
jsonCommand.bigDecimalValueOfParameterNamed(RescheduleLoansApiConstants.emiParamName);
-            validateForCumulativeLoan(dataValidatorBuilder, loan, jsonElement, 
rescheduleFromDate, endDate, emi);
-        } else {
-            validateForProgressiveLoan(dataValidatorBuilder, loan, 
jsonElement, rescheduleFromDate, adjustedDueDate);
-        }
-
-        if (!dataValidationErrors.isEmpty()) {
-            throw new PlatformApiDataValidationException(dataValidationErrors);
-        }
+        
dataValidatorBuilder.reset().parameter(RescheduleLoansApiConstants.graceOnPrincipalParamName).value(graceOnPrincipal).ignoreIfNull()
+                .integerGreaterThanZero();
     }
 
-    private void validateForProgressiveLoan(final DataValidatorBuilder 
dataValidatorBuilder, final Loan loan, final JsonElement jsonElement,
-            final LocalDate rescheduleFromDate, final LocalDate 
adjustedDueDate) {
-        final var unsupportedFields = 
List.of(RescheduleLoansApiConstants.graceOnPrincipalParamName, //
-                RescheduleLoansApiConstants.graceOnInterestParamName, //
-                RescheduleLoansApiConstants.extraTermsParamName, //
-                RescheduleLoansApiConstants.emiParamName//
-        );
-
-        for (var unsupportedField : unsupportedFields) {
-            if (this.fromJsonHelper.parameterHasValue(unsupportedField, 
jsonElement)) {
-                
dataValidatorBuilder.reset().parameter(unsupportedField).failWithCode(
-                        
RescheduleLoansApiConstants.rescheduleSelectedOperationNotSupportedErrorCode,
-                        "Selected operation is not supported by Progressive 
Loan at a time during Loan Rescheduling");
-                return;
-            }
-        }
-
-        final LocalDate businessDate = DateUtils.getBusinessLocalDate();
-        LoanRepaymentScheduleInstallment installment = null;
-        if (rescheduleFromDate != null) {
-            boolean hasInterestRateChange = 
this.fromJsonHelper.parameterHasValue(RescheduleLoansApiConstants.newInterestRateParamName,
-                    jsonElement);
-            if (hasInterestRateChange && adjustedDueDate != null) {
-                
dataValidatorBuilder.reset().parameter(RescheduleLoansApiConstants.adjustedDueDateParamName).failWithCode(
-                        
RescheduleLoansApiConstants.rescheduleMultipleOperationsNotSupportedErrorCode,
-                        "Only one operation is supported at a time during Loan 
Rescheduling");
-                return;
-            }
-
-            if (hasInterestRateChange && 
!rescheduleFromDate.isAfter(businessDate)) {
-                
dataValidatorBuilder.reset().parameter(RescheduleLoansApiConstants.rescheduleFromDateParamName).failWithCode(
-                        
"loan.reschedule.interestratechange.reschedulefrom.shouldbefuture",
-                        "Loan Reschedule From date should be in the future.");
-            }
-
-            if (adjustedDueDate != null) {
-                installment = 
loan.getRepaymentScheduleInstallment(rescheduleFromDate);
-            } else if (hasInterestRateChange) {
-                installment = 
loan.getRelatedRepaymentScheduleInstallment(rescheduleFromDate);
-            }
-
-            validateReschedulingInstallment(dataValidatorBuilder, installment);
-        }
-
-        validateForOverdueCharges(dataValidatorBuilder, loan, installment);
+    public static LocalDate 
validateAndRetrieveRescheduleFromDate(FromJsonHelper fromJsonHelper, 
JsonElement jsonElement,
+            DataValidatorBuilder dataValidatorBuilder) {
+        final LocalDate rescheduleFromDate = 
fromJsonHelper.extractLocalDateNamed(RescheduleLoansApiConstants.rescheduleFromDateParamName,
+                jsonElement);
+        
dataValidatorBuilder.reset().parameter(RescheduleLoansApiConstants.rescheduleFromDateParamName).value(rescheduleFromDate).notNull();
+        return rescheduleFromDate;
     }
 
-    private void validateForCumulativeLoan(final DataValidatorBuilder 
dataValidatorBuilder, final Loan loan, final JsonElement jsonElement,
-            final LocalDate rescheduleFromDate, final LocalDate endDate, final 
BigDecimal emi) {
-        if (emi != null || endDate != null) {
-            
dataValidatorBuilder.reset().parameter(RescheduleLoansApiConstants.endDateParamName).value(endDate).notNull();
-            
dataValidatorBuilder.reset().parameter(RescheduleLoansApiConstants.emiParamName).value(emi).notNull().positiveAmount();
-
-            if (endDate != null) {
-                LoanRepaymentScheduleInstallment endInstallment = 
loan.getRepaymentScheduleInstallment(endDate);
+    public static void validateSubmittedOnDate(FromJsonHelper fromJsonHelper, 
Loan loan, JsonElement jsonElement,
+            DataValidatorBuilder dataValidatorBuilder) {
+        final LocalDate submittedOnDate = 
fromJsonHelper.extractLocalDateNamed(RescheduleLoansApiConstants.submittedOnDateParamName,
+                jsonElement);
+        
dataValidatorBuilder.reset().parameter(RescheduleLoansApiConstants.submittedOnDateParamName).value(submittedOnDate).notNull();
 
-                if (endInstallment == null) {
-                    
dataValidatorBuilder.reset().parameter(RescheduleLoansApiConstants.endDateParamName)
-                            
.failWithCode("repayment.schedule.installment.does.not.exist", "Repayment 
schedule installment does not exist");
-                }
-            }
+        if (submittedOnDate != null && 
DateUtils.isAfter(loan.getDisbursementDate(), submittedOnDate)) {
+            
dataValidatorBuilder.reset().parameter(RescheduleLoansApiConstants.submittedOnDateParamName)
+                    .failWithCode("before.loan.disbursement.date", "Submission 
date cannot be before the loan disbursement date");
         }
+    }
 
-        // at least one of the following must be provided => graceOnPrincipal,
-        // graceOnInterest, extraTerms, newInterestRate
-        if 
(!this.fromJsonHelper.parameterExists(RescheduleLoansApiConstants.graceOnPrincipalParamName,
 jsonElement)
-                && 
!this.fromJsonHelper.parameterExists(RescheduleLoansApiConstants.graceOnInterestParamName,
 jsonElement)
-                && 
!this.fromJsonHelper.parameterExists(RescheduleLoansApiConstants.extraTermsParamName,
 jsonElement)
-                && 
!this.fromJsonHelper.parameterExists(RescheduleLoansApiConstants.newInterestRateParamName,
 jsonElement)
-                && 
!this.fromJsonHelper.parameterExists(RescheduleLoansApiConstants.adjustedDueDateParamName,
 jsonElement)
-                && 
!this.fromJsonHelper.parameterExists(RescheduleLoansApiConstants.emiParamName, 
jsonElement)) {
-            
dataValidatorBuilder.reset().parameter(RescheduleLoansApiConstants.graceOnPrincipalParamName).notNull();
+    public static void validateLoanIsActive(Loan loan, DataValidatorBuilder 
dataValidatorBuilder) {
+        if (!loan.getStatus().isActive()) {
+            
dataValidatorBuilder.reset().failWithCodeNoParameterAddedToErrorCode("loan.is.not.active",
 "Loan is not active");
         }
+    }
 
-        final LoanRepaymentScheduleInstallment installment = 
loan.getRepaymentScheduleInstallment(rescheduleFromDate);
-
-        if (rescheduleFromDate != null) {
-            validateReschedulingInstallment(dataValidatorBuilder, installment);
-        }
+    public static void validateSupportedParameters(JsonCommand jsonCommand, 
Set<String> createRequestDataParameters) {
+        final String jsonString = jsonCommand.json();
 
-        if (loan.isMultiDisburmentLoan()) {
-            if (!loan.loanProduct().isDisallowExpectedDisbursements()) {
-                
dataValidatorBuilder.reset().failWithCodeNoParameterAddedToErrorCode(
-                        
RescheduleLoansApiConstants.rescheduleForMultiDisbursementNotSupportedErrorCode,
-                        "Loan rescheduling is not supported for 
multidisbursement tranche loans");
-            }
+        if (StringUtils.isBlank(jsonString)) {
+            throw new InvalidJsonException();
         }
 
-        validateForOverdueCharges(dataValidatorBuilder, loan, installment);
+        final Type typeToken = new TypeToken<Map<String, Object>>() 
{}.getType();
+        jsonCommand.checkForUnsupportedParameters(typeToken, jsonString, 
createRequestDataParameters);
     }
 
-    private static void validateReschedulingInstallment(DataValidatorBuilder 
dataValidatorBuilder,
+    public static void validateReschedulingInstallment(DataValidatorBuilder 
dataValidatorBuilder,
             LoanRepaymentScheduleInstallment installment) {
         if (installment == null) {
             
dataValidatorBuilder.reset().parameter(RescheduleLoansApiConstants.rescheduleFromDateParamName)
@@ -266,7 +212,7 @@ public class LoanRescheduleRequestDataValidator {
         }
     }
 
-    private void validateForOverdueCharges(final DataValidatorBuilder 
dataValidatorBuilder, final Loan loan,
+    public static void validateForOverdueCharges(final DataValidatorBuilder 
dataValidatorBuilder, final Loan loan,
             final LoanRepaymentScheduleInstallment installment) {
         if (installment != null) {
             LocalDate rescheduleFromDate = installment.getFromDate();
@@ -281,37 +227,99 @@ public class LoanRescheduleRequestDataValidator {
     }
 
     /**
-     * Validates a user request to approve a loan reschedule request
+     * Validates the request to create a new loan reschedule entry
      *
      * @param jsonCommand
      *            the JSON command object (instance of the JsonCommand class)
+     **/
+    @Override
+    public void validateForCreateAction(final JsonCommand jsonCommand, final 
Loan loan) {
+        if (loan.getLoanProductRelatedDetail().getLoanScheduleType() == 
LoanScheduleType.PROGRESSIVE) {
+            
progressiveLoanRescheduleRequestDataValidatorDelegate.validateForCreateAction(jsonCommand,
 loan);
+        } else {
+            validateSupportedParameters(jsonCommand, 
CREATE_REQUEST_DATA_PARAMETERS);
+            final List<ApiParameterError> dataValidationErrors = new 
ArrayList<>();
+            final DataValidatorBuilder dataValidatorBuilder = new 
DataValidatorBuilder(dataValidationErrors)
+                    
.resource(StringUtils.lowerCase(RescheduleLoansApiConstants.ENTITY_NAME));
+
+            final JsonElement jsonElement = jsonCommand.parsedJson();
+            validateLoanIsActive(loan, dataValidatorBuilder);
+            validateSubmittedOnDate(fromJsonHelper, loan, jsonElement, 
dataValidatorBuilder);
+            final LocalDate rescheduleFromDate = 
validateAndRetrieveRescheduleFromDate(fromJsonHelper, jsonElement, 
dataValidatorBuilder);
+            validateInterestRate(fromJsonHelper, jsonElement, 
dataValidatorBuilder);
+            validateGraceOnPrincipal(fromJsonHelper, jsonElement, 
dataValidatorBuilder);
+            validateGraceOnInterest(fromJsonHelper, jsonElement, 
dataValidatorBuilder);
+            validateExtraTerms(fromJsonHelper, jsonElement, 
dataValidatorBuilder);
+            validateRescheduleReasonId(fromJsonHelper, jsonElement, 
dataValidatorBuilder);
+            validateRescheduleReasonComment(fromJsonHelper, jsonElement, 
dataValidatorBuilder);
+            validateAndRetrieveAdjustedDate(fromJsonHelper, jsonElement, 
rescheduleFromDate, dataValidatorBuilder);
+            validateEMIAndEndDate(fromJsonHelper, loan, jsonElement, 
dataValidatorBuilder);
+            validateIsThereAnyIncomingChange(fromJsonHelper, jsonElement, 
dataValidatorBuilder);
+            validateMultiDisburseLoan(loan, dataValidatorBuilder);
+
+            LoanRepaymentScheduleInstallment installment = 
loan.getRepaymentScheduleInstallment(rescheduleFromDate);
+            validateReschedulingInstallment(dataValidatorBuilder, installment);
+            validateForOverdueCharges(dataValidatorBuilder, loan, installment);
+
+            if (!dataValidationErrors.isEmpty()) {
+                throw new 
PlatformApiDataValidationException(dataValidationErrors);
+            }
+        }
+    }
+
+    private void validateIsThereAnyIncomingChange(FromJsonHelper 
fromJsonHelper, JsonElement jsonElement,
+            DataValidatorBuilder dataValidatorBuilder) {
+        // at least one of the following must be provided => graceOnPrincipal,
+        // graceOnInterest, extraTerms, newInterestRate
+        if 
(!fromJsonHelper.parameterExists(RescheduleLoansApiConstants.graceOnPrincipalParamName,
 jsonElement)
+                && 
!fromJsonHelper.parameterExists(RescheduleLoansApiConstants.graceOnInterestParamName,
 jsonElement)
+                && 
!fromJsonHelper.parameterExists(RescheduleLoansApiConstants.extraTermsParamName,
 jsonElement)
+                && 
!fromJsonHelper.parameterExists(RescheduleLoansApiConstants.newInterestRateParamName,
 jsonElement)
+                && 
!fromJsonHelper.parameterExists(RescheduleLoansApiConstants.adjustedDueDateParamName,
 jsonElement)
+                && 
!fromJsonHelper.parameterExists(RescheduleLoansApiConstants.emiParamName, 
jsonElement)) {
+            
dataValidatorBuilder.reset().parameter(RescheduleLoansApiConstants.graceOnPrincipalParamName).notNull();
+        }
+    }
+
+    /**
+     * Validates a user request to approve a loan reschedule request
      *
+     * @param jsonCommand
+     *            the JSON command object (instance of the JsonCommand class)
      **/
+    @Override
     public void validateForApproveAction(final JsonCommand jsonCommand, 
LoanRescheduleRequest loanRescheduleRequest) {
-        final String jsonString = jsonCommand.json();
+        if 
(loanRescheduleRequest.getLoan().getLoanRepaymentScheduleDetail().getLoanScheduleType().equals(LoanScheduleType.PROGRESSIVE))
 {
+            
progressiveLoanRescheduleRequestDataValidatorDelegate.validateForApproveAction(jsonCommand,
 loanRescheduleRequest);
+        } else {
+            validateSupportedParameters(jsonCommand, 
APPROVE_REQUEST_DATA_PARAMETERS);
 
-        if (StringUtils.isBlank(jsonString)) {
-            throw new InvalidJsonException();
-        }
+            final List<ApiParameterError> dataValidationErrors = new 
ArrayList<>();
+            final DataValidatorBuilder dataValidatorBuilder = new 
DataValidatorBuilder(dataValidationErrors)
+                    
.resource(StringUtils.lowerCase(RescheduleLoansApiConstants.ENTITY_NAME));
 
-        final Type typeToken = new TypeToken<Map<String, Object>>() 
{}.getType();
-        this.fromJsonHelper.checkForUnsupportedParameters(typeToken, 
jsonString, APPROVE_REQUEST_DATA_PARAMETERS);
+            final JsonElement jsonElement = jsonCommand.parsedJson();
+            validateApprovalDate(fromJsonHelper, loanRescheduleRequest, 
jsonElement, dataValidatorBuilder);
+            validateRescheduleRequestStatus(loanRescheduleRequest, 
dataValidatorBuilder);
 
-        final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
-        final DataValidatorBuilder dataValidatorBuilder = new 
DataValidatorBuilder(dataValidationErrors)
-                
.resource(StringUtils.lowerCase(RescheduleLoansApiConstants.ENTITY_NAME));
+            LocalDate rescheduleFromDate = 
loanRescheduleRequest.getRescheduleFromDate();
+            final Loan loan = loanRescheduleRequest.getLoan();
+            LoanRepaymentScheduleInstallment installment;
 
-        final JsonElement jsonElement = jsonCommand.parsedJson();
+            validateLoanIsActive(loan, dataValidatorBuilder);
+            installment = 
loan.getRepaymentScheduleInstallment(rescheduleFromDate);
 
-        final LocalDate approvedOnDate = 
this.fromJsonHelper.extractLocalDateNamed(RescheduleLoansApiConstants.approvedOnDateParam,
-                jsonElement);
-        
dataValidatorBuilder.reset().parameter(RescheduleLoansApiConstants.approvedOnDateParam).value(approvedOnDate).notNull();
+            validateReschedulingInstallment(dataValidatorBuilder, installment);
+            validateForOverdueCharges(dataValidatorBuilder, loan, installment);
 
-        if (approvedOnDate != null && 
DateUtils.isAfter(loanRescheduleRequest.getSubmittedOnDate(), approvedOnDate)) {
-            
dataValidatorBuilder.reset().parameter(RescheduleLoansApiConstants.approvedOnDateParam).failWithCode("before.submission.date",
-                    "Approval date cannot be before the request submission 
date.");
+            if (!dataValidationErrors.isEmpty()) {
+                throw new 
PlatformApiDataValidationException(dataValidationErrors);
+            }
         }
+    }
 
+    public static void validateRescheduleRequestStatus(LoanRescheduleRequest 
loanRescheduleRequest,
+            DataValidatorBuilder dataValidatorBuilder) {
         LoanRescheduleRequestStatusEnumData 
loanRescheduleRequestStatusEnumData = LoanRescheduleRequestEnumerations
                 .status(loanRescheduleRequest.getStatusEnum());
 
@@ -320,38 +328,16 @@ public class LoanRescheduleRequestDataValidator {
                     "Loan reschedule request approval is not allowed. "
                             + "Loan reschedule request is not in submitted and 
pending approval state.");
         }
+    }
 
-        LocalDate rescheduleFromDate = 
loanRescheduleRequest.getRescheduleFromDate();
-        final Loan loan = loanRescheduleRequest.getLoan();
-        LoanRepaymentScheduleInstallment installment = null;
-        if (loan != null) {
-
-            if (!loan.getStatus().isActive()) {
-                
dataValidatorBuilder.reset().failWithCodeNoParameterAddedToErrorCode("loan.is.not.active",
 "Loan is not active");
-            }
-
-            if (rescheduleFromDate != null) {
-                final boolean isProgressiveLoanSchedule = 
loan.getLoanProductRelatedDetail()
-                        .getLoanScheduleType() == LoanScheduleType.PROGRESSIVE;
-                installment = isProgressiveLoanSchedule ? 
loan.getRelatedRepaymentScheduleInstallment(rescheduleFromDate)
-                        : 
loan.getRepaymentScheduleInstallment(rescheduleFromDate);
-
-                if (installment == null) {
-                    
dataValidatorBuilder.reset().failWithCodeNoParameterAddedToErrorCode(
-                            
"loan.repayment.schedule.installment.does.not.exist", "Repayment schedule 
installment does not exist");
-                }
-
-                if (installment != null && installment.isObligationsMet()) {
-                    
dataValidatorBuilder.reset().failWithCodeNoParameterAddedToErrorCode(
-                            "loan.repayment.schedule.installment." + 
"obligation.met", "Repayment schedule installment obligation met");
-                }
-            }
-        }
-
-        validateForOverdueCharges(dataValidatorBuilder, loan, installment);
+    public static void validateApprovalDate(FromJsonHelper fromJsonHelper, 
LoanRescheduleRequest loanRescheduleRequest,
+            JsonElement jsonElement, DataValidatorBuilder 
dataValidatorBuilder) {
+        final LocalDate approvedOnDate = 
fromJsonHelper.extractLocalDateNamed(RescheduleLoansApiConstants.approvedOnDateParam,
 jsonElement);
+        
dataValidatorBuilder.reset().parameter(RescheduleLoansApiConstants.approvedOnDateParam).value(approvedOnDate).notNull();
 
-        if (!dataValidationErrors.isEmpty()) {
-            throw new PlatformApiDataValidationException(dataValidationErrors);
+        if (approvedOnDate != null && 
DateUtils.isAfter(loanRescheduleRequest.getSubmittedOnDate(), approvedOnDate)) {
+            
dataValidatorBuilder.reset().parameter(RescheduleLoansApiConstants.approvedOnDateParam).failWithCode("before.submission.date",
+                    "Approval date cannot be before the request submission 
date.");
         }
     }
 
@@ -360,17 +346,10 @@ public class LoanRescheduleRequestDataValidator {
      *
      * @param jsonCommand
      *            the JSON command object (instance of the JsonCommand class)
-     *
      **/
+    @Override
     public void validateForRejectAction(final JsonCommand jsonCommand, 
LoanRescheduleRequest loanRescheduleRequest) {
-        final String jsonString = jsonCommand.json();
-
-        if (StringUtils.isBlank(jsonString)) {
-            throw new InvalidJsonException();
-        }
-
-        final Type typeToken = new TypeToken<Map<String, Object>>() 
{}.getType();
-        this.fromJsonHelper.checkForUnsupportedParameters(typeToken, 
jsonString, REJECT_REQUEST_DATA_PARAMETERS);
+        validateSupportedParameters(jsonCommand, 
REJECT_REQUEST_DATA_PARAMETERS);
 
         final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
         final DataValidatorBuilder dataValidatorBuilder = new 
DataValidatorBuilder(dataValidationErrors)
diff --git 
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/domain/LoanRescheduleRequest.java
 
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/domain/LoanRescheduleRequest.java
index d7eb19a7c..b8211af7e 100644
--- 
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/domain/LoanRescheduleRequest.java
+++ 
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/domain/LoanRescheduleRequest.java
@@ -272,14 +272,15 @@ public class LoanRescheduleRequest extends 
AbstractPersistableCustom<Long> {
         return this.loanRescheduleRequestToTermVariationMappings;
     }
 
+    public LoanTermVariations 
getInterestRateFromInstallmentTermVariationIfExists() {
+        return this.loanRescheduleRequestToTermVariationMappings.stream()
+                
.map(LoanRescheduleRequestToTermVariationMapping::getLoanTermVariations)
+                .filter(loanTermVariations -> 
loanTermVariations.getTermType().isInterestRateFromInstallment()).findFirst().orElse(null);
+    }
+
     public LoanTermVariations getDueDateTermVariationIfExists() {
-        if (this.loanRescheduleRequestToTermVariationMappings != null && 
this.loanRescheduleRequestToTermVariationMappings.size() > 0) {
-            for (LoanRescheduleRequestToTermVariationMapping mapping : 
this.loanRescheduleRequestToTermVariationMappings) {
-                if 
(mapping.getLoanTermVariations().getTermType().isDueDateVariation()) {
-                    return mapping.getLoanTermVariations();
-                }
-            }
-        }
-        return null;
+        return this.loanRescheduleRequestToTermVariationMappings.stream()
+                
.map(LoanRescheduleRequestToTermVariationMapping::getLoanTermVariations)
+                .filter(loanTermVariations -> 
loanTermVariations.getTermType().isDueDateVariation()).findFirst().orElse(null);
     }
 }
diff --git 
a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/data/ProgressiveLoanRescheduleRequestDataValidator.java
 
b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/data/ProgressiveLoanRescheduleRequestDataValidator.java
new file mode 100644
index 000000000..8736ab5c5
--- /dev/null
+++ 
b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/data/ProgressiveLoanRescheduleRequestDataValidator.java
@@ -0,0 +1,188 @@
+/**
+ * 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.rescheduleloan.data;
+
+import static 
org.apache.fineract.portfolio.loanaccount.rescheduleloan.data.LoanRescheduleRequestDataValidatorImpl.APPROVE_REQUEST_DATA_PARAMETERS;
+import static 
org.apache.fineract.portfolio.loanaccount.rescheduleloan.data.LoanRescheduleRequestDataValidatorImpl.CREATE_REQUEST_DATA_PARAMETERS;
+import static 
org.apache.fineract.portfolio.loanaccount.rescheduleloan.data.LoanRescheduleRequestDataValidatorImpl.validateAndRetrieveAdjustedDate;
+import static 
org.apache.fineract.portfolio.loanaccount.rescheduleloan.data.LoanRescheduleRequestDataValidatorImpl.validateAndRetrieveRescheduleFromDate;
+import static 
org.apache.fineract.portfolio.loanaccount.rescheduleloan.data.LoanRescheduleRequestDataValidatorImpl.validateApprovalDate;
+import static 
org.apache.fineract.portfolio.loanaccount.rescheduleloan.data.LoanRescheduleRequestDataValidatorImpl.validateForOverdueCharges;
+import static 
org.apache.fineract.portfolio.loanaccount.rescheduleloan.data.LoanRescheduleRequestDataValidatorImpl.validateInterestRate;
+import static 
org.apache.fineract.portfolio.loanaccount.rescheduleloan.data.LoanRescheduleRequestDataValidatorImpl.validateLoanIsActive;
+import static 
org.apache.fineract.portfolio.loanaccount.rescheduleloan.data.LoanRescheduleRequestDataValidatorImpl.validateRescheduleReasonComment;
+import static 
org.apache.fineract.portfolio.loanaccount.rescheduleloan.data.LoanRescheduleRequestDataValidatorImpl.validateRescheduleReasonId;
+import static 
org.apache.fineract.portfolio.loanaccount.rescheduleloan.data.LoanRescheduleRequestDataValidatorImpl.validateRescheduleRequestStatus;
+import static 
org.apache.fineract.portfolio.loanaccount.rescheduleloan.data.LoanRescheduleRequestDataValidatorImpl.validateReschedulingInstallment;
+import static 
org.apache.fineract.portfolio.loanaccount.rescheduleloan.data.LoanRescheduleRequestDataValidatorImpl.validateSubmittedOnDate;
+import static 
org.apache.fineract.portfolio.loanaccount.rescheduleloan.data.LoanRescheduleRequestDataValidatorImpl.validateSupportedParameters;
+
+import com.google.gson.JsonElement;
+import jakarta.persistence.criteria.Predicate;
+import java.math.BigDecimal;
+import java.time.LocalDate;
+import java.util.ArrayList;
+import java.util.List;
+import lombok.AllArgsConstructor;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.ApiParameterError;
+import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder;
+import 
org.apache.fineract.infrastructure.core.exception.GeneralPlatformDomainRuleException;
+import 
org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
+import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
+import org.apache.fineract.infrastructure.core.service.DateUtils;
+import org.apache.fineract.portfolio.loanaccount.domain.Loan;
+import 
org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanStatus;
+import 
org.apache.fineract.portfolio.loanaccount.rescheduleloan.RescheduleLoansApiConstants;
+import 
org.apache.fineract.portfolio.loanaccount.rescheduleloan.domain.LoanRescheduleRequest;
+import 
org.apache.fineract.portfolio.loanaccount.rescheduleloan.domain.LoanRescheduleRequestRepository;
+import org.springframework.stereotype.Component;
+
+@Component("progressiveLoanRescheduleRequestDataValidatorImpl")
+@AllArgsConstructor
+public class ProgressiveLoanRescheduleRequestDataValidator implements 
LoanRescheduleRequestDataValidator {
+
+    private final FromJsonHelper fromJsonHelper;
+    private final LoanRescheduleRequestRepository 
loanRescheduleRequestRepository;
+
+    @Override
+    public void validateForCreateAction(JsonCommand jsonCommand, Loan loan) {
+        validateSupportedParameters(jsonCommand, 
CREATE_REQUEST_DATA_PARAMETERS);
+
+        final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+        final DataValidatorBuilder dataValidatorBuilder = new 
DataValidatorBuilder(dataValidationErrors)
+                
.resource(StringUtils.lowerCase(RescheduleLoansApiConstants.ENTITY_NAME));
+
+        final JsonElement jsonElement = jsonCommand.parsedJson();
+
+        validateLoanIsActive(loan, dataValidatorBuilder);
+        validateSubmittedOnDate(fromJsonHelper, loan, jsonElement, 
dataValidatorBuilder);
+        final LocalDate rescheduleFromDate = 
validateAndRetrieveRescheduleFromDate(fromJsonHelper, jsonElement, 
dataValidatorBuilder);
+        validateRescheduleReasonId(fromJsonHelper, jsonElement, 
dataValidatorBuilder);
+        validateRescheduleReasonComment(fromJsonHelper, jsonElement, 
dataValidatorBuilder);
+        LocalDate adjustedDueDate = 
validateAndRetrieveAdjustedDate(fromJsonHelper, jsonElement, 
rescheduleFromDate, dataValidatorBuilder);
+        BigDecimal interestRate = validateInterestRate(fromJsonHelper, 
jsonElement, dataValidatorBuilder);
+        validateUnsupportedParams(jsonElement, dataValidatorBuilder);
+
+        boolean hasInterestRateChange = interestRate != null;
+        boolean hasAdjustDueDateChange = adjustedDueDate != null;
+
+        if (hasInterestRateChange && hasAdjustDueDateChange) {
+            
dataValidatorBuilder.reset().parameter(RescheduleLoansApiConstants.adjustedDueDateParamName).failWithCode(
+                    
RescheduleLoansApiConstants.rescheduleMultipleOperationsNotSupportedErrorCode,
+                    "Only one operation is supported at a time during Loan 
Rescheduling");
+        }
+
+        final LocalDate businessDate = DateUtils.getBusinessLocalDate();
+        if (rescheduleFromDate != null) {
+            if (hasInterestRateChange && 
!rescheduleFromDate.isAfter(businessDate)) {
+                
dataValidatorBuilder.reset().parameter(RescheduleLoansApiConstants.rescheduleFromDateParamName).failWithCode(
+                        
"loan.reschedule.interest.rate.change.reschedule.from.date.should.be.in.future",
+                        "Loan Reschedule From date should be in the future.");
+            }
+            if (hasInterestRateChange) {
+                validateInterestRateChangeRescheduleFromDate(loan, 
rescheduleFromDate);
+            }
+        }
+
+        LoanRepaymentScheduleInstallment installment;
+        if (hasInterestRateChange) {
+            installment = 
loan.getRelatedRepaymentScheduleInstallment(rescheduleFromDate);
+        } else {
+            installment = 
loan.getRepaymentScheduleInstallment(rescheduleFromDate);
+        }
+
+        validateReschedulingInstallment(dataValidatorBuilder, installment);
+        validateForOverdueCharges(dataValidatorBuilder, loan, installment);
+
+        if (!dataValidationErrors.isEmpty()) {
+            throw new PlatformApiDataValidationException(dataValidationErrors);
+        }
+    }
+
+    private void validateUnsupportedParams(JsonElement jsonElement, 
DataValidatorBuilder dataValidatorBuilder) {
+        final var unsupportedFields = 
List.of(RescheduleLoansApiConstants.graceOnPrincipalParamName, //
+                RescheduleLoansApiConstants.graceOnInterestParamName, //
+                RescheduleLoansApiConstants.extraTermsParamName, //
+                RescheduleLoansApiConstants.emiParamName//
+        );
+
+        for (var unsupportedField : unsupportedFields) {
+            if (this.fromJsonHelper.parameterHasValue(unsupportedField, 
jsonElement)) {
+                
dataValidatorBuilder.reset().parameter(unsupportedField).failWithCode(
+                        
RescheduleLoansApiConstants.rescheduleSelectedOperationNotSupportedErrorCode,
+                        "Selected operation is not supported by Progressive 
Loan at a time during Loan Rescheduling");
+            }
+        }
+    }
+
+    @Override
+    public void validateForApproveAction(JsonCommand jsonCommand, 
LoanRescheduleRequest loanRescheduleRequest) {
+        validateSupportedParameters(jsonCommand, 
APPROVE_REQUEST_DATA_PARAMETERS);
+
+        final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+        final DataValidatorBuilder dataValidatorBuilder = new 
DataValidatorBuilder(dataValidationErrors)
+                
.resource(StringUtils.lowerCase(RescheduleLoansApiConstants.ENTITY_NAME));
+        final JsonElement jsonElement = jsonCommand.parsedJson();
+        validateApprovalDate(fromJsonHelper, loanRescheduleRequest, 
jsonElement, dataValidatorBuilder);
+        validateRescheduleRequestStatus(loanRescheduleRequest, 
dataValidatorBuilder);
+        LocalDate rescheduleFromDate = 
loanRescheduleRequest.getRescheduleFromDate();
+        final Loan loan = loanRescheduleRequest.getLoan();
+        LoanRepaymentScheduleInstallment installment;
+        validateLoanIsActive(loan, dataValidatorBuilder);
+
+        if 
(loanRescheduleRequest.getInterestRateFromInstallmentTermVariationIfExists() != 
null) {
+            installment = 
loan.getRelatedRepaymentScheduleInstallment(rescheduleFromDate);
+            if (!rescheduleFromDate.isAfter(DateUtils.getBusinessLocalDate())) 
{
+                throw new GeneralPlatformDomainRuleException(
+                        
"loan.reschedule.interest.rate.change.reschedule.from.date.should.be.in.future",
+                        "Loan Reschedule From date should be in the future.", 
rescheduleFromDate);
+            }
+        } else {
+            installment = 
loan.getRepaymentScheduleInstallment(rescheduleFromDate);
+        }
+        validateReschedulingInstallment(dataValidatorBuilder, installment);
+        validateForOverdueCharges(dataValidatorBuilder, loan, installment);
+
+        if (!dataValidationErrors.isEmpty()) {
+            throw new PlatformApiDataValidationException(dataValidationErrors);
+        }
+    }
+
+    @Override
+    public void validateForRejectAction(JsonCommand jsonCommand, 
LoanRescheduleRequest loanRescheduleRequest) {
+        throw new UnsupportedOperationException("Nothing to override here");
+    }
+
+    private void validateInterestRateChangeRescheduleFromDate(Loan loan, 
LocalDate rescheduleFromDate) {
+        boolean alreadyExistInterestRateChange = 
loanRescheduleRequestRepository.exists((root, query, criteriaBuilder) -> {
+            Predicate loanPredicate = criteriaBuilder.equal(root.get("loan"), 
loan);
+            Predicate statusPredicate = root.get("statusEnum")
+                    
.in(List.of(LoanStatus.SUBMITTED_AND_PENDING_APPROVAL.getValue(), 
LoanStatus.APPROVED.getValue()));
+            Predicate datePredicate = 
criteriaBuilder.equal(root.get("rescheduleFromDate"), rescheduleFromDate);
+            return criteriaBuilder.and(loanPredicate, statusPredicate, 
datePredicate);
+        });
+        if (alreadyExistInterestRateChange) {
+            throw new 
GeneralPlatformDomainRuleException("loan.reschedule.interest.rate.change.already.exists",
+                    "Interest rate change for the provided date is already 
exists.", rescheduleFromDate);
+        }
+    }
+}
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/service/LoanRescheduleRequestWritePlatformServiceImpl.java
 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/service/LoanRescheduleRequestWritePlatformServiceImpl.java
index 751c3d41f..235155aaa 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/service/LoanRescheduleRequestWritePlatformServiceImpl.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/service/LoanRescheduleRequestWritePlatformServiceImpl.java
@@ -64,7 +64,6 @@ import 
org.apache.fineract.portfolio.loanaccount.domain.LoanSummaryWrapper;
 import org.apache.fineract.portfolio.loanaccount.domain.LoanTermVariationType;
 import org.apache.fineract.portfolio.loanaccount.domain.LoanTermVariations;
 import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
-import 
org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRepository;
 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.domain.DefaultScheduledDateGenerator;
@@ -87,6 +86,7 @@ import 
org.apache.fineract.portfolio.loanaccount.service.ReplayedTransactionBusi
 import org.apache.fineract.useradministration.domain.AppUser;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Qualifier;
 import org.springframework.dao.DataIntegrityViolationException;
 import org.springframework.dao.NonTransientDataAccessException;
 import org.springframework.orm.jpa.JpaSystemException;
@@ -101,11 +101,11 @@ public class 
LoanRescheduleRequestWritePlatformServiceImpl implements LoanResche
 
     private final CodeValueRepositoryWrapper codeValueRepositoryWrapper;
     private final PlatformSecurityContext platformSecurityContext;
+    @Qualifier("loanRescheduleRequestDataValidator")
     private final LoanRescheduleRequestDataValidator 
loanRescheduleRequestDataValidator;
     private final LoanRescheduleRequestRepository 
loanRescheduleRequestRepository;
     private final LoanRepaymentScheduleHistoryRepository 
loanRepaymentScheduleHistoryRepository;
     private final LoanScheduleHistoryWritePlatformService 
loanScheduleHistoryWritePlatformService;
-    private final LoanTransactionRepository loanTransactionRepository;
     private final JournalEntryWritePlatformService 
journalEntryWritePlatformService;
     private final LoanRepositoryWrapper loanRepositoryWrapper;
     private final LoanAssembler loanAssembler;
diff --git 
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/BaseLoanIntegrationTest.java
 
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/BaseLoanIntegrationTest.java
index 15d557e88..0228e044a 100644
--- 
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/BaseLoanIntegrationTest.java
+++ 
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/BaseLoanIntegrationTest.java
@@ -333,7 +333,7 @@ public abstract class BaseLoanIntegrationTest {
         }).toList();
     }
 
-    private AdvancedPaymentData createDefaultPaymentAllocation(String 
futureInstallmentAllocationRule) {
+    public AdvancedPaymentData createDefaultPaymentAllocation(String 
futureInstallmentAllocationRule) {
         AdvancedPaymentData advancedPaymentData = new AdvancedPaymentData();
         advancedPaymentData.setTransactionType("DEFAULT");
         
advancedPaymentData.setFutureInstallmentAllocationRule(futureInstallmentAllocationRule);
diff --git 
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanAccountChargeOffWithAdvancedPaymentAllocationTest.java
 
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanAccountChargeOffWithAdvancedPaymentAllocationTest.java
index 7f478bbcc..cf2877dca 100644
--- 
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanAccountChargeOffWithAdvancedPaymentAllocationTest.java
+++ 
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanAccountChargeOffWithAdvancedPaymentAllocationTest.java
@@ -32,11 +32,9 @@ import java.time.LocalDate;
 import java.time.format.DateTimeFormatter;
 import java.time.format.DateTimeFormatterBuilder;
 import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.HashMap;
 import java.util.List;
 import java.util.UUID;
-import java.util.concurrent.atomic.AtomicInteger;
 import org.apache.fineract.client.models.AdvancedPaymentData;
 import org.apache.fineract.client.models.AllowAttributeOverrides;
 import org.apache.fineract.client.models.ChargeData;
@@ -48,7 +46,6 @@ import 
org.apache.fineract.client.models.GetLoanTransactionRelation;
 import org.apache.fineract.client.models.GetLoansLoanIdResponse;
 import 
org.apache.fineract.client.models.GetLoansLoanIdTransactionsTransactionIdResponse;
 import org.apache.fineract.client.models.JournalEntryTransactionItem;
-import org.apache.fineract.client.models.PaymentAllocationOrder;
 import org.apache.fineract.client.models.PostLoanProductsRequest;
 import org.apache.fineract.client.models.PostLoanProductsResponse;
 import org.apache.fineract.client.models.PostLoansLoanIdTransactionsRequest;
@@ -73,7 +70,6 @@ import 
org.apache.fineract.integrationtests.common.products.DelinquencyBucketsHe
 import org.apache.fineract.integrationtests.common.system.CodeHelper;
 import 
org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleProcessingType;
 import 
org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleType;
-import org.apache.fineract.portfolio.loanproduct.domain.PaymentAllocationType;
 import org.junit.jupiter.api.Assertions;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
@@ -751,29 +747,4 @@ public class 
LoanAccountChargeOffWithAdvancedPaymentAllocationTest extends BaseL
 
         return Long.valueOf(fundId);
     }
-
-    private AdvancedPaymentData createDefaultPaymentAllocation(String 
futureInstallmentAllocationRule) {
-        AdvancedPaymentData advancedPaymentData = new AdvancedPaymentData();
-        advancedPaymentData.setTransactionType("DEFAULT");
-        
advancedPaymentData.setFutureInstallmentAllocationRule(futureInstallmentAllocationRule);
-
-        List<PaymentAllocationOrder> paymentAllocationOrders = 
getPaymentAllocationOrder(PaymentAllocationType.PAST_DUE_PENALTY,
-                PaymentAllocationType.PAST_DUE_FEE, 
PaymentAllocationType.PAST_DUE_PRINCIPAL, 
PaymentAllocationType.PAST_DUE_INTEREST,
-                PaymentAllocationType.DUE_PENALTY, 
PaymentAllocationType.DUE_FEE, PaymentAllocationType.DUE_PRINCIPAL,
-                PaymentAllocationType.DUE_INTEREST, 
PaymentAllocationType.IN_ADVANCE_PENALTY, PaymentAllocationType.IN_ADVANCE_FEE,
-                PaymentAllocationType.IN_ADVANCE_PRINCIPAL, 
PaymentAllocationType.IN_ADVANCE_INTEREST);
-
-        advancedPaymentData.setPaymentAllocationOrder(paymentAllocationOrders);
-        return advancedPaymentData;
-    }
-
-    private List<PaymentAllocationOrder> 
getPaymentAllocationOrder(PaymentAllocationType... paymentAllocationTypes) {
-        AtomicInteger integer = new AtomicInteger(1);
-        return Arrays.stream(paymentAllocationTypes).map(pat -> {
-            PaymentAllocationOrder paymentAllocationOrder = new 
PaymentAllocationOrder();
-            paymentAllocationOrder.setPaymentAllocationRule(pat.name());
-            paymentAllocationOrder.setOrder(integer.getAndIncrement());
-            return paymentAllocationOrder;
-        }).toList();
-    }
 }
diff --git 
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanRescheduleRequestTest.java
 
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanRescheduleRequestTest.java
index 361b6b745..0f3b14c58 100644
--- 
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanRescheduleRequestTest.java
+++ 
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanRescheduleRequestTest.java
@@ -19,6 +19,7 @@
 package org.apache.fineract.integrationtests;
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
 import static org.junit.jupiter.api.Assertions.assertTrue;
 
 import io.restassured.builder.RequestSpecBuilder;
@@ -31,19 +32,31 @@ import java.math.BigDecimal;
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
+import java.util.concurrent.atomic.AtomicReference;
+import org.apache.fineract.client.models.AdvancedPaymentData;
+import org.apache.fineract.client.models.PostClientsResponse;
+import org.apache.fineract.client.models.PostCreateRescheduleLoansRequest;
+import org.apache.fineract.client.models.PostCreateRescheduleLoansResponse;
+import org.apache.fineract.client.models.PostLoansLoanIdRequest;
+import org.apache.fineract.client.models.PostLoansRequest;
+import org.apache.fineract.client.models.PostLoansResponse;
+import org.apache.fineract.client.models.PostUpdateRescheduleLoansRequest;
+import org.apache.fineract.client.util.CallFailedRuntimeException;
 import org.apache.fineract.integrationtests.common.ClientHelper;
 import org.apache.fineract.integrationtests.common.CollateralManagementHelper;
 import org.apache.fineract.integrationtests.common.LoanRescheduleRequestHelper;
 import org.apache.fineract.integrationtests.common.Utils;
+import org.apache.fineract.integrationtests.common.accounting.Account;
 import 
org.apache.fineract.integrationtests.common.loans.LoanApplicationTestBuilder;
 import 
org.apache.fineract.integrationtests.common.loans.LoanProductTestBuilder;
 import 
org.apache.fineract.integrationtests.common.loans.LoanRescheduleRequestTestBuilder;
-import 
org.apache.fineract.integrationtests.common.loans.LoanTestLifecycleExtension;
 import org.apache.fineract.integrationtests.common.loans.LoanTransactionHelper;
+import 
org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.impl.AdvancedPaymentScheduleTransactionProcessor;
+import 
org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleProcessingType;
+import 
org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleType;
 import org.junit.jupiter.api.Assertions;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
-import org.junit.jupiter.api.extension.ExtendWith;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -51,8 +64,7 @@ import org.slf4j.LoggerFactory;
  * Test the creation, approval and rejection of a loan reschedule request
  **/
 @SuppressWarnings({ "rawtypes" })
-@ExtendWith(LoanTestLifecycleExtension.class)
-public class LoanRescheduleRequestTest {
+public class LoanRescheduleRequestTest extends BaseLoanIntegrationTest {
 
     private static final Logger LOG = 
LoggerFactory.getLogger(LoanRescheduleRequestTest.class);
     private ResponseSpecification responseSpec;
@@ -235,6 +247,99 @@ public class LoanRescheduleRequestTest {
         LOG.info("Successfully approved loan reschedule request (ID: {})", 
this.loanRescheduleRequestId);
     }
 
+    @Test
+    public void testInterestRateChangeForProgressiveLoan() {
+        PostClientsResponse client = 
clientHelper.createClient(ClientHelper.defaultClientCreationRequest());
+        final Account assetAccount = accountHelper.createAssetAccount();
+        final Account incomeAccount = accountHelper.createIncomeAccount();
+        final Account expenseAccount = accountHelper.createExpenseAccount();
+        final Account overpaymentAccount = 
accountHelper.createLiabilityAccount();
+
+        Integer commonLoanProductId = createLoanProduct("500", "15", "4", 
true, "25", true, LoanScheduleType.PROGRESSIVE,
+                LoanScheduleProcessingType.HORIZONTAL, assetAccount, 
incomeAccount, expenseAccount, overpaymentAccount);
+        AtomicReference<PostCreateRescheduleLoansResponse> rescheduleResponse 
= new AtomicReference<>();
+        AtomicReference<PostLoansResponse> loanResponse = new 
AtomicReference<>();
+        // Do not allow interest rate change on not active loan
+        // Do not allow interest rate change twice on the same day
+        runAt("15 February 2023", () -> {
+
+            loanResponse.set(applyForLoanApplication(client.getClientId(), 
commonLoanProductId, BigDecimal.valueOf(500.0), 45, 15, 3,
+                    BigDecimal.ZERO, "01 January 2023", "01 January 2023"));
+
+            loanTransactionHelper.approveLoan(loanResponse.get().getLoanId(),
+                    new 
PostLoansLoanIdRequest().approvedLoanAmount(BigDecimal.valueOf(500)).dateFormat(DATETIME_PATTERN)
+                            .approvedOnDate("01 January 2023").locale("en"));
+
+            CallFailedRuntimeException exception = 
assertThrows(CallFailedRuntimeException.class,
+                    () -> loanRescheduleRequestHelper
+                            .createLoanRescheduleRequest(new 
PostCreateRescheduleLoansRequest().loanId(loanResponse.get().getLoanId())
+                                    
.dateFormat(DATETIME_PATTERN).locale("en").submittedOnDate("15 February 2023")
+                                    
.newInterestRate(BigDecimal.ONE).rescheduleReasonId(1L).rescheduleFromDate("16 
February 2023")));
+            assertEquals(400, exception.getResponse().code());
+            assertTrue(exception.getMessage().contains("loan.is.not.active"));
+
+            loanTransactionHelper.disburseLoan(loanResponse.get().getLoanId(),
+                    new PostLoansLoanIdRequest().actualDisbursementDate("15 
February 2023").dateFormat(DATETIME_PATTERN)
+                            
.transactionAmount(BigDecimal.valueOf(500.00)).locale("en"));
+
+            
rescheduleResponse.set(loanRescheduleRequestHelper.createLoanRescheduleRequest(new
 PostCreateRescheduleLoansRequest()
+                    
.loanId(loanResponse.get().getLoanId()).dateFormat(DATETIME_PATTERN).locale("en").submittedOnDate("15
 February 2023")
+                    
.newInterestRate(BigDecimal.ONE).rescheduleReasonId(1L).rescheduleFromDate("16 
February 2023")));
+
+            exception = assertThrows(CallFailedRuntimeException.class,
+                    () -> loanRescheduleRequestHelper
+                            .createLoanRescheduleRequest(new 
PostCreateRescheduleLoansRequest().loanId(loanResponse.get().getLoanId())
+                                    
.dateFormat(DATETIME_PATTERN).locale("en").submittedOnDate("15 February 2023")
+                                    
.newInterestRate(BigDecimal.ONE).rescheduleReasonId(1L).rescheduleFromDate("16 
February 2023")));
+            assertEquals(403, exception.getResponse().code());
+            
assertTrue(exception.getMessage().contains("loan.reschedule.interest.rate.change.already.exists"));
+        });
+        // Do not allow approve an interest rate change if the reschedule from 
date is not in the future
+        // Do not allow create interest rate change if a previous interest 
rate change got already approved for that
+        // date
+        runAt("16 February 2023", () -> {
+            CallFailedRuntimeException exception = 
assertThrows(CallFailedRuntimeException.class,
+                    () -> 
loanRescheduleRequestHelper.approveLoanRescheduleRequest(rescheduleResponse.get().getResourceId(),
+                            new 
PostUpdateRescheduleLoansRequest().approvedOnDate("16 February 
2024").locale("en")
+                                    .dateFormat(DATETIME_PATTERN)));
+            assertEquals(403, exception.getResponse().code());
+            
assertTrue(exception.getMessage().contains("loan.reschedule.interest.rate.change.reschedule.from.date.should.be.in.future"));
+
+            PostCreateRescheduleLoansResponse rescheduleLoansResponse = 
loanRescheduleRequestHelper
+                    .createLoanRescheduleRequest(new 
PostCreateRescheduleLoansRequest().loanId(loanResponse.get().getLoanId())
+                            
.dateFormat(DATETIME_PATTERN).locale("en").submittedOnDate("17 February 
2023").newInterestRate(BigDecimal.ONE)
+                            .rescheduleReasonId(1L).rescheduleFromDate("17 
February 2023"));
+
+            
loanRescheduleRequestHelper.approveLoanRescheduleRequest(rescheduleLoansResponse.getResourceId(),
+                    new PostUpdateRescheduleLoansRequest().approvedOnDate("17 
February 2024").locale("en").dateFormat(DATETIME_PATTERN));
+
+            exception = assertThrows(CallFailedRuntimeException.class,
+                    () -> loanRescheduleRequestHelper
+                            .createLoanRescheduleRequest(new 
PostCreateRescheduleLoansRequest().loanId(rescheduleLoansResponse.getLoanId())
+                                    
.dateFormat(DATETIME_PATTERN).locale("en").submittedOnDate("17 February 2023")
+                                    
.newInterestRate(BigDecimal.ONE).rescheduleReasonId(1L).rescheduleFromDate("17 
February 2023")));
+            assertEquals(403, exception.getResponse().code());
+            
assertTrue(exception.getMessage().contains("loan.reschedule.interest.rate.change.already.exists"));
+
+        });
+
+        // Allow new interest rate change if the previous got rejected
+        runAt("17 February 2023", () -> {
+            PostCreateRescheduleLoansResponse rescheduleLoansResponse = 
loanRescheduleRequestHelper
+                    .createLoanRescheduleRequest(new 
PostCreateRescheduleLoansRequest().loanId(loanResponse.get().getLoanId())
+                            
.dateFormat(DATETIME_PATTERN).locale("en").submittedOnDate("18 February 
2023").newInterestRate(BigDecimal.ONE)
+                            .rescheduleReasonId(1L).rescheduleFromDate("18 
February 2023"));
+
+            
loanRescheduleRequestHelper.rejectLoanRescheduleRequest(rescheduleLoansResponse.getResourceId(),
+                    new PostUpdateRescheduleLoansRequest().rejectedOnDate("18 
February 2024").locale("en").dateFormat(DATETIME_PATTERN));
+
+            loanRescheduleRequestHelper.createLoanRescheduleRequest(new 
PostCreateRescheduleLoansRequest()
+                    
.loanId(loanResponse.get().getLoanId()).dateFormat(DATETIME_PATTERN).locale("en").submittedOnDate("18
 February 2023")
+                    
.newInterestRate(BigDecimal.ONE).rescheduleReasonId(1L).rescheduleFromDate("18 
February 2023"));
+
+        });
+    }
+
     /**
      * create new loan reschedule request
      **/
@@ -255,4 +360,53 @@ public class LoanRescheduleRequestTest {
     public void testCreateLoanRescheduleChangeEMIRequest() {
         this.createLoanRescheduleChangeEMIRequest();
     }
+
+    private PostLoansResponse applyForLoanApplication(final Long clientId, 
final Integer loanProductId, final BigDecimal principal,
+            final int loanTermFrequency, final int repaymentAfterEvery, final 
int numberOfRepayments, final BigDecimal interestRate,
+            final String expectedDisbursementDate, final String 
submittedOnDate, String transactionProcessorCode,
+            String loanScheduleProcessingType) {
+        LOG.info("--------------------------------APPLYING FOR LOAN 
APPLICATION--------------------------------");
+        return loanTransactionHelper.applyLoan(new 
PostLoansRequest().clientId(clientId).productId(loanProductId.longValue())
+                
.expectedDisbursementDate(expectedDisbursementDate).dateFormat(DATETIME_PATTERN)
+                
.transactionProcessingStrategyCode(transactionProcessorCode).locale("en").submittedOnDate(submittedOnDate)
+                
.amortizationType(1).interestRatePerPeriod(interestRate).interestCalculationPeriodType(1).interestType(0)
+                
.repaymentFrequencyType(0).repaymentEvery(repaymentAfterEvery).repaymentFrequencyType(0)
+                
.numberOfRepayments(numberOfRepayments).loanTermFrequency(loanTermFrequency).loanTermFrequencyType(0).principal(principal)
+                
.loanType("individual").loanScheduleProcessingType(loanScheduleProcessingType)
+                .maxOutstandingLoanBalance(BigDecimal.valueOf(35000)));
+    }
+
+    private PostLoansResponse applyForLoanApplication(final Long clientId, 
final Integer loanProductId, final BigDecimal principal,
+            final int loanTermFrequency, final int repaymentAfterEvery, final 
int numberOfRepayments, final BigDecimal interestRate,
+            final String expectedDisbursementDate, final String 
submittedOnDate) {
+        return applyForLoanApplication(clientId, loanProductId, principal, 
loanTermFrequency, repaymentAfterEvery, numberOfRepayments,
+                interestRate, expectedDisbursementDate, submittedOnDate, 
LoanScheduleProcessingType.HORIZONTAL);
+    }
+
+    private PostLoansResponse applyForLoanApplication(final Long clientId, 
final Integer loanProductId, final BigDecimal principal,
+            final int loanTermFrequency, final int repaymentAfterEvery, final 
int numberOfRepayments, final BigDecimal interestRate,
+            final String expectedDisbursementDate, final String 
submittedOnDate, LoanScheduleProcessingType loanScheduleProcessingType) {
+        LOG.info("--------------------------------APPLYING FOR LOAN 
APPLICATION--------------------------------");
+        return applyForLoanApplication(clientId, loanProductId, principal, 
loanTermFrequency, repaymentAfterEvery, numberOfRepayments,
+                interestRate, expectedDisbursementDate, submittedOnDate,
+                
AdvancedPaymentScheduleTransactionProcessor.ADVANCED_PAYMENT_ALLOCATION_STRATEGY,
 loanScheduleProcessingType.name());
+    }
+
+    private Integer createLoanProduct(final String principal, final String 
repaymentAfterEvery, final String numberOfRepayments,
+            boolean downPaymentEnabled, String downPaymentPercentage, boolean 
autoPayForDownPayment, LoanScheduleType loanScheduleType,
+            LoanScheduleProcessingType loanScheduleProcessingType, final 
Account... accounts) {
+        AdvancedPaymentData defaultAllocation = 
createDefaultPaymentAllocation("NEXT_INSTALLMENT");
+        LOG.info("------------------------------CREATING NEW LOAN PRODUCT 
---------------------------------------");
+        final String loanProductJSON = new 
LoanProductTestBuilder().withMinPrincipal(principal).withPrincipal(principal)
+                
.withRepaymentTypeAsDays().withRepaymentAfterEvery(repaymentAfterEvery).withNumberOfRepayments(numberOfRepayments)
+                .withEnableDownPayment(downPaymentEnabled, 
downPaymentPercentage, autoPayForDownPayment).withinterestRatePerPeriod("0")
+                .withInterestRateFrequencyTypeAsMonths()
+                
.withRepaymentStrategy(AdvancedPaymentScheduleTransactionProcessor.ADVANCED_PAYMENT_ALLOCATION_STRATEGY)
+                
.withAmortizationTypeAsEqualPrincipalPayment().withInterestTypeAsFlat().withAccountingRulePeriodicAccrual(accounts)
+                
.addAdvancedPaymentAllocation(defaultAllocation).withInterestCalculationPeriodTypeAsRepaymentPeriod(true)
+                
.withInterestTypeAsDecliningBalance().withMultiDisburse().withDisallowExpectedDisbursements(true)
+                
.withLoanScheduleType(loanScheduleType).withLoanScheduleProcessingType(loanScheduleProcessingType).withDaysInMonth("30")
+                .withDaysInYear("365").withMoratorium("0", "0").build(null);
+        return loanTransactionHelper.getLoanProductId(loanProductJSON);
+    }
 }
diff --git 
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanTransactionAccrualActivityPostingTest.java
 
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanTransactionAccrualActivityPostingTest.java
index f9b15df13..2c593c0c5 100644
--- 
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanTransactionAccrualActivityPostingTest.java
+++ 
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanTransactionAccrualActivityPostingTest.java
@@ -939,7 +939,7 @@ public class LoanTransactionAccrualActivityPostingTest 
extends BaseLoanIntegrati
         AtomicReference<Long> loanId = new AtomicReference<>();
         runAt(creationBusinessDay, () -> {
             Long localLoanProductId = loanTransactionHelper
-                    
.createLoanProduct(loanProductsRequestPin30InterestDecliningBalanceDailyRecalculationCompoundingNoneAccrualActivity())
+                    
.createLoanProduct(loanProductsRequestInterestDecliningBalanceDailyRecalculationCompoundingNoneAccrualActivity())
                     .getResourceId();
             loanId.set(applyForLoanApplication(client.getClientId(), 
localLoanProductId, BigDecimal.valueOf(1000), disbursementDay));
             loanTransactionHelper.approveLoan(loanId.get(), new 
PostLoansLoanIdRequest().approvedLoanAmount(BigDecimal.valueOf(1000))
@@ -964,7 +964,7 @@ public class LoanTransactionAccrualActivityPostingTest 
extends BaseLoanIntegrati
         });
     }
 
-    private PostLoanProductsRequest 
loanProductsRequestPin30InterestDecliningBalanceDailyRecalculationCompoundingNoneAccrualActivity()
 {
+    private PostLoanProductsRequest 
loanProductsRequestInterestDecliningBalanceDailyRecalculationCompoundingNoneAccrualActivity()
 {
         String name = Utils.uniqueRandomStringGenerator("LOAN_PRODUCT_", 6);
         String shortName = Utils.uniqueRandomStringGenerator("", 4);
 
diff --git 
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/LoanRescheduleRequestHelper.java
 
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/LoanRescheduleRequestHelper.java
index 901e52dfe..432a99301 100644
--- 
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/LoanRescheduleRequestHelper.java
+++ 
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/LoanRescheduleRequestHelper.java
@@ -83,4 +83,8 @@ public class LoanRescheduleRequestHelper extends 
IntegrationTest {
     public PostUpdateRescheduleLoansResponse approveLoanRescheduleRequest(Long 
scheduleId, PostUpdateRescheduleLoansRequest request) {
         return 
ok(fineract().rescheduleLoans.updateLoanRescheduleRequest(scheduleId, request, 
"approve"));
     }
+
+    public PostUpdateRescheduleLoansResponse rejectLoanRescheduleRequest(Long 
scheduleId, PostUpdateRescheduleLoansRequest request) {
+        return 
ok(fineract().rescheduleLoans.updateLoanRescheduleRequest(scheduleId, request, 
"reject"));
+    }
 }

Reply via email to