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 4cb849fa1 FINERACT-1981: Introduce Interest should not be calculated 
on past due principal amount
4cb849fa1 is described below

commit 4cb849fa17a52356d690c04e7dcd5e4b63e07cfc
Author: Janos Meszaros <[email protected]>
AuthorDate: Wed Nov 27 17:52:40 2024 +0100

    FINERACT-1981: Introduce Interest should not be calculated on past due 
principal amount
---
 .../loan/v1/LoanInterestRecalculationDataV1.avsc   |   8 ++
 .../v1/LoanProductInterestRecalculationDataV1.avsc |   8 ++
 .../data/LoanInterestRecalculationData.java        |   4 +-
 .../domain/LoanInterestRecalculationDetails.java   |  13 +-
 .../loanproduct/LoanProductConstants.java          |   1 +
 .../api/LoanProductsApiResourceSwagger.java        |   4 +
 .../loanproduct/data/LoanProductData.java          |   7 +-
 .../data/LoanProductInterestRecalculationData.java |  14 +-
 .../LoanProductInterestRecalculationDetails.java   |  23 ++-
 ...dvancedPaymentScheduleTransactionProcessor.java |   4 +
 .../LoanInterestRecalculationCOBBusinessStep.java  |   7 +-
 .../service/LoanReadPlatformServiceImpl.java       | 156 +++++++++++----------
 .../serialization/LoanProductDataValidator.java    |   9 +-
 .../LoanProductReadPlatformServiceImpl.java        |   8 +-
 .../db/changelog/tenant/changelog-tenant.xml       |   1 +
 ...dd_disallow_interest_calc_on_past_due_field.xml |  39 ++++++
 .../LoanInterestRecalculationCOBTest.java          |  66 ++++++++-
 17 files changed, 287 insertions(+), 85 deletions(-)

diff --git 
a/fineract-avro-schemas/src/main/avro/loan/v1/LoanInterestRecalculationDataV1.avsc
 
b/fineract-avro-schemas/src/main/avro/loan/v1/LoanInterestRecalculationDataV1.avsc
index c799b4240..29e789ced 100644
--- 
a/fineract-avro-schemas/src/main/avro/loan/v1/LoanInterestRecalculationDataV1.avsc
+++ 
b/fineract-avro-schemas/src/main/avro/loan/v1/LoanInterestRecalculationDataV1.avsc
@@ -146,6 +146,14 @@
                 "null",
                 "boolean"
             ]
+        },
+        {
+            "default": null,
+            "name": "disallowInterestCalculationOnPastDue",
+            "type": [
+                "null",
+                "boolean"
+            ]
         }
     ]
 }
diff --git 
a/fineract-avro-schemas/src/main/avro/loan/v1/LoanProductInterestRecalculationDataV1.avsc
 
b/fineract-avro-schemas/src/main/avro/loan/v1/LoanProductInterestRecalculationDataV1.avsc
index 483116378..fffb4dce8 100644
--- 
a/fineract-avro-schemas/src/main/avro/loan/v1/LoanProductInterestRecalculationDataV1.avsc
+++ 
b/fineract-avro-schemas/src/main/avro/loan/v1/LoanProductInterestRecalculationDataV1.avsc
@@ -146,6 +146,14 @@
                 "null",
                 "boolean"
             ]
+        },
+        {
+            "default": null,
+            "name": "disallowInterestCalculationOnPastDue",
+            "type": [
+                "null",
+                "boolean"
+            ]
         }
     ]
 }
diff --git 
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanInterestRecalculationData.java
 
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanInterestRecalculationData.java
index 6c5c6aa41..60b08374c 100644
--- 
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanInterestRecalculationData.java
+++ 
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanInterestRecalculationData.java
@@ -47,6 +47,7 @@ public class LoanInterestRecalculationData {
     private Boolean isCompoundingToBePostedAsTransaction;
     private CalendarData compoundingCalendarData;
     private Boolean allowCompoundingOnEod;
+    private Boolean disallowInterestCalculationOnPastDue;
 
     public LoanInterestRecalculationData(final Long id, final Long loanId, 
final EnumOptionData interestRecalculationCompoundingType,
             final EnumOptionData rescheduleStrategyType, final CalendarData 
calendarData,
@@ -56,7 +57,7 @@ public class LoanInterestRecalculationData {
             final EnumOptionData recalculationCompoundingFrequencyType, final 
Integer recalculationCompoundingFrequencyInterval,
             final EnumOptionData recalculationCompoundingFrequencyNthDay, 
final EnumOptionData recalculationCompoundingFrequencyWeekday,
             final Integer recalculationCompoundingFrequencyOnDay, final 
Boolean isCompoundingToBePostedAsTransaction,
-            final Boolean allowCompoundingOnEod) {
+            final Boolean allowCompoundingOnEod, final Boolean 
disallowInterestCalculationOnPastDue) {
         this.id = id;
         this.loanId = loanId;
         this.interestRecalculationCompoundingType = 
interestRecalculationCompoundingType;
@@ -75,6 +76,7 @@ public class LoanInterestRecalculationData {
         this.compoundingCalendarData = compoundingCalendarData;
         this.isCompoundingToBePostedAsTransaction = 
isCompoundingToBePostedAsTransaction;
         this.allowCompoundingOnEod = allowCompoundingOnEod;
+        this.disallowInterestCalculationOnPastDue = 
disallowInterestCalculationOnPastDue;
     }
 
     public LoanInterestRecalculationData withCalendarData(final CalendarData 
calendarData, CalendarData compoundingCalendarData) {
diff --git 
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanInterestRecalculationDetails.java
 
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanInterestRecalculationDetails.java
index 5da30a483..454e524e0 100644
--- 
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanInterestRecalculationDetails.java
+++ 
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanInterestRecalculationDetails.java
@@ -88,6 +88,9 @@ public class LoanInterestRecalculationDetails extends 
AbstractPersistableCustom<
     @Column(name = "allow_compounding_on_eod")
     private Boolean allowCompoundingOnEod;
 
+    @Column(name = "disallow_interest_calc_on_past_due")
+    private Boolean disallowInterestCalculationOnPastDue;
+
     protected LoanInterestRecalculationDetails() {
         // Default constructor for jpa repository
     }
@@ -96,7 +99,7 @@ public class LoanInterestRecalculationDetails extends 
AbstractPersistableCustom<
             final Integer restFrequencyType, final Integer restInterval, final 
Integer restFrequencyNthDay, Integer restFrequencyWeekday,
             Integer restFrequencyOnDay, Integer compoundingFrequencyType, 
Integer compoundingInterval, Integer compoundingFrequencyNthDay,
             Integer compoundingFrequencyWeekday, Integer 
compoundingFrequencyOnDay, final boolean isCompoundingToBePostedAsTransaction,
-            final boolean allowCompoundingOnEod) {
+            final boolean allowCompoundingOnEod, final boolean 
disallowInterestCalculationOnPastDue) {
         this.interestRecalculationCompoundingMethod = 
interestRecalculationCompoundingMethod;
         this.rescheduleStrategyMethod = rescheduleStrategyMethod;
         this.restFrequencyNthDay = restFrequencyNthDay;
@@ -111,6 +114,7 @@ public class LoanInterestRecalculationDetails extends 
AbstractPersistableCustom<
         this.compoundingInterval = compoundingInterval;
         this.isCompoundingToBePostedAsTransaction = 
isCompoundingToBePostedAsTransaction;
         this.allowCompoundingOnEod = allowCompoundingOnEod;
+        this.disallowInterestCalculationOnPastDue = 
disallowInterestCalculationOnPastDue;
     }
 
     public static LoanInterestRecalculationDetails createFrom(
@@ -127,7 +131,8 @@ public class LoanInterestRecalculationDetails extends 
AbstractPersistableCustom<
                 
loanProductInterestRecalculationDetails.getCompoundingFrequencyWeekday(),
                 
loanProductInterestRecalculationDetails.getCompoundingFrequencyOnDay(),
                 
loanProductInterestRecalculationDetails.getIsCompoundingToBePostedAsTransaction(),
-                
loanProductInterestRecalculationDetails.allowCompoundingOnEod());
+                
loanProductInterestRecalculationDetails.allowCompoundingOnEod(),
+                
loanProductInterestRecalculationDetails.disallowInterestCalculationOnPastDue());
     }
 
     public void updateLoan(final Loan loan) {
@@ -189,4 +194,8 @@ public class LoanInterestRecalculationDetails extends 
AbstractPersistableCustom<
     public boolean allowCompoundingOnEod() {
         return this.allowCompoundingOnEod;
     }
+
+    public Boolean disallowInterestCalculationOnPastDue() {
+        return disallowInterestCalculationOnPastDue;
+    }
 }
diff --git 
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/LoanProductConstants.java
 
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/LoanProductConstants.java
index 55ab6a1ff..834f131c6 100644
--- 
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/LoanProductConstants.java
+++ 
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/LoanProductConstants.java
@@ -97,6 +97,7 @@ public interface LoanProductConstants {
     String recalculationCompoundingFrequencyNthDayParamName = 
"recalculationCompoundingFrequencyNthDayType";
     String recalculationCompoundingFrequencyOnDayParamName = 
"recalculationCompoundingFrequencyOnDayType";
     String isCompoundingToBePostedAsTransactionParamName = 
"isCompoundingToBePostedAsTransaction";
+    String disallowInterestCalculationOnPastDueParamName = 
"disallowInterestCalculationOnPastDue";
 
     // Guarantee related
     String holdGuaranteeFundsParamName = "holdGuaranteeFunds";
diff --git 
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/api/LoanProductsApiResourceSwagger.java
 
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/api/LoanProductsApiResourceSwagger.java
index a97d1d9c9..bc13d8629 100644
--- 
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/api/LoanProductsApiResourceSwagger.java
+++ 
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/api/LoanProductsApiResourceSwagger.java
@@ -198,6 +198,8 @@ final class LoanProductsApiResourceSwagger {
         public Boolean isCompoundingToBePostedAsTransaction;
         @Schema(example = "false")
         public Boolean allowCompoundingOnEod;
+        @Schema(example = "false")
+        public Boolean disallowInterestCalculationOnPastDue;
 
         // Accounting
         @Schema(example = "3")
@@ -520,6 +522,8 @@ final class LoanProductsApiResourceSwagger {
             public GetLoanProductsPreClosureInterestCalculationStrategy 
preClosureInterestCalculationStrategy;
             @Schema(example = "true")
             public Boolean isArrearsBasedOnOriginalSchedule;
+            @Schema(example = "false")
+            public Boolean disallowInterestCalculationOnPastDue;
             @Schema(example = "true")
             public Boolean isCompoundingToBePostedAsTransaction;
             @Schema(example = "true")
diff --git 
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/data/LoanProductData.java
 
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/data/LoanProductData.java
index 5ffb54479..3a1d14bed 100644
--- 
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/data/LoanProductData.java
+++ 
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/data/LoanProductData.java
@@ -1182,7 +1182,7 @@ public class LoanProductData implements Serializable {
                 getInterestRecalculationRestOnDayType(), 
compoundingCalendarData, getRecalculationCompoundingFrequencyType(),
                 getRecalculationCompoundingFrequencyInterval(), 
getInterestRecalculationCompoundingNthDayType(),
                 getInterestRecalculationCompoundingWeekDayType(), 
getInterestRecalculationCompoundingOnDayType(),
-                isCompoundingToBePostedAsTransaction(), 
allowCompoundingOnEod());
+                isCompoundingToBePostedAsTransaction(), 
allowCompoundingOnEod(), disallowInterestCalculationOnPastDue());
     }
 
     private EnumOptionData getRescheduleStrategyType() {
@@ -1279,6 +1279,11 @@ public class LoanProductData implements Serializable {
         return isInterestRecalculationEnabled() ? 
this.interestRecalculationData.isAllowCompoundingOnEod() : null;
     }
 
+    @SuppressFBWarnings("NP_BOOLEAN_RETURN_NULL")
+    public Boolean disallowInterestCalculationOnPastDue() {
+        return isInterestRecalculationEnabled() ? 
this.interestRecalculationData.disallowInterestCalculationOnPastDue() : null;
+    }
+
     public void 
setLoanProductConfigurableAttributes(LoanProductConfigurableAttributes 
loanProductConfigurableAttributes) {
         this.allowAttributeOverrides = loanProductConfigurableAttributes;
     }
diff --git 
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/data/LoanProductInterestRecalculationData.java
 
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/data/LoanProductInterestRecalculationData.java
index 8c6fe4110..9820a6316 100644
--- 
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/data/LoanProductInterestRecalculationData.java
+++ 
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/data/LoanProductInterestRecalculationData.java
@@ -50,6 +50,7 @@ public class LoanProductInterestRecalculationData implements 
Serializable {
     private final boolean isCompoundingToBePostedAsTransaction;
     private final EnumOptionData preClosureInterestCalculationStrategy;
     private final boolean allowCompoundingOnEod;
+    private final Boolean disallowInterestCalculationOnPastDue;
 
     public LoanProductInterestRecalculationData(final Long id, final Long 
productId,
             final EnumOptionData interestRecalculationCompoundingType, final 
EnumOptionData rescheduleStrategyType,
@@ -59,7 +60,8 @@ public class LoanProductInterestRecalculationData implements 
Serializable {
             final Integer recalculationCompoundingFrequencyInterval, final 
EnumOptionData recalculationCompoundingFrequencyNthDay,
             final EnumOptionData recalculationCompoundingFrequencyWeekday, 
final Integer recalculationCompoundingFrequencyOnDay,
             final boolean isArrearsBasedOnOriginalSchedule, boolean 
isCompoundingToBePostedAsTransaction,
-            final EnumOptionData preCloseInterestCalculationStrategy, final 
boolean allowCompoundingOnEod) {
+            final EnumOptionData preCloseInterestCalculationStrategy, final 
boolean allowCompoundingOnEod,
+            final Boolean disallowInterestCalculationOnPastDue) {
         this.id = id;
         this.productId = productId;
         this.interestRecalculationCompoundingType = 
interestRecalculationCompoundingType;
@@ -78,6 +80,7 @@ public class LoanProductInterestRecalculationData implements 
Serializable {
         this.preClosureInterestCalculationStrategy = 
preCloseInterestCalculationStrategy;
         this.isCompoundingToBePostedAsTransaction = 
isCompoundingToBePostedAsTransaction;
         this.allowCompoundingOnEod = allowCompoundingOnEod;
+        this.disallowInterestCalculationOnPastDue = 
disallowInterestCalculationOnPastDue;
     }
 
     public static LoanProductInterestRecalculationData 
sensibleDefaultsForNewLoanProductCreation() {
@@ -101,12 +104,15 @@ public class LoanProductInterestRecalculationData 
implements Serializable {
         final EnumOptionData preCloseInterestCalculationStrategy = 
preCloseInterestCalculationStrategy(
                 
LoanPreClosureInterestCalculationStrategy.TILL_PRE_CLOSURE_DATE);
         final boolean allowCompoundingOnEod = false;
+        final boolean disallowInterestCalculationOnPastDue = false;
+
         return new LoanProductInterestRecalculationData(id, productId, 
interestRecalculationCompoundingType, rescheduleStrategyType,
                 recalculationRestFrequencyType, 
recalculationRestFrequencyInterval, recalculationRestFrequencyNthDay,
                 recalculationRestFrequencyWeekday, 
recalculationRestFrequencyOnDay, recalculationCompoundingFrequencyType,
                 recalculationCompoundingFrequencyInterval, 
recalculationCompoundingFrequencyNthDay,
                 recalculationCompoundingFrequencyWeekday, 
recalculationCompoundingFrequencyOnDay, isArrearsBasedOnOriginalSchedule,
-                isCompoundingToBePostedAsTransaction, 
preCloseInterestCalculationStrategy, allowCompoundingOnEod);
+                isCompoundingToBePostedAsTransaction, 
preCloseInterestCalculationStrategy, allowCompoundingOnEod,
+                disallowInterestCalculationOnPastDue);
     }
 
     public boolean isArrearsBasedOnOriginalSchedule() {
@@ -124,4 +130,8 @@ public class LoanProductInterestRecalculationData 
implements Serializable {
     public boolean isIsCompoundingToBePostedAsTransaction() {
         return isCompoundingToBePostedAsTransaction;
     }
+
+    public Boolean disallowInterestCalculationOnPastDue() {
+        return disallowInterestCalculationOnPastDue;
+    }
 }
diff --git 
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/domain/LoanProductInterestRecalculationDetails.java
 
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/domain/LoanProductInterestRecalculationDetails.java
index b46c1bae5..ecca9c1dd 100644
--- 
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/domain/LoanProductInterestRecalculationDetails.java
+++ 
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/domain/LoanProductInterestRecalculationDetails.java
@@ -96,6 +96,9 @@ public class LoanProductInterestRecalculationDetails extends 
AbstractPersistable
     @Column(name = "allow_compounding_on_eod")
     private Boolean allowCompoundingOnEod;
 
+    @Column(name = "disallow_interest_calc_on_past_due")
+    private Boolean disallowInterestCalculationOnPastDue;
+
     protected LoanProductInterestRecalculationDetails() {
         //
     }
@@ -162,11 +165,14 @@ public class LoanProductInterestRecalculationDetails 
extends AbstractPersistable
         final boolean isCompoundingToBePostedAsTransaction = command
                 
.booleanPrimitiveValueOfParameterNamed(LoanProductConstants.isCompoundingToBePostedAsTransactionParamName);
 
+        final boolean disallowInterestCalculationOnPastDue = command
+                
.booleanPrimitiveValueOfParameterNamed(LoanProductConstants.disallowInterestCalculationOnPastDueParamName);
+
         return new 
LoanProductInterestRecalculationDetails(interestRecalculationCompoundingMethod, 
loanRescheduleStrategyMethod,
                 recurrenceFrequency, recurrenceInterval, recurrenceOnNthDay, 
recurrenceOnDay, recurrenceOnWeekday,
                 compoundingRecurrenceFrequency, compoundingInterval, 
compoundingRecurrenceOnNthDay, compoundingRecurrenceOnDay,
                 compoundingRecurrenceOnWeekday, 
isArrearsBasedOnOriginalSchedule, preCloseInterestCalculationStrategy,
-                isCompoundingToBePostedAsTransaction, allowCompoundingOnEod);
+                isCompoundingToBePostedAsTransaction, allowCompoundingOnEod, 
disallowInterestCalculationOnPastDue);
     }
 
     private LoanProductInterestRecalculationDetails(final Integer 
interestRecalculationCompoundingMethod,
@@ -175,7 +181,8 @@ public class LoanProductInterestRecalculationDetails 
extends AbstractPersistable
             Integer compoundingFrequencyType, Integer compoundingInterval, 
final Integer compoundingFrequencyNthDay,
             final Integer compoundingFrequencyOnDay, final Integer 
compoundingFrequencyWeekday,
             final boolean isArrearsBasedOnOriginalSchedule, final Integer 
preCloseInterestCalculationStrategy,
-            final boolean isCompoundingToBePostedAsTransaction, final boolean 
allowCompoundingOnEod) {
+            final boolean isCompoundingToBePostedAsTransaction, final boolean 
allowCompoundingOnEod,
+            final boolean disallowInterestCalculationOnPastDue) {
         this.interestRecalculationCompoundingMethod = 
interestRecalculationCompoundingMethod;
         this.rescheduleStrategyMethod = rescheduleStrategyMethod;
         this.restFrequencyType = restFrequencyType;
@@ -192,6 +199,7 @@ public class LoanProductInterestRecalculationDetails 
extends AbstractPersistable
         this.preClosureInterestCalculationStrategy = 
preCloseInterestCalculationStrategy;
         this.isCompoundingToBePostedAsTransaction = 
isCompoundingToBePostedAsTransaction;
         this.allowCompoundingOnEod = allowCompoundingOnEod;
+        this.disallowInterestCalculationOnPastDue = 
disallowInterestCalculationOnPastDue;
     }
 
     public void updateProduct(final LoanProduct loanProduct) {
@@ -406,6 +414,13 @@ public class LoanProductInterestRecalculationDetails 
extends AbstractPersistable
             this.isCompoundingToBePostedAsTransaction = newValue;
         }
 
+        if 
(command.isChangeInBooleanParameterNamed(LoanProductConstants.disallowInterestCalculationOnPastDueParamName,
+                this.disallowInterestCalculationOnPastDue)) {
+            final boolean newValue = command
+                    
.booleanPrimitiveValueOfParameterNamed(LoanProductConstants.disallowInterestCalculationOnPastDueParamName);
+            
actualChanges.put(LoanProductConstants.disallowInterestCalculationOnPastDueParamName,
 newValue);
+            this.disallowInterestCalculationOnPastDue = newValue;
+        }
     }
 
     public RecalculationFrequencyType getRestFrequencyType() {
@@ -463,4 +478,8 @@ public class LoanProductInterestRecalculationDetails 
extends AbstractPersistable
     public Boolean allowCompoundingOnEod() {
         return this.allowCompoundingOnEod;
     }
+
+    public Boolean disallowInterestCalculationOnPastDue() {
+        return disallowInterestCalculationOnPastDue;
+    }
 }
diff --git 
a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/AdvancedPaymentScheduleTransactionProcessor.java
 
b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/AdvancedPaymentScheduleTransactionProcessor.java
index dc35c20c3..8c180124c 100644
--- 
a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/AdvancedPaymentScheduleTransactionProcessor.java
+++ 
b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/AdvancedPaymentScheduleTransactionProcessor.java
@@ -982,6 +982,10 @@ public class AdvancedPaymentScheduleTransactionProcessor 
extends AbstractLoanRep
 
     private void adjustOverduePrincipalForInstallment(LocalDate currentDate, 
LoanRepaymentScheduleInstallment currentInstallment,
             Money overduePrincipal, Money aggregatedOverDuePrincipal, 
ProgressiveTransactionCtx ctx) {
+        if 
(currentInstallment.getLoan().getLoanInterestRecalculationDetails().disallowInterestCalculationOnPastDue())
 {
+            return;
+        }
+
         LocalDate fromDate = currentInstallment.getFromDate();
         LocalDate toDate = currentInstallment.getDueDate();
         boolean hasUpdate = false;
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/cob/loan/LoanInterestRecalculationCOBBusinessStep.java
 
b/fineract-provider/src/main/java/org/apache/fineract/cob/loan/LoanInterestRecalculationCOBBusinessStep.java
index cc0c401f3..e19e25f75 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/cob/loan/LoanInterestRecalculationCOBBusinessStep.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/cob/loan/LoanInterestRecalculationCOBBusinessStep.java
@@ -36,8 +36,11 @@ public class LoanInterestRecalculationCOBBusinessStep 
implements LoanCOBBusiness
     @Override
     public Loan execute(Loan loan) {
         if (!loan.isInterestBearing() || !loan.getStatus().isActive() || 
loan.isNpa() || loan.isChargedOff()
-                || !loan.isInterestRecalculationEnabledForProduct()) {
-            log.debug("Skip processing loan interest recalculation [{}] - 
reason: not interest bearing loan or not active.", loan.getId());
+                || !loan.isInterestRecalculationEnabledForProduct()
+                || 
loan.getLoanInterestRecalculationDetails().disallowInterestCalculationOnPastDue())
 {
+            log.debug(
+                    "Skip processing loan interest recalculation [{}] - 
Possible reasons: Loan is not an interest bearing loan, Loan is not active, 
Interest recalculation on past due is disabled on this loan",
+                    loan.getId());
             return loan;
         }
 
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java
 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java
index 652fe67f0..c88e42e56 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java
@@ -694,6 +694,7 @@ public class LoanReadPlatformServiceImpl implements 
LoanReadPlatformService, Loa
                     + " lir.compounding_frequency_on_day as 
compoundingFrequencyOnDay, "
                     + " lir.is_compounding_to_be_posted_as_transaction as 
isCompoundingToBePostedAsTransaction, "
                     + " lir.allow_compounding_on_eod as allowCompoundingOnEod, 
"
+                    + " lir.disallow_interest_calc_on_past_due as 
disallowInterestCalculationOnPastDue, "
                     + " l.is_floating_interest_rate as isFloatingInterestRate, 
"
                     + " l.interest_rate_differential as 
interestRateDifferential, "
                     + " l.create_standing_instruction_at_disbursement as 
createStandingInstructionAtDisbursement, "
@@ -1037,11 +1038,12 @@ public class LoanReadPlatformServiceImpl implements 
LoanReadPlatformService, Loa
 
                 final Boolean isCompoundingToBePostedAsTransaction = 
rs.getBoolean("isCompoundingToBePostedAsTransaction");
                 final Boolean allowCompoundingOnEod = 
rs.getBoolean("allowCompoundingOnEod");
+                final Boolean disallowInterestCalculationOnPastDue = 
rs.getBoolean("disallowInterestCalculationOnPastDue");
                 interestRecalculationData = new 
LoanInterestRecalculationData(lprId, productId, 
interestRecalculationCompoundingType,
                         rescheduleStrategyType, calendarData, 
restFrequencyType, restFrequencyInterval, restFrequencyNthDayEnum,
                         restFrequencyWeekDayEnum, restFrequencyOnDay, 
compoundingCalendarData, compoundingFrequencyType,
                         compoundingInterval, compoundingFrequencyNthDayEnum, 
compoundingFrequencyWeekDayEnum, compoundingFrequencyOnDay,
-                        isCompoundingToBePostedAsTransaction, 
allowCompoundingOnEod);
+                        isCompoundingToBePostedAsTransaction, 
allowCompoundingOnEod, disallowInterestCalculationOnPastDue);
             }
 
             final boolean canUseForTopup = rs.getBoolean("canUseForTopup");
@@ -2039,42 +2041,49 @@ public class LoanReadPlatformServiceImpl implements 
LoanReadPlatformService, Loa
 
     @Override
     public Collection<Long> fetchLoansForInterestRecalculation() {
-        StringBuilder sqlBuilder = new StringBuilder();
-        sqlBuilder.append("SELECT ml.id FROM m_loan ml ");
-        sqlBuilder.append(" INNER JOIN m_loan_repayment_schedule mr on 
mr.loan_id = ml.id ");
-        sqlBuilder.append(" LEFT JOIN m_loan_disbursement_detail dd on 
dd.loan_id=ml.id and dd.disbursedon_date is null ");
-        // For Floating rate changes
-        sqlBuilder.append(
-                " left join m_product_loan_floating_rates pfr on ml.product_id 
= pfr.loan_product_id and ml.is_floating_interest_rate = true");
-        sqlBuilder.append(" left join m_floating_rates fr on  
pfr.floating_rates_id = fr.id");
-        sqlBuilder.append(" left join m_floating_rates_periods frp on fr.id = 
frp.floating_rates_id ");
-        sqlBuilder.append(" left join m_loan_reschedule_request lrr on 
lrr.loan_id = ml.id");
-        // this is to identify the applicable rates when base rate is changed
-        sqlBuilder.append(" left join  m_floating_rates bfr on  
bfr.is_base_lending_rate = true");
-        sqlBuilder.append(" left join  m_floating_rates_periods bfrp on  
bfr.id = bfrp.floating_rates_id and bfrp.created_date >= ?");
-        sqlBuilder.append(" WHERE ml.loan_status_id = ? ");
-        sqlBuilder.append(" and ml.is_npa = false and ml.is_charged_off = 
false and dd.is_reversed = false ");
-        sqlBuilder.append(" and ((");
-        sqlBuilder.append("ml.interest_recalculation_enabled = true ");
-        sqlBuilder.append(" and (ml.interest_recalcualated_on is null or 
ml.interest_recalcualated_on <> ?)");
-        sqlBuilder.append(" and ((");
-        sqlBuilder.append(" mr.completed_derived is false ");
-        sqlBuilder.append(" and mr.duedate < ? )");
-        sqlBuilder.append(" or dd.expected_disburse_date < ? )) ");
-        sqlBuilder.append(" or (");
-        sqlBuilder.append(" fr.is_active = true and  frp.is_active = true");
-        sqlBuilder.append(" and (frp.created_date >= ?  or ");
-        sqlBuilder
-                .append("(bfrp.id is not null and 
frp.is_differential_to_base_lending_rate = true and frp.from_date >= 
bfrp.from_date)) ");
-        sqlBuilder.append("and lrr.loan_id is null");
-        sqlBuilder.append(" ))");
-        sqlBuilder.append(" group by ml.id");
+        final String sql = """
+                SELECT l.id
+                FROM m_loan l
+                INNER JOIN m_loan_repayment_schedule mr ON mr.loan_id = l.id
+                LEFT JOIN m_loan_disbursement_detail dd ON dd.loan_id=l.id AND 
dd.disbursedon_date IS NULL
+                -- for past due interest recalculation
+                LEFT JOIN m_loan_recalculation_details rcd ON rcd.loan_id = 
l.id
+                -- For Floating rate changes
+                LEFT JOIN m_product_loan_floating_rates pfr
+                    ON l.product_id = pfr.loan_product_id AND 
l.is_floating_interest_rate = TRUE
+                LEFT JOIN m_floating_rates fr ON pfr.floating_rates_id = fr.id
+                LEFT JOIN m_floating_rates_periods frp ON fr.id = 
frp.floating_rates_id
+                LEFT JOIN m_loan_reschedule_request lrr ON lrr.loan_id = l.id
+                -- this is to identify the applicable rates when base rate is 
changed
+                LEFT JOIN m_floating_rates bfr ON bfr.is_base_lending_rate = 
TRUE
+                LEFT JOIN m_floating_rates_periods bfrp ON bfr.id = 
bfrp.floating_rates_id AND bfrp.created_date >= ?
+                WHERE l.loan_status_id = ?
+                  AND l.is_npa = FALSE
+                  AND l.is_charged_off = FALSE
+                  AND dd.is_reversed = FALSE
+                  AND (
+                        (l.interest_recalculation_enabled = TRUE
+                            AND (l.interest_recalcualated_on IS NULL OR 
l.interest_recalcualated_on <> ?)
+                            AND ((mr.completed_derived IS FALSE AND mr.duedate 
< ?) OR dd.expected_disburse_date < ?)
+                            AND rcd.disallow_interest_calc_on_past_due = FALSE)
+                       OR
+                        -- float rate changes
+                        (fr.is_active = TRUE
+                            AND frp.is_active = TRUE
+                            AND (frp.created_date >= ?
+                                 OR (bfrp.id IS NOT NULL
+                                     AND 
frp.is_differential_to_base_lending_rate = TRUE
+                                     AND frp.from_date >= bfrp.from_date))
+                            AND lrr.loan_id IS NULL)
+                  )
+                GROUP BY l.id
+                """;
         try {
             LocalDate currentdate = DateUtils.getBusinessLocalDate();
             // will look only for yesterday modified rates
             LocalDate yesterday = 
DateUtils.getBusinessLocalDate().minusDays(1);
-            return this.jdbcTemplate.queryForList(sqlBuilder.toString(), 
Long.class, yesterday, LoanStatus.ACTIVE.getValue(), currentdate,
-                    currentdate, currentdate, yesterday);
+            return this.jdbcTemplate.queryForList(sql, Long.class, yesterday, 
LoanStatus.ACTIVE.getValue(), currentdate, currentdate,
+                    currentdate, yesterday);
         } catch (final EmptyResultDataAccessException e) {
             return null;
         }
@@ -2085,45 +2094,50 @@ public class LoanReadPlatformServiceImpl implements 
LoanReadPlatformService, Loa
         LocalDate currentdate = DateUtils.getBusinessLocalDate();
         // will look only for yesterday modified rates
         LocalDate yesterday = DateUtils.getBusinessLocalDate().minusDays(1);
-        StringBuilder sqlBuilder = new StringBuilder();
-        sqlBuilder.append("SELECT ml.id FROM m_loan ml ");
-        sqlBuilder.append(" left join m_client mc on mc.id = ml.client_id ");
-        sqlBuilder.append(" left join m_office o on mc.office_id = o.id  ");
-        sqlBuilder.append(" INNER JOIN m_loan_repayment_schedule mr on 
mr.loan_id = ml.id ");
-        sqlBuilder.append(
-                " LEFT JOIN m_loan_disbursement_detail dd on dd.loan_id=ml.id 
and dd.disbursedon_date is null and dd.is_reversed = false ");
-        // For Floating rate changes
-        sqlBuilder.append(
-                " left join m_product_loan_floating_rates pfr on ml.product_id 
= pfr.loan_product_id and ml.is_floating_interest_rate = true");
-        sqlBuilder.append(" left join m_floating_rates fr on  
pfr.floating_rates_id = fr.id");
-        sqlBuilder.append(" left join m_floating_rates_periods frp on fr.id = 
frp.floating_rates_id ");
-        sqlBuilder.append(" left join m_loan_reschedule_request lrr on 
lrr.loan_id = ml.id");
-        // this is to identify the applicable rates when base rate is changed
-        sqlBuilder.append(" left join  m_floating_rates bfr on  
bfr.is_base_lending_rate = true");
-        sqlBuilder.append(" left join  m_floating_rates_periods bfrp on  
bfr.id = bfrp.floating_rates_id and bfrp.created_date >= ?");
-        sqlBuilder.append(" WHERE ml.loan_status_id = ? ");
-        sqlBuilder.append(" and ml.is_npa = false and ml.is_charged_off = 
false ");
-        sqlBuilder.append(" and ((");
-        sqlBuilder.append("ml.interest_recalculation_enabled = true ");
-        sqlBuilder.append(" and (ml.interest_recalcualated_on is null or 
ml.interest_recalcualated_on <> ? )");
-        sqlBuilder.append(" and ((");
-        sqlBuilder.append(" mr.completed_derived is false ");
-        sqlBuilder.append(" and mr.duedate < ? )");
-        sqlBuilder.append(" or dd.expected_disburse_date < ? )) ");
-        sqlBuilder.append(" or (");
-        sqlBuilder.append(" fr.is_active = true and  frp.is_active = true");
-        sqlBuilder.append(" and (frp.created_date >=  ?  or ");
-        sqlBuilder
-                .append("(bfrp.id is not null and 
frp.is_differential_to_base_lending_rate = true and frp.from_date >= 
bfrp.from_date)) ");
-        sqlBuilder.append("and lrr.loan_id is null");
-        sqlBuilder.append(" ))");
-        sqlBuilder.append(" and ml.id >= ?  and o.hierarchy like ? ");
-        sqlBuilder.append(" group by ml.id ");
-        sqlBuilder.append(" limit ? ");
+        final String sql = """
+                SELECT l.id
+                FROM m_loan l
+                LEFT JOIN m_client c ON c.id = l.client_id
+                LEFT JOIN m_office o ON c.office_id = o.id
+                INNER JOIN m_loan_repayment_schedule rps ON rps.loan_id = l.id
+                LEFT JOIN m_loan_disbursement_detail dd
+                    ON dd.loan_id=l.id AND dd.disbursedon_date IS NULL AND 
dd.is_reversed = FALSE
+                -- for past due interest recalculation
+                LEFT JOIN m_loan_recalculation_details rcd ON rcd.loan_id = 
l.id
+                -- For Floating rate changes
+                LEFT JOIN m_product_loan_floating_rates pfr
+                    ON l.product_id = pfr.loan_product_id AND 
l.is_floating_interest_rate = TRUE
+                LEFT JOIN m_floating_rates fr ON pfr.floating_rates_id = fr.id
+                LEFT JOIN m_floating_rates_periods frp ON fr.id = 
frp.floating_rates_id
+                LEFT JOIN m_loan_reschedule_request lrr ON lrr.loan_id = l.id
+                -- this is to identify the applicable rates when base rate is 
changed
+                LEFT JOIN m_floating_rates bfr ON bfr.is_base_lending_rate = 
TRUE
+                LEFT JOIN m_floating_rates_periods bfrp ON bfr.id = 
bfrp.floating_rates_id AND bfrp.created_date >= ?
+                WHERE l.loan_status_id = ?
+                    AND l.is_npa = FALSE
+                    AND l.is_charged_off = FALSE
+                    AND (
+                         (l.interest_recalculation_enabled = TRUE
+                             AND (l.interest_recalcualated_on IS NULL OR 
l.interest_recalcualated_on <> ?)
+                             AND ((rps.completed_derived IS FALSE AND 
rps.duedate < ?) OR dd.expected_disburse_date < ?)
+                             AND rcd.disallow_interest_calc_on_past_due = 
FALSE)
+                        OR
+                         (fr.is_active = TRUE
+                             AND frp.is_active = TRUE
+                             AND (frp.created_date >= ?
+                                  OR (bfrp.id IS NOT NULL
+                                      AND 
frp.is_differential_to_base_lending_rate = TRUE
+                                      AND frp.from_date >= bfrp.from_date))
+                             AND lrr.loan_id IS NULL)
+                    )
+                    AND l.id >= ?
+                    AND o.hierarchy like ?
+                GROUP BY l.id
+                LIMIT ?
+                """;
         try {
-            return Collections.synchronizedList(
-                    this.jdbcTemplate.queryForList(sqlBuilder.toString(), 
Long.class, yesterday, LoanStatus.ACTIVE.getValue(), currentdate,
-                            currentdate, currentdate, yesterday, 
maxLoanIdInList, officeHierarchy, pageSize));
+            return 
Collections.synchronizedList(this.jdbcTemplate.queryForList(sql, Long.class, 
yesterday, LoanStatus.ACTIVE.getValue(),
+                    currentdate, currentdate, currentdate, yesterday, 
maxLoanIdInList, officeHierarchy, pageSize));
         } catch (final EmptyResultDataAccessException e) {
             return null;
         }
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/serialization/LoanProductDataValidator.java
 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/serialization/LoanProductDataValidator.java
index 7ecc81e6f..51813bb31 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/serialization/LoanProductDataValidator.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/serialization/LoanProductDataValidator.java
@@ -174,7 +174,8 @@ public final class LoanProductDataValidator {
             LoanProductConstants.recalculationRestFrequencyWeekdayParamName, 
LoanProductConstants.recalculationRestFrequencyNthDayParamName,
             LoanProductConstants.recalculationRestFrequencyOnDayParamName,
             
LoanProductConstants.isCompoundingToBePostedAsTransactionParamName, 
LoanProductConstants.allowCompoundingOnEodParamName,
-            LoanProductConstants.CAN_USE_FOR_TOPUP, 
LoanProductConstants.IS_EQUAL_AMORTIZATION_PARAM, 
LoanProductConstants.RATES_PARAM_NAME,
+            
LoanProductConstants.disallowInterestCalculationOnPastDueParamName, 
LoanProductConstants.CAN_USE_FOR_TOPUP,
+            LoanProductConstants.IS_EQUAL_AMORTIZATION_PARAM, 
LoanProductConstants.RATES_PARAM_NAME,
             
LoanProductConstants.fixedPrincipalPercentagePerInstallmentParamName, 
LoanProductConstants.DISALLOW_EXPECTED_DISBURSEMENTS,
             
LoanProductConstants.ALLOW_APPROVED_DISBURSED_AMOUNTS_OVER_APPLIED, 
LoanProductConstants.OVER_APPLIED_CALCULATION_TYPE,
             LoanProductConstants.OVER_APPLIED_NUMBER, 
LoanProductConstants.DELINQUENCY_BUCKET_PARAM_NAME,
@@ -1070,6 +1071,12 @@ public final class LoanProductDataValidator {
             if 
(fromApiJsonHelper.parameterExists(LoanProductConstants.LOAN_SCHEDULE_TYPE, 
element)) {
                 loanScheduleType = 
fromApiJsonHelper.extractStringNamed(LoanProductConstants.LOAN_SCHEDULE_TYPE, 
element);
             }
+            if 
(LoanScheduleType.CUMULATIVE.equals(LoanScheduleType.valueOf(loanScheduleType))
+                    && 
fromApiJsonHelper.parameterExists(LoanProductConstants.disallowInterestCalculationOnPastDueParamName,
 element)) {
+                
baseDataValidator.reset().parameter(LoanProductConstants.disallowInterestCalculationOnPastDueParamName).failWithCode(
+                        
"disallow.interest.calculation.on.past.due.not.supported.for.loan.schedule.type.cumulative",
+                        "Do not calculate interest on past due principal 
balances is only supported for Progressive loan schedule type");
+            }
             if 
(LoanScheduleType.CUMULATIVE.equals(LoanScheduleType.valueOf(loanScheduleType))
                     && 
LoanRescheduleStrategyMethod.ADJUST_LAST_UNPAID_PERIOD.equals(loanRescheduleStrategyMethod))
 {
                 
baseDataValidator.reset().parameter(LoanProductConstants.rescheduleStrategyMethodParameterName).failWithCode(
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/service/LoanProductReadPlatformServiceImpl.java
 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/service/LoanProductReadPlatformServiceImpl.java
index feddcf502..1e88cffea 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/service/LoanProductReadPlatformServiceImpl.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/service/LoanProductReadPlatformServiceImpl.java
@@ -259,7 +259,9 @@ public class LoanProductReadPlatformServiceImpl implements 
LoanProductReadPlatfo
                     + "lpr.compounding_frequency_weekday_enum as 
compoundingFrequencyWeekDayEnum, "
                     + "lpr.compounding_frequency_on_day as 
compoundingFrequencyOnDay, "
                     + "lpr.is_compounding_to_be_posted_as_transaction as 
isCompoundingToBePostedAsTransaction, "
-                    + "lpr.allow_compounding_on_eod as allowCompoundingOnEod, 
" + "lp.hold_guarantee_funds as holdGuaranteeFunds, "
+                    + "lpr.allow_compounding_on_eod as allowCompoundingOnEod, "
+                    + "lpr.disallow_interest_calc_on_past_due as 
disallowInterestCalculationOnPastDue, "
+                    + "lp.hold_guarantee_funds as holdGuaranteeFunds, "
                     + "lp.principal_threshold_for_last_installment as 
principalThresholdForLastInstallment, "
                     + "lp.fixed_principal_percentage_per_installment 
fixedPrincipalPercentagePerInstallment, "
                     + "lp.sync_expected_with_disbursement_date as 
syncExpectedWithDisbursementDate, "
@@ -477,12 +479,14 @@ public class LoanProductReadPlatformServiceImpl 
implements LoanProductReadPlatfo
                 final EnumOptionData preCloseInterestCalculationStrategy = 
LoanEnumerations
                         
.preCloseInterestCalculationStrategy(preCloseInterestCalculationStrategyEnumValue);
                 final boolean allowCompoundingOnEod = 
rs.getBoolean("allowCompoundingOnEod");
+                final boolean disallowInterestCalculationOnPastDue = 
rs.getBoolean("disallowInterestCalculationOnPastDue");
 
                 interestRecalculationData = new 
LoanProductInterestRecalculationData(lprId, productId, 
interestRecalculationCompoundingType,
                         rescheduleStrategyType, restFrequencyType, 
restFrequencyInterval, restFrequencyNthDayEnum, restFrequencyWeekDayEnum,
                         restFrequencyOnDay, compoundingFrequencyType, 
compoundingInterval, compoundingFrequencyNthDayEnum,
                         compoundingFrequencyWeekDayEnum, 
compoundingFrequencyOnDay, isArrearsBasedOnOriginalSchedule,
-                        isCompoundingToBePostedAsTransaction, 
preCloseInterestCalculationStrategy, allowCompoundingOnEod);
+                        isCompoundingToBePostedAsTransaction, 
preCloseInterestCalculationStrategy, allowCompoundingOnEod,
+                        disallowInterestCalculationOnPastDue);
             }
 
             final boolean amortization = rs.getBoolean("amortizationBoolean");
diff --git 
a/fineract-provider/src/main/resources/db/changelog/tenant/changelog-tenant.xml 
b/fineract-provider/src/main/resources/db/changelog/tenant/changelog-tenant.xml
index af723c51a..b01cf4d54 100644
--- 
a/fineract-provider/src/main/resources/db/changelog/tenant/changelog-tenant.xml
+++ 
b/fineract-provider/src/main/resources/db/changelog/tenant/changelog-tenant.xml
@@ -174,4 +174,5 @@
     <include 
file="parts/0153_add_charge_off_reason_id_to_acc_product_mapping.xml" 
relativeToChangelogFile="true" />
     <include file="parts/0154_add_interest_refund_to_r_enum_value.xml" 
relativeToChangelogFile="true" />
     <include 
file="parts/0155_add_configuration_enable_immediate_charge_accrual_post_maturity.xml"
 relativeToChangelogFile="true" />
+    <include 
file="parts/0156_add_disallow_interest_calc_on_past_due_field.xml" 
relativeToChangelogFile="true" />
 </databaseChangeLog>
diff --git 
a/fineract-provider/src/main/resources/db/changelog/tenant/parts/0156_add_disallow_interest_calc_on_past_due_field.xml
 
b/fineract-provider/src/main/resources/db/changelog/tenant/parts/0156_add_disallow_interest_calc_on_past_due_field.xml
new file mode 100644
index 000000000..c078794d2
--- /dev/null
+++ 
b/fineract-provider/src/main/resources/db/changelog/tenant/parts/0156_add_disallow_interest_calc_on_past_due_field.xml
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+    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.
+
+-->
+<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog";
+                   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance";
+                   
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog 
http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.3.xsd";>
+
+    <changeSet id="1" author="fineract">
+        <addColumn tableName="m_product_loan_recalculation_details">
+            <column name="disallow_interest_calc_on_past_due" 
defaultValueBoolean="false" type="boolean">
+                <constraints nullable="false"/>
+            </column>
+        </addColumn>
+
+        <addColumn tableName="m_loan_recalculation_details">
+            <column name="disallow_interest_calc_on_past_due" 
defaultValueBoolean="false" type="boolean">
+                <constraints nullable="false"/>
+            </column>
+        </addColumn>
+    </changeSet>
+</databaseChangeLog>
diff --git 
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanInterestRecalculationCOBTest.java
 
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanInterestRecalculationCOBTest.java
index 63395b1b9..c87f5bac6 100644
--- 
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanInterestRecalculationCOBTest.java
+++ 
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanInterestRecalculationCOBTest.java
@@ -69,7 +69,7 @@ public class LoanInterestRecalculationCOBTest extends 
BaseLoanIntegrationTest {
         Utils.initializeRESTAssured();
         requestSpec = new 
RequestSpecBuilder().setContentType(ContentType.JSON).build();
         requestSpec.header("Authorization", "Basic " + 
Utils.loginIntoServerAndGetBase64EncodedAuthenticationKey());
-        requestSpec.header("Fineract-Platform-TenantId", "default");
+        requestSpec.header("Fineract-Platform-TenantId", Utils.DEFAULT_TENANT);
         responseSpec = new ResponseSpecBuilder().expectStatusCode(200).build();
         loanTransactionHelper = new LoanTransactionHelper(requestSpec, 
responseSpec);
         schedulerJobHelper = new SchedulerJobHelper(requestSpec);
@@ -1222,4 +1222,68 @@ public class LoanInterestRecalculationCOBTest extends 
BaseLoanIntegrationTest {
         payoffOnDateAndVerifyStatus("1 May 2023", loanIdRef.get());
     }
 
+    @Test
+    public void 
verifyEarlyLateRepaymentOnProgressiveLoanThePastDueHasNoEffect() {
+        AtomicReference<Long> loanIdRef = new AtomicReference<>();
+        runAt("1 January 2024", () -> {
+            PostLoanProductsResponse loanProduct = loanProductHelper //
+                    .createLoanProduct(create4IProgressive() //
+                            
.recalculationRestFrequencyType(RecalculationRestFrequencyType.DAILY) //
+                            .disallowInterestCalculationOnPastDue(true) //
+            );
+
+            Long loanId = applyAndApproveProgressiveLoan(client.getClientId(), 
loanProduct.getResourceId(), "1 January 2024", 100.0, 7.0, 6,
+                    null);
+            loanIdRef.set(loanId);
+
+            disburseLoan(loanId, BigDecimal.valueOf(100), "1 January 2024");
+
+            GetLoansLoanIdResponse loanDetails = 
loanTransactionHelper.getLoanDetails(loanId);
+            logLoanDetails(loanDetails);
+
+            validateFullyUnpaidRepaymentPeriod(loanDetails, 1, "01 February 
2024", 16.43, 0.0, 0.0, 0.58);
+            validateFullyUnpaidRepaymentPeriod(loanDetails, 2, "01 March 
2024", 16.52, 0.0, 0.0, 0.49);
+            validateFullyUnpaidRepaymentPeriod(loanDetails, 3, "01 April 
2024", 16.62, 0.0, 0.0, 0.39);
+            validateFullyUnpaidRepaymentPeriod(loanDetails, 4, "01 May 2024", 
16.72, 0.0, 0.0, 0.29);
+            validateFullyUnpaidRepaymentPeriod(loanDetails, 5, "01 June 2024", 
16.81, 0.0, 0.0, 0.20);
+            validateFullyUnpaidRepaymentPeriod(loanDetails, 6, "01 July 2024", 
16.90, 0.0, 0.0, 0.10);
+
+        });
+        runAt("15 February 2024", () -> { // we have past due and should not 
count extra interest
+            Long loanId = loanIdRef.get();
+
+            inlineLoanCOBHelper.executeInlineCOB(List.of(loanId));
+            loanTransactionHelper.makeLoanRepayment("15 February 2024", 15.0F, 
loanId.intValue());
+
+            GetLoansLoanIdResponse loanDetails = 
loanTransactionHelper.getLoanDetails(loanId);
+            logLoanDetails(loanDetails);
+
+            validateRepaymentPeriod(loanDetails, 1, LocalDate.of(2024, 2, 1), 
16.43, 15.0, 1.43, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.58, 0,
+                    0.58, 0.0, 15.0);
+            validateFullyUnpaidRepaymentPeriod(loanDetails, 2, "01 March 
2024", 16.52, 0.0, 0.0, 0.49);
+            validateFullyUnpaidRepaymentPeriod(loanDetails, 3, "01 April 
2024", 16.62, 0.0, 0.0, 0.39);
+            validateFullyUnpaidRepaymentPeriod(loanDetails, 4, "01 May 2024", 
16.72, 0.0, 0.0, 0.29);
+            validateFullyUnpaidRepaymentPeriod(loanDetails, 5, "01 June 2024", 
16.81, 0.0, 0.0, 0.20);
+            validateFullyUnpaidRepaymentPeriod(loanDetails, 6, "01 July 2024", 
16.90, 0.0, 0.0, 0.10);
+        });
+        runAt("15 February 2024", () -> { // we turn from past due into early 
repayment and should have less interest
+            Long loanId = loanIdRef.get();
+
+            inlineLoanCOBHelper.executeInlineCOB(List.of(loanId));
+            loanTransactionHelper.makeLoanRepayment("15 February 2024", 
19.02F, loanId.intValue());
+
+            GetLoansLoanIdResponse loanDetails = 
loanTransactionHelper.getLoanDetails(loanId);
+            logLoanDetails(loanDetails);
+
+            validateRepaymentPeriod(loanDetails, 1, LocalDate.of(2024, 2, 1), 
16.43, 16.43, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.58, 0.58,
+                    0.0, 0.0, 17.01);
+            validateRepaymentPeriod(loanDetails, 2, LocalDate.of(2024, 3, 1), 
16.77, 16.77, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.24, 0.24,
+                    0.0, 17.01, 0.0);
+            validateFullyUnpaidRepaymentPeriod(loanDetails, 3, "01 April 
2024", 16.42, 0.0, 0.0, 0.59);
+            validateFullyUnpaidRepaymentPeriod(loanDetails, 4, "01 May 2024", 
16.72, 0.0, 0.0, 0.29);
+            validateFullyUnpaidRepaymentPeriod(loanDetails, 5, "01 June 2024", 
16.81, 0.0, 0.0, 0.20);
+            validateFullyUnpaidRepaymentPeriod(loanDetails, 6, "01 July 2024", 
16.85, 0.0, 0.0, 0.10);
+        });
+    }
+
 }

Reply via email to