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

(fineract) branch develop updated: FINERACT-2081: Skip loan processing if no overdue

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 b68149c6c FINERACT-2081: Skip loan processing if no overdue
b68149c6c is described below

commit b68149c6cd02349139d4612e70420ff6b9c42593
Author: Andrii Kulminskyi <ku...@gmail.com>
AuthorDate: Wed Sep 25 17:43:28 2024 +0300

    FINERACT-2081: Skip loan processing if no overdue
---
 .../src/test/resources/features/COB.feature        | 18 ++---
 .../resources/features/LoanDelinquency.feature     | 82 +++++----------------
 .../ApplyChargeToOverdueLoansBusinessStep.java     |  4 +-
 ...ApplyChargeToOverdueLoanInstallmentTasklet.java |  4 +-
 .../LoanChargeWritePlatformServiceImpl.java        |  9 +++
 .../service/LoanReadPlatformServiceImpl.java       | 20 ++---
 .../ApplyChargeToOverdueLoansBusinessStepTest.java | 80 ++++++++++++++++++++
 ...yChargeToOverdueLoanInstallmentTaskletTest.java | 86 ++++++++++++++++++++++
 .../LoanTransactionAccrualActivityPostingTest.java |  2 +-
 9 files changed, 219 insertions(+), 86 deletions(-)

diff --git a/fineract-e2e-tests-runner/src/test/resources/features/COB.feature b/fineract-e2e-tests-runner/src/test/resources/features/COB.feature
index 82fb15fd1..47ae563f2 100644
--- a/fineract-e2e-tests-runner/src/test/resources/features/COB.feature
+++ b/fineract-e2e-tests-runner/src/test/resources/features/COB.feature
@@ -58,9 +58,9 @@ Feature: COBFeature
     When Admin successfully disburse the loan on "01 July 2023" with "1000" EUR transaction amount
     Then Loan status will be "ACTIVE"
     Then Admin checks that last closed business date of loan is "null"
-    When Admin sets the business date to "10 August 2023"
+    When Admin sets the business date to "09 August 2023"
     When Admin runs inline COB job for Loan
-    Then Admin checks that last closed business date of loan is "09 August 2023"
+    Then Admin checks that last closed business date of loan is "08 August 2023"
     Then Admin checks that delinquency range is: "RANGE_3" and has delinquentDate "2023-08-03"
     Then Loan delinquency history has the following details:
       | Range (Classification) | Added on date  | Lifted on date |
@@ -68,7 +68,7 @@ Feature: COBFeature
       | RANGE_1                | 04 August 2023 | 09 August 2023 |
     When Admin sets the business date to "12 August 2023"
     When Admin runs COB job
-    Then Admin checks that last closed business date of loan is "09 August 2023"
+    Then Admin checks that last closed business date of loan is "08 August 2023"
     Then Loan delinquency history has the following details:
       | Range (Classification) | Added on date  | Lifted on date |
       | RANGE_3                | 09 August 2023 |                |
@@ -89,21 +89,21 @@ Feature: COBFeature
     Then Admin checks that delinquency range is: "RANGE_3" and has delinquentDate "2023-08-03"
     Then Loan delinquency history has the following details:
       | Range (Classification) | Added on date  | Lifted on date |
-      | RANGE_3                | 09 August 2023 |                |
+      | RANGE_3                | 10 August 2023 |                |
       | RANGE_1                | 04 August 2023 | 09 August 2023 |
     And Customer makes "AUTOPAY" repayment on "10 August 2023" with 1000 EUR transaction amount
     Then Loan status will be "CLOSED_OBLIGATIONS_MET"
     Then Admin checks that delinquency range is: "NO_DELINQUENCY" and has delinquentDate ""
     Then Loan delinquency history has the following details:
       | Range (Classification) | Added on date  | Lifted on date |
-      | RANGE_3                | 09 August 2023 | 10 August 2023 |
+      | RANGE_3                | 10 August 2023 | 10 August 2023 |
       | RANGE_1                | 04 August 2023 | 09 August 2023 |
     When Admin sets the business date to "11 August 2023"
     When Admin runs COB job
     Then Admin checks that last closed business date of loan is "09 August 2023"
     Then Loan delinquency history has the following details:
       | Range (Classification) | Added on date  | Lifted on date |
-      | RANGE_3                | 09 August 2023 | 10 August 2023 |
+      | RANGE_3                | 10 August 2023 | 10 August 2023 |
       | RANGE_1                | 04 August 2023 | 09 August 2023 |
 
 
@@ -121,21 +121,21 @@ Feature: COBFeature
     Then Admin checks that delinquency range is: "RANGE_3" and has delinquentDate "2023-08-03"
     Then Loan delinquency history has the following details:
       | Range (Classification) | Added on date  | Lifted on date |
-      | RANGE_3                | 09 August 2023 |                |
+      | RANGE_3                | 10 August 2023 |                |
       | RANGE_1                | 04 August 2023 | 09 August 2023 |
     And Customer makes "AUTOPAY" repayment on "10 August 2023" with 1200 EUR transaction amount
     Then Loan status will be "OVERPAID"
     Then Admin checks that delinquency range is: "NO_DELINQUENCY" and has delinquentDate ""
     Then Loan delinquency history has the following details:
       | Range (Classification) | Added on date  | Lifted on date |
-      | RANGE_3                | 09 August 2023 | 10 August 2023 |
+      | RANGE_3                | 10 August 2023 | 10 August 2023 |
       | RANGE_1                | 04 August 2023 | 09 August 2023 |
     When Admin sets the business date to "11 August 2023"
     When Admin runs COB job
     Then Admin checks that last closed business date of loan is "09 August 2023"
     Then Loan delinquency history has the following details:
       | Range (Classification) | Added on date  | Lifted on date |
-      | RANGE_3                | 09 August 2023 | 10 August 2023 |
+      | RANGE_3                | 10 August 2023 | 10 August 2023 |
       | RANGE_1                | 04 August 2023 | 09 August 2023 |
 
    @Skip
diff --git a/fineract-e2e-tests-runner/src/test/resources/features/LoanDelinquency.feature b/fineract-e2e-tests-runner/src/test/resources/features/LoanDelinquency.feature
index 694b83627..ebd342ebd 100644
--- a/fineract-e2e-tests-runner/src/test/resources/features/LoanDelinquency.feature
+++ b/fineract-e2e-tests-runner/src/test/resources/features/LoanDelinquency.feature
@@ -225,7 +225,7 @@ Feature: LoanDelinquency
     When Admin sets the business date to "01 October 2023"
     When Admin creates a client with random data
     When Admin creates a fully customized loan with the following data:
-      | LoanProduct                                       | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type  | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy            |
+      | LoanProduct                                      | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type  | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy            |
       | LP2_DOWNPAYMENT_AUTO_ADVANCED_PAYMENT_ALLOCATION | 01 October 2023   | 1000           | 0                      | FLAT          | SAME_AS_REPAYMENT_PERIOD    | EQUAL_INSTALLMENTS | 45                | DAYS                  | 15             | DAYS                   | 3                  | 0                       | 0                      | 0                    | ADVANCED_PAYMENT_ALLOCATION |
     And Admin successfully approves the loan on "01 October 2023" with "1000" amount and expected disbursement date on "01 October 2023"
     When Admin successfully disburse the loan on "01 October 2023" with "1000" EUR transaction amount
@@ -625,11 +625,7 @@ Feature: LoanDelinquency
       | PAUSE  | 15 October 2023 | 30 October 2023 |
     Then Loan has the following LOAN level delinquency data:
       | classification | delinquentAmount | delinquentDate  | delinquentDays | pastDueDays |
-      | RANGE_3        | 250.0            | 04 October 2023 | 11             | 14          |
-    Then Loan has the following INSTALLMENT level delinquency data:
-      | rangeId | Range   | Amount |
-      | 2       | RANGE_3 | 250.00 |
-    Then Installment level delinquency event has correct data
+      | NO_DELINQUENCY | 250.0            | 04 October 2023 | 11             | 14          |
 #    --- Delinquency resume ---
     When Admin sets the business date to "25 October 2023"
     When Admin initiate a DELINQUENCY RESUME with startDate: "25 October 2023"
@@ -647,11 +643,7 @@ Feature: LoanDelinquency
       | RESUME | 25 October 2023 |                 |
     Then Loan has the following LOAN level delinquency data:
       | classification | delinquentAmount | delinquentDate  | delinquentDays | pastDueDays |
-      | RANGE_3        | 500.0            | 04 October 2023 | 12             | 25          |
-    Then Loan has the following INSTALLMENT level delinquency data:
-      | rangeId | Range   | Amount |
-      | 2       | RANGE_3 | 250.00 |
-    Then Installment level delinquency event has correct data
+      | NO_DELINQUENCY | 500.0            | 04 October 2023 | 12             | 25          |
 
 
   Scenario: Verify Loan delinquency pause E2E - PAUSE period with RESUME and second PAUSE
@@ -674,11 +666,7 @@ Feature: LoanDelinquency
       | PAUSE  | 15 October 2023 | 30 October 2023 |
     Then Loan has the following LOAN level delinquency data:
       | classification | delinquentAmount | delinquentDate  | delinquentDays | pastDueDays |
-      | RANGE_3        | 250.0            | 04 October 2023 | 11             | 14          |
-    Then Loan has the following INSTALLMENT level delinquency data:
-      | rangeId | Range   | Amount |
-      | 2       | RANGE_3 | 250.00 |
-    Then Installment level delinquency event has correct data
+      | NO_DELINQUENCY | 250.0            | 04 October 2023 | 11             | 14          |
 #    --- Delinquency resume ---
     When Admin sets the business date to "25 October 2023"
     When Admin initiate a DELINQUENCY RESUME with startDate: "25 October 2023"
@@ -696,11 +684,7 @@ Feature: LoanDelinquency
       | RESUME | 25 October 2023 |                 |
     Then Loan has the following LOAN level delinquency data:
       | classification | delinquentAmount | delinquentDate  | delinquentDays | pastDueDays |
-      | RANGE_3        | 500.0            | 04 October 2023 | 12             | 25          |
-    Then Loan has the following INSTALLMENT level delinquency data:
-      | rangeId | Range   | Amount |
-      | 2       | RANGE_3 | 250.00 |
-    Then Installment level delinquency event has correct data
+      | NO_DELINQUENCY | 500.0            | 04 October 2023 | 12             | 25          |
 #   --- Delinquency runs ---
     When Admin sets the business date to "13 November 2023"
     When Admin runs inline COB job for Loan
@@ -755,10 +739,11 @@ Feature: LoanDelinquency
       | PAUSE  | 14 November 2023 | 30 November 2023 |
     Then Loan has the following LOAN level delinquency data:
       | classification | delinquentAmount | delinquentDate  | delinquentDays | pastDueDays |
-      | RANGE_30       | 1000.0           | 04 October 2023 | 31             | 60          |
+      | RANGE_3        | 1000.0           | 04 October 2023 | 31             | 60          |
     Then Loan has the following INSTALLMENT level delinquency data:
       | rangeId | Range    | Amount |
-      | 2       | RANGE_3  | 500.00 |
+      | 1       | RANGE_1  | 250.00 |
+      | 2       | RANGE_3  | 250.00 |
       | 3       | RANGE_30 | 250.00 |
 #    --- Delinquency runs again ---
     When Admin sets the business date to "01 December 2023"
@@ -802,12 +787,7 @@ Feature: LoanDelinquency
       | PAUSE  | 06 October 2023 | 30 October 2023 |
     Then Loan has the following LOAN level delinquency data:
       | classification | delinquentAmount | delinquentDate  | delinquentDays | pastDueDays |
-      | RANGE_1        | 250.0            | 04 October 2023 | 2              | 5           |
-#    --- Grace period applied only on Loan level, not on installment level ---
-    Then Loan has the following INSTALLMENT level delinquency data:
-      | rangeId | Range   | Amount |
-      | 2       | RANGE_3 | 250.00 |
-    Then Installment level delinquency event has correct data
+      | NO_DELINQUENCY | 250.0            | 04 October 2023 | 2              | 5           |
 #    --- Full repayment for late/due date installments ---
     When Admin sets the business date to "16 October 2023"
     Then Loan Delinquency pause periods has the following data:
@@ -818,11 +798,7 @@ Feature: LoanDelinquency
       | PAUSE  | 06 October 2023 | 30 October 2023 |
     Then Loan has the following LOAN level delinquency data:
       | classification | delinquentAmount | delinquentDate  | delinquentDays | pastDueDays |
-      | RANGE_1        | 250.0            | 04 October 2023 | 2              | 15          |
-#    --- Grace period applied only on Loan level, not on installment level ---
-    Then Loan has the following INSTALLMENT level delinquency data:
-      | rangeId | Range   | Amount |
-      | 2       | RANGE_3 | 250.00 |
+      | NO_DELINQUENCY | 250.0            | 04 October 2023 | 2              | 15          |
     And Customer makes "AUTOPAY" repayment on "16 October 2023" with 500 EUR transaction amount
     When Admin runs inline COB job for Loan
     Then Loan Delinquency pause periods has the following data:
@@ -857,12 +833,7 @@ Feature: LoanDelinquency
       | PAUSE  | 06 October 2023 | 30 October 2023 |
     Then Loan has the following LOAN level delinquency data:
       | classification | delinquentAmount | delinquentDate  | delinquentDays | pastDueDays |
-      | RANGE_1        | 250.0            | 04 October 2023 | 2              | 5           |
-#    --- Grace period applied only on Loan level, not on installment level ---
-    Then Loan has the following INSTALLMENT level delinquency data:
-      | rangeId | Range   | Amount |
-      | 2       | RANGE_3 | 250.00 |
-    Then Installment level delinquency event has correct data
+      | NO_DELINQUENCY | 250.0            | 04 October 2023 | 2              | 5           |
 #    --- Full repayment for late/due date installments ---
     When Admin sets the business date to "16 October 2023"
     Then Loan Delinquency pause periods has the following data:
@@ -873,11 +844,7 @@ Feature: LoanDelinquency
       | PAUSE  | 06 October 2023 | 30 October 2023 |
     Then Loan has the following LOAN level delinquency data:
       | classification | delinquentAmount | delinquentDate  | delinquentDays | pastDueDays |
-      | RANGE_1        | 250.0            | 04 October 2023 | 2              | 15          |
-#    --- Grace period applied only on Loan level, not on installment level ---
-    Then Loan has the following INSTALLMENT level delinquency data:
-      | rangeId | Range   | Amount |
-      | 2       | RANGE_3 | 250.00 |
+      | NO_DELINQUENCY | 250.0            | 04 October 2023 | 2              | 15          |
     And Customer makes "AUTOPAY" repayment on "16 October 2023" with 150 EUR transaction amount
     When Admin runs inline COB job for Loan
     Then Loan Delinquency pause periods has the following data:
@@ -915,12 +882,7 @@ Feature: LoanDelinquency
       | PAUSE  | 06 October 2023 | 30 October 2023 |
     Then Loan has the following LOAN level delinquency data:
       | classification | delinquentAmount | delinquentDate  | delinquentDays | pastDueDays |
-      | RANGE_1        | 250.0            | 04 October 2023 | 2              | 5           |
-#    --- Grace period applied only on Loan level, not on installment level ---
-    Then Loan has the following INSTALLMENT level delinquency data:
-      | rangeId | Range   | Amount |
-      | 2       | RANGE_3 | 250.00 |
-    Then Installment level delinquency event has correct data
+      | NO_DELINQUENCY | 250.0            | 04 October 2023 | 2              | 5           |
 #    --- Full repayment for late/due date installments ---
     When Admin sets the business date to "16 October 2023"
     Then Loan Delinquency pause periods has the following data:
@@ -931,11 +893,7 @@ Feature: LoanDelinquency
       | PAUSE  | 06 October 2023 | 30 October 2023 |
     Then Loan has the following LOAN level delinquency data:
       | classification | delinquentAmount | delinquentDate  | delinquentDays | pastDueDays |
-      | RANGE_1        | 250.0            | 04 October 2023 | 2              | 15          |
-#    --- Grace period applied only on Loan level, not on installment level ---
-    Then Loan has the following INSTALLMENT level delinquency data:
-      | rangeId | Range   | Amount |
-      | 2       | RANGE_3 | 250.00 |
+      | NO_DELINQUENCY | 250.0            | 04 October 2023 | 2              | 15          |
     And Customer makes "AUTOPAY" repayment on "16 October 2023" with 250 EUR transaction amount
     When Admin runs inline COB job for Loan
     Then Loan Delinquency pause periods has the following data:
@@ -991,7 +949,7 @@ Feature: LoanDelinquency
     When Admin sets the business date to "01 October 2023"
     When Admin creates a client with random data
     When Admin creates a fully customized loan with the following data:
-      | LoanProduct                                                                                       | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type  | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy            |
+      | LoanProduct                                                                                      | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type  | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy            |
       | LP2_DOWNPAYMENT_ADV_PMT_ALLOC_PROGRESSIVE_LOAN_SCHEDULE_HORIZONTAL_INSTALLMENT_LEVEL_DELINQUENCY | 01 October 2023   | 1000           | 0                      | FLAT          | SAME_AS_REPAYMENT_PERIOD    | EQUAL_INSTALLMENTS | 45                | DAYS                  | 15             | DAYS                   | 3                  | 0                       | 0                      | 0                    | ADVANCED_PAYMENT_ALLOCATION |
     And Admin successfully approves the loan on "01 October 2023" with "1000" amount and expected disbursement date on "01 October 2023"
     When Admin successfully disburse the loan on "01 October 2023" with "1000" EUR transaction amount
@@ -1034,10 +992,7 @@ Feature: LoanDelinquency
       | true   | 30 October 2023  | 30 October 2023 |
     Then Loan has the following LOAN level delinquency data:
       | classification | delinquentAmount | delinquentDate  | delinquentDays | pastDueDays |
-      | RANGE_3        | 500.0            | 04 October 2023 | 21             | 29          |
-    Then Loan has the following INSTALLMENT level delinquency data:
-      | rangeId | Range   | Amount |
-      | 2       | RANGE_3 | 500.00 |
+      | NO_DELINQUENCY | 500.0            | 04 October 2023 | 21             | 29          |
     When Admin sets the business date to "31 October 2023"
     Then Loan Delinquency pause periods has the following data:
       | active | pausePeriodStart | pausePeriodEnd  |
@@ -1045,10 +1000,7 @@ Feature: LoanDelinquency
       | false  | 30 October 2023  | 30 October 2023 |
     Then Loan has the following LOAN level delinquency data:
       | classification | delinquentAmount | delinquentDate  | delinquentDays | pastDueDays |
-      | RANGE_3        | 500.0            | 04 October 2023 | 22             | 30          |
-    Then Loan has the following INSTALLMENT level delinquency data:
-      | rangeId | Range   | Amount |
-      | 2       | RANGE_3 | 500.00 |
+      | NO_DELINQUENCY | 500.0            | 04 October 2023 | 22             | 30          |
 
 
   Scenario: Verify that creating a loan with Advanced payment allocation with product no Advanced payment allocation set results an error
diff --git a/fineract-provider/src/main/java/org/apache/fineract/cob/loan/ApplyChargeToOverdueLoansBusinessStep.java b/fineract-provider/src/main/java/org/apache/fineract/cob/loan/ApplyChargeToOverdueLoansBusinessStep.java
index 86a26eceb..7d157a49f 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/cob/loan/ApplyChargeToOverdueLoansBusinessStep.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/cob/loan/ApplyChargeToOverdueLoansBusinessStep.java
@@ -37,7 +37,9 @@ public class ApplyChargeToOverdueLoansBusinessStep implements LoanCOBBusinessSte
     public Loan execute(Loan loan) {
         final Collection<OverdueLoanScheduleData> overdueLoanScheduleDataList = loanReadPlatformService
                 .retrieveAllOverdueInstallmentsForLoan(loan);
-        loanChargeWritePlatformService.applyOverdueChargesForLoan(loan.getId(), overdueLoanScheduleDataList);
+        if (!overdueLoanScheduleDataList.isEmpty()) {
+            loanChargeWritePlatformService.applyOverdueChargesForLoan(loan.getId(), overdueLoanScheduleDataList);
+        }
         return loan;
     }
 
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/jobs/applychargetooverdueloaninstallment/ApplyChargeToOverdueLoanInstallmentTasklet.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/jobs/applychargetooverdueloaninstallment/ApplyChargeToOverdueLoanInstallmentTasklet.java
index bd09da8fc..0477ab170 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/jobs/applychargetooverdueloaninstallment/ApplyChargeToOverdueLoanInstallmentTasklet.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/jobs/applychargetooverdueloaninstallment/ApplyChargeToOverdueLoanInstallmentTasklet.java
@@ -68,7 +68,9 @@ public class ApplyChargeToOverdueLoanInstallmentTasklet implements Tasklet {
             List<Throwable> exceptions = new ArrayList<>();
             for (Map.Entry<Long, Collection<OverdueLoanScheduleData>> entry : overdueScheduleData.entrySet()) {
                 try {
-                    loanChargeWritePlatformService.applyOverdueChargesForLoan(entry.getKey(), entry.getValue());
+                    if (!entry.getValue().isEmpty()) {
+                        loanChargeWritePlatformService.applyOverdueChargesForLoan(entry.getKey(), entry.getValue());
+                    }
 
                 } catch (final PlatformApiDataValidationException e) {
                     final List<ApiParameterError> errors = e.getErrors();
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanChargeWritePlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanChargeWritePlatformServiceImpl.java
index 0a483a3ff..773116fdb 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanChargeWritePlatformServiceImpl.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanChargeWritePlatformServiceImpl.java
@@ -31,6 +31,7 @@ import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Locale;
 import java.util.Map;
+import java.util.Optional;
 import java.util.Set;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
@@ -764,11 +765,19 @@ public class LoanChargeWritePlatformServiceImpl implements LoanChargeWritePlatfo
     @Transactional
     @Override
     public void applyOverdueChargesForLoan(final Long loanId, Collection<OverdueLoanScheduleData> overdueLoanScheduleDataList) {
+        if (overdueLoanScheduleDataList.isEmpty()) {
+            return;
+        }
         Loan loan = this.loanAssembler.assembleFrom(loanId);
         if (loan.isChargedOff()) {
             log.warn("Adding charge to Loan: {} is not allowed. Loan Account is Charged-off", loanId);
             return;
         }
+        Optional<Charge> optPenaltyCharge = loan.getLoanProduct().getCharges().stream()
+                .filter((e) -> ChargeTimeType.OVERDUE_INSTALLMENT.getValue().equals(e.getChargeTimeType()) && e.isLoanCharge()).findFirst();
+        if (optPenaltyCharge.isEmpty()) {
+            return;
+        }
         final List<Long> existingTransactionIds = loan.findExistingTransactionIds();
         final List<Long> existingReversedTransactionIds = loan.findExistingReversedTransactionIds();
         boolean runInterestRecalculation = false;
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 7b745a752..4e38213f2 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
@@ -1642,6 +1642,15 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService, Loa
         if (!loan.isOpen()) {
             return list;
         }
+
+        Optional<Charge> optPenaltyCharge = loan.getLoanProduct().getCharges().stream()
+                .filter((e) -> ChargeTimeType.OVERDUE_INSTALLMENT.getValue().equals(e.getChargeTimeType()) && e.isLoanCharge()).findFirst();
+
+        if (optPenaltyCharge.isEmpty()) {
+            return list;
+        }
+        final Charge penaltyCharge = optPenaltyCharge.get();
+
         final Long penaltyWaitPeriod = configurationDomainService.retrievePenaltyWaitPeriod();
         final boolean backdatePenalties = configurationDomainService.isBackdatePenaltiesEnabled();
 
@@ -1657,16 +1666,9 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService, Loa
                 if (!backdatePenalties && !isDueToday) {
                     continue;
                 }
-                Optional<Charge> penaltyCharge = loan.getLoanProduct().getCharges().stream()
-                        .filter((e) -> ChargeTimeType.OVERDUE_INSTALLMENT.getValue().equals(e.getChargeTimeType()) && e.isLoanCharge())
-                        .findFirst();
-
-                if (penaltyCharge.isEmpty()) {
-                    continue;
-                }
 
-                list.add(new OverdueLoanScheduleData(loan.getId(), penaltyCharge.get().getId(),
-                        DateUtils.DEFAULT_DATE_FORMATTER.format(installment.getDueDate()), penaltyCharge.get().getAmount(),
+                list.add(new OverdueLoanScheduleData(loan.getId(), penaltyCharge.getId(),
+                        DateUtils.DEFAULT_DATE_FORMATTER.format(installment.getDueDate()), penaltyCharge.getAmount(),
                         DateUtils.DEFAULT_DATE_FORMAT, Locale.ENGLISH.toLanguageTag(),
                         installment.getPrincipalOutstanding(loan.getCurrency()).getAmount(),
                         installment.getInterestOutstanding(loan.getCurrency()).getAmount(), installment.getInstallmentNumber()));
diff --git a/fineract-provider/src/test/java/org/apache/fineract/cob/loan/ApplyChargeToOverdueLoansBusinessStepTest.java b/fineract-provider/src/test/java/org/apache/fineract/cob/loan/ApplyChargeToOverdueLoansBusinessStepTest.java
new file mode 100644
index 000000000..b9d51d050
--- /dev/null
+++ b/fineract-provider/src/test/java/org/apache/fineract/cob/loan/ApplyChargeToOverdueLoansBusinessStepTest.java
@@ -0,0 +1,80 @@
+/**
+ * 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.cob.loan;
+
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.mockito.Mockito.anyCollection;
+import static org.mockito.Mockito.anyLong;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import java.util.Collections;
+import java.util.List;
+import org.apache.fineract.portfolio.loanaccount.domain.Loan;
+import org.apache.fineract.portfolio.loanaccount.loanschedule.data.OverdueLoanScheduleData;
+import org.apache.fineract.portfolio.loanaccount.service.LoanChargeWritePlatformService;
+import org.apache.fineract.portfolio.loanaccount.service.LoanReadPlatformService;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+public class ApplyChargeToOverdueLoansBusinessStepTest {
+
+    private LoanReadPlatformService loanReadPlatformService;
+    private LoanChargeWritePlatformService loanChargeWritePlatformService;
+    private ApplyChargeToOverdueLoansBusinessStep applyChargeToOverdueLoansBusinessStep;
+
+    @BeforeEach
+    public void setUp() {
+        loanReadPlatformService = mock(LoanReadPlatformService.class);
+        loanChargeWritePlatformService = mock(LoanChargeWritePlatformService.class);
+        applyChargeToOverdueLoansBusinessStep = new ApplyChargeToOverdueLoansBusinessStep(loanReadPlatformService,
+                loanChargeWritePlatformService);
+    }
+
+    @Test
+    public void testExecute_WhenNoOverdueInstallments_ShouldNotApplyCharges() {
+        Loan loan = mock(Loan.class);
+
+        when(loanReadPlatformService.retrieveAllOverdueInstallmentsForLoan(loan)).thenReturn(Collections.emptyList());
+
+        Loan result = applyChargeToOverdueLoansBusinessStep.execute(loan);
+
+        assertNotNull(result);
+        verify(loanChargeWritePlatformService, never()).applyOverdueChargesForLoan(anyLong(), anyCollection());
+    }
+
+    @Test
+    public void testExecute_WhenOverdueInstallmentsExist_ShouldApplyCharges() {
+        Long testId = 1L;
+        Loan loan = mock(Loan.class);
+        OverdueLoanScheduleData overdueData = mock(OverdueLoanScheduleData.class);
+        List<OverdueLoanScheduleData> overdueList = Collections.singletonList(overdueData);
+
+        when(loan.getId()).thenReturn(testId);
+        when(loanReadPlatformService.retrieveAllOverdueInstallmentsForLoan(loan)).thenReturn(overdueList);
+
+        Loan result = applyChargeToOverdueLoansBusinessStep.execute(loan);
+
+        assertNotNull(result);
+        verify(loanChargeWritePlatformService, times(1)).applyOverdueChargesForLoan(testId, overdueList);
+    }
+}
diff --git a/fineract-provider/src/test/java/org/apache/fineract/portfolio/loanaccount/jobs/applychargetooverdueloaninstallment/ApplyChargeToOverdueLoanInstallmentTaskletTest.java b/fineract-provider/src/test/java/org/apache/fineract/portfolio/loanaccount/jobs/applychargetooverdueloaninstallment/ApplyChargeToOverdueLoanInstallmentTaskletTest.java
new file mode 100644
index 000000000..dc384d5eb
--- /dev/null
+++ b/fineract-provider/src/test/java/org/apache/fineract/portfolio/loanaccount/jobs/applychargetooverdueloaninstallment/ApplyChargeToOverdueLoanInstallmentTaskletTest.java
@@ -0,0 +1,86 @@
+/**
+ * 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.jobs.applychargetooverdueloaninstallment;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.mockito.Mockito.anyBoolean;
+import static org.mockito.Mockito.anyCollection;
+import static org.mockito.Mockito.anyLong;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import java.util.Collections;
+import org.apache.fineract.infrastructure.configuration.domain.ConfigurationDomainService;
+import org.apache.fineract.portfolio.loanaccount.loanschedule.data.OverdueLoanScheduleData;
+import org.apache.fineract.portfolio.loanaccount.service.LoanChargeWritePlatformService;
+import org.apache.fineract.portfolio.loanaccount.service.LoanReadPlatformService;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.springframework.batch.core.StepContribution;
+import org.springframework.batch.core.scope.context.ChunkContext;
+import org.springframework.batch.repeat.RepeatStatus;
+
+public class ApplyChargeToOverdueLoanInstallmentTaskletTest {
+
+    private LoanReadPlatformService loanReadPlatformService;
+    private LoanChargeWritePlatformService loanChargeWritePlatformService;
+    private StepContribution contribution;
+    private ChunkContext chunkContext;
+    private ApplyChargeToOverdueLoanInstallmentTasklet tasklet;
+
+    @BeforeEach
+    public void setUp() {
+        loanReadPlatformService = mock(LoanReadPlatformService.class);
+        loanChargeWritePlatformService = mock(LoanChargeWritePlatformService.class);
+        ConfigurationDomainService configurationDomainService = mock(ConfigurationDomainService.class);
+        contribution = mock(StepContribution.class);
+        chunkContext = mock(ChunkContext.class);
+
+        tasklet = new ApplyChargeToOverdueLoanInstallmentTasklet(configurationDomainService, loanReadPlatformService,
+                loanChargeWritePlatformService);
+    }
+
+    @Test
+    public void testExecute_WhenNoOverdueInstallments_ShouldNotApplyCharges() throws Exception {
+        when(loanReadPlatformService.retrieveAllLoansWithOverdueInstallments(anyLong(), anyBoolean()))
+                .thenReturn(Collections.emptyList());
+
+        RepeatStatus status = tasklet.execute(contribution, chunkContext);
+
+        assertEquals(RepeatStatus.FINISHED, status);
+
+        verify(loanChargeWritePlatformService, never()).applyOverdueChargesForLoan(anyLong(), anyCollection());
+    }
+
+    @Test
+    public void testExecute_WhenOverdueInstallmentsExist_ShouldApplyCharges() throws Exception {
+        OverdueLoanScheduleData overdueData = mock(OverdueLoanScheduleData.class);
+        when(loanReadPlatformService.retrieveAllLoansWithOverdueInstallments(anyLong(), anyBoolean()))
+                .thenReturn(Collections.singletonList(overdueData));
+
+        RepeatStatus status = tasklet.execute(contribution, chunkContext);
+
+        assertEquals(RepeatStatus.FINISHED, status);
+
+        verify(loanChargeWritePlatformService, times(1)).applyOverdueChargesForLoan(anyLong(), anyCollection());
+    }
+}
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 2c593c0c5..2598ead68 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
@@ -424,7 +424,7 @@ public class LoanTransactionAccrualActivityPostingTest extends BaseLoanIntegrati
             verifyTransactions(loanId.get(), //
                     transaction(1000.0, "Disbursement", disbursementDay, 1000.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0),
                     transaction(20.0, "Accrual", "01 February 2023", 0, 0, 0, 0, 20, 0.0, 0.0),
-                    transaction(50.0, "Repayment", "10 January 2023", 950, 50, 0, 0, 0, 0.0, 0.0),
+                    transaction(50.0, "Repayment", "10 January 2023", 970, 30, 0, 0, 20, 0.0, 0.0),
                     transaction(20.0, "Accrual Activity", "01 February 2023", 0, 0, 0.0, 0.0, 20.0, 0.0, 0.0));
 
         });