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 2023/04/12 13:03:18 UTC

[fineract] branch develop updated: [FINERACT-1678] Exclude loan IDs from execution context

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 ace078602 [FINERACT-1678] Exclude loan IDs from execution context
ace078602 is described below

commit ace07860240217c24d40c717026446e9b1f990b2
Author: taskain7 <ta...@gmail.com>
AuthorDate: Wed Apr 12 06:55:50 2023 +0200

    [FINERACT-1678] Exclude loan IDs from execution context
---
 .../LoanCOBParameter.java}                         |  23 +++--
 .../fineract/cob/loan/AbstractLoanItemReader.java  |   2 +-
 .../fineract/cob/loan/ApplyLoanLockTasklet.java    |  18 +++-
 .../fineract/cob/loan/FetchAndLockLoanTasklet.java | 112 ---------------------
 .../fineract/cob/loan/InlineCOBLoanItemReader.java |   2 +-
 .../InlineLoanCOBBuildExecutionContextTasklet.java |   3 +-
 .../apache/fineract/cob/loan/LoanCOBConstant.java  |   3 +-
 .../cob/loan/LoanCOBManagerConfiguration.java      |  33 +++---
 .../fineract/cob/loan/LoanCOBPartitioner.java      |  37 ++++---
 .../cob/loan/LoanCOBWorkerConfiguration.java       |   2 +-
 .../fineract/cob/loan/LoanIdParameterTasklet.java  |  56 +++++++++++
 .../fineract/cob/loan/LoanInlineCOBConfig.java     |   4 +-
 .../apache/fineract/cob/loan/LoanItemReader.java   |  18 +++-
 .../apache/fineract/cob/loan/LockLoanTasklet.java  |  78 ++++++++++++++
 .../RetrieveAllNonClosedLoanIdServiceImpl.java     |   5 +-
 .../fineract/cob/loan/RetrieveLoanIdService.java   |   3 +-
 .../cob/loan/StayedLockedLoansTasklet.java         |   3 +-
 .../loanaccount/domain/LoanRepository.java         |  21 ++--
 .../loan/ApplyLoanLockTaskletStepDefinitions.java  |  13 ++-
 .../cob/loan/FetchAndLockLoanStepDefinitions.java  | 109 +++++++++-----------
 .../loan/LoanCOBPartitionerStepDefinitions.java    |  29 ++++--
 .../cob/loan/LoanItemReaderStepDefinitions.java    |  33 ++++--
 .../features/cob/loan/cob.loan.reader.feature      |   1 -
 23 files changed, 348 insertions(+), 260 deletions(-)

diff --git a/fineract-provider/src/main/java/org/apache/fineract/cob/loan/RetrieveLoanIdService.java b/fineract-provider/src/main/java/org/apache/fineract/cob/data/LoanCOBParameter.java
similarity index 61%
copy from fineract-provider/src/main/java/org/apache/fineract/cob/loan/RetrieveLoanIdService.java
copy to fineract-provider/src/main/java/org/apache/fineract/cob/data/LoanCOBParameter.java
index f5a9c7b52..b6e2b5ed9 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/cob/loan/RetrieveLoanIdService.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/cob/data/LoanCOBParameter.java
@@ -16,18 +16,19 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.fineract.cob.loan;
+package org.apache.fineract.cob.data;
 
-import java.time.LocalDate;
-import java.util.List;
-import org.apache.fineract.cob.data.LoanIdAndLastClosedBusinessDate;
+import com.fasterxml.jackson.annotation.JsonTypeInfo;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
 
-public interface RetrieveLoanIdService {
-
-    List<Long> retrieveLoanIdsNDaysBehind(Long numberOfDays, LocalDate businessDate);
-
-    List<LoanIdAndLastClosedBusinessDate> retrieveLoanIdsBehindDateOrNull(LocalDate businessDate, List<Long> loanIds);
-
-    List<LoanIdAndLastClosedBusinessDate> retrieveLoanIdsOldestCobProcessed(LocalDate businessDate);
+@AllArgsConstructor
+@Getter
+@NoArgsConstructor
+@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS)
+public class LoanCOBParameter {
 
+    private Long minLoanId;
+    private Long maxLoanId;
 }
diff --git a/fineract-provider/src/main/java/org/apache/fineract/cob/loan/AbstractLoanItemReader.java b/fineract-provider/src/main/java/org/apache/fineract/cob/loan/AbstractLoanItemReader.java
index 13ca8d34c..c92fdbf8d 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/cob/loan/AbstractLoanItemReader.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/cob/loan/AbstractLoanItemReader.java
@@ -37,7 +37,7 @@ import org.springframework.batch.item.ItemReader;
 @RequiredArgsConstructor
 public abstract class AbstractLoanItemReader implements ItemReader<Loan> {
 
-    private final LoanRepository loanRepository;
+    protected final LoanRepository loanRepository;
 
     @Setter(AccessLevel.PROTECTED)
     private List<Long> alreadyLockedOrProcessedAccounts;
diff --git a/fineract-provider/src/main/java/org/apache/fineract/cob/loan/ApplyLoanLockTasklet.java b/fineract-provider/src/main/java/org/apache/fineract/cob/loan/ApplyLoanLockTasklet.java
index 66049cf9b..06a23060b 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/cob/loan/ApplyLoanLockTasklet.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/cob/loan/ApplyLoanLockTasklet.java
@@ -20,17 +20,23 @@ package org.apache.fineract.cob.loan;
 
 import com.google.common.collect.Lists;
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.List;
 import java.util.Map;
+import java.util.Objects;
 import java.util.function.Function;
 import java.util.stream.Collectors;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
+import org.apache.fineract.cob.data.LoanCOBParameter;
 import org.apache.fineract.cob.domain.LoanAccountLock;
 import org.apache.fineract.cob.domain.LoanAccountLockRepository;
 import org.apache.fineract.cob.domain.LockOwner;
+import org.apache.fineract.infrastructure.businessdate.domain.BusinessDateType;
 import org.apache.fineract.infrastructure.core.config.FineractProperties;
 import org.apache.fineract.infrastructure.core.service.DateUtils;
+import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanRepository;
 import org.jetbrains.annotations.NotNull;
 import org.springframework.batch.core.StepContribution;
 import org.springframework.batch.core.scope.context.ChunkContext;
@@ -46,11 +52,21 @@ public class ApplyLoanLockTasklet implements Tasklet {
     private final LoanAccountLockRepository accountLockRepository;
     private final FineractProperties fineractProperties;
     private final JdbcTemplate jdbcTemplate;
+    private final LoanRepository loanRepository;
 
     @Override
     public RepeatStatus execute(@NotNull StepContribution contribution, @NotNull ChunkContext chunkContext) throws Exception {
         ExecutionContext executionContext = contribution.getStepExecution().getExecutionContext();
-        List<Long> loanIds = (List<Long>) executionContext.get(LoanCOBConstant.LOAN_IDS);
+        LoanCOBParameter loanCOBParameter = (LoanCOBParameter) executionContext.get(LoanCOBConstant.LOAN_COB_PARAMETER);
+        List<Long> loanIds;
+        if (Objects.isNull(loanCOBParameter)
+                || (Objects.isNull(loanCOBParameter.getMinLoanId()) && Objects.isNull(loanCOBParameter.getMaxLoanId()))
+                || (loanCOBParameter.getMinLoanId().equals(0L) && loanCOBParameter.getMaxLoanId().equals(0L))) {
+            loanIds = Collections.emptyList();
+        } else {
+            loanIds = new ArrayList<>(loanRepository.findAllNonClosedLoansBehindOrNullByMinAndMaxLoanId(loanCOBParameter.getMinLoanId(),
+                    loanCOBParameter.getMaxLoanId(), ThreadLocalContextUtil.getBusinessDateByType(BusinessDateType.COB_DATE)));
+        }
         List<List<Long>> loanIdPartitions = Lists.partition(loanIds, fineractProperties.getQuery().getInClauseParameterSizeLimit());
         List<LoanAccountLock> accountLocks = new ArrayList<>();
         loanIdPartitions.forEach(loanIdPartition -> accountLocks.addAll(accountLockRepository.findAllByLoanIdIn(loanIdPartition)));
diff --git a/fineract-provider/src/main/java/org/apache/fineract/cob/loan/FetchAndLockLoanTasklet.java b/fineract-provider/src/main/java/org/apache/fineract/cob/loan/FetchAndLockLoanTasklet.java
deleted file mode 100644
index e7c758677..000000000
--- a/fineract-provider/src/main/java/org/apache/fineract/cob/loan/FetchAndLockLoanTasklet.java
+++ /dev/null
@@ -1,112 +0,0 @@
-/**
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.fineract.cob.loan;
-
-import com.google.common.collect.Lists;
-import java.time.LocalDate;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-import java.util.Objects;
-import lombok.RequiredArgsConstructor;
-import lombok.extern.slf4j.Slf4j;
-import org.apache.fineract.cob.domain.LoanAccountLock;
-import org.apache.fineract.cob.domain.LoanAccountLockRepository;
-import org.apache.fineract.cob.domain.LockOwner;
-import org.apache.fineract.infrastructure.businessdate.domain.BusinessDateType;
-import org.apache.fineract.infrastructure.core.config.FineractProperties;
-import org.apache.fineract.infrastructure.core.service.DateUtils;
-import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil;
-import org.jetbrains.annotations.NotNull;
-import org.springframework.batch.core.StepContribution;
-import org.springframework.batch.core.scope.context.ChunkContext;
-import org.springframework.batch.core.step.tasklet.Tasklet;
-import org.springframework.batch.repeat.RepeatStatus;
-import org.springframework.jdbc.core.JdbcTemplate;
-
-@Slf4j
-@RequiredArgsConstructor
-public class FetchAndLockLoanTasklet implements Tasklet {
-
-    private static final Long NUMBER_OF_DAYS_BEHIND = 1L;
-
-    private final LoanAccountLockRepository loanAccountLockRepository;
-
-    private final RetrieveLoanIdService retrieveLoanIdService;
-
-    private final FineractProperties fineractProperties;
-
-    private final JdbcTemplate jdbcTemplate;
-
-    @Override
-    public RepeatStatus execute(@NotNull StepContribution contribution, @NotNull ChunkContext chunkContext) throws Exception {
-        String businessDateParameter = (String) contribution.getStepExecution().getJobExecution().getExecutionContext()
-                .get(LoanCOBConstant.BUSINESS_DATE_PARAMETER_NAME);
-        LocalDate businessDate = LocalDate.parse(Objects.requireNonNull(businessDateParameter));
-        List<Long> allNonClosedLoanIds = retrieveLoanIdService.retrieveLoanIdsNDaysBehind(NUMBER_OF_DAYS_BEHIND, businessDate);
-        if (allNonClosedLoanIds.isEmpty()) {
-            contribution.getStepExecution().getJobExecution().getExecutionContext().put(LoanCOBConstant.LOAN_IDS, Collections.emptyList());
-            return RepeatStatus.FINISHED;
-        }
-        List<Long> remainingIds = new ArrayList<>(allNonClosedLoanIds);
-
-        List<List<Long>> remainingIdPartitions = Lists.partition(remainingIds, getInClauseParameterSizeLimit());
-        List<LoanAccountLock> loanAccountLocks = new ArrayList<>();
-        remainingIdPartitions.forEach(
-                remainingIdPartition -> loanAccountLocks.addAll(loanAccountLockRepository.findAllByLoanIdIn(remainingIdPartition)));
-
-        List<Long> alreadySoftLockedAccounts = loanAccountLocks.stream()
-                .filter(e -> LockOwner.LOAN_COB_PARTITIONING.equals(e.getLockOwner())).map(LoanAccountLock::getLoanId).toList();
-        List<Long> alreadyMarkedForInlineCOBLockedAccounts = loanAccountLocks.stream()
-                .filter(e -> LockOwner.LOAN_INLINE_COB_PROCESSING.equals(e.getLockOwner())).map(LoanAccountLock::getLoanId).toList();
-        List<Long> alreadyMarkedForChunkProcessingLockedAccounts = loanAccountLocks.stream()
-                .filter(e -> LockOwner.LOAN_COB_CHUNK_PROCESSING.equals(e.getLockOwner())).map(LoanAccountLock::getLoanId).toList();
-
-        // Remove already hard locked accounts
-        remainingIds.removeAll(alreadyMarkedForChunkProcessingLockedAccounts);
-        remainingIds.removeAll(alreadyMarkedForInlineCOBLockedAccounts);
-
-        List<Long> lockableLoanAccounts = new ArrayList<>(remainingIds);
-        lockableLoanAccounts.removeAll(alreadySoftLockedAccounts);
-
-        applySoftLock(lockableLoanAccounts);
-
-        contribution.getStepExecution().getJobExecution().getExecutionContext().put(LoanCOBConstant.LOAN_IDS, remainingIds);
-
-        return RepeatStatus.FINISHED;
-    }
-
-    private void applySoftLock(List<Long> accountsToLock) {
-        LocalDate cobBusinessDate = ThreadLocalContextUtil.getBusinessDateByType(BusinessDateType.COB_DATE);
-        jdbcTemplate.batchUpdate("""
-                    INSERT INTO m_loan_account_locks (loan_id, version, lock_owner, lock_placed_on, lock_placed_on_cob_business_date)
-                    VALUES (?, ?, ?, ?, ?)
-                """, accountsToLock, getInClauseParameterSizeLimit(), (ps, id) -> {
-            ps.setLong(1, id);
-            ps.setLong(2, 1);
-            ps.setString(3, LockOwner.LOAN_COB_PARTITIONING.name());
-            ps.setObject(4, DateUtils.getOffsetDateTimeOfTenant());
-            ps.setObject(5, cobBusinessDate);
-        });
-    }
-
-    private int getInClauseParameterSizeLimit() {
-        return fineractProperties.getQuery().getInClauseParameterSizeLimit();
-    }
-}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/cob/loan/InlineCOBLoanItemReader.java b/fineract-provider/src/main/java/org/apache/fineract/cob/loan/InlineCOBLoanItemReader.java
index 4da7b1398..deaa9aaee 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/cob/loan/InlineCOBLoanItemReader.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/cob/loan/InlineCOBLoanItemReader.java
@@ -36,7 +36,7 @@ public class InlineCOBLoanItemReader extends AbstractLoanItemReader {
     @SuppressWarnings({ "unchecked" })
     public void beforeStep(@NotNull StepExecution stepExecution) {
         ExecutionContext executionContext = stepExecution.getJobExecution().getExecutionContext();
-        List<Long> loanIds = (List<Long>) executionContext.get(LoanCOBConstant.LOAN_IDS);
+        List<Long> loanIds = (List<Long>) executionContext.get(LoanCOBConstant.LOAN_COB_PARAMETER);
         setRemainingData(new ArrayList<>(loanIds));
     }
 }
diff --git a/fineract-provider/src/main/java/org/apache/fineract/cob/loan/InlineLoanCOBBuildExecutionContextTasklet.java b/fineract-provider/src/main/java/org/apache/fineract/cob/loan/InlineLoanCOBBuildExecutionContextTasklet.java
index 276d7c702..3eb01ef41 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/cob/loan/InlineLoanCOBBuildExecutionContextTasklet.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/cob/loan/InlineLoanCOBBuildExecutionContextTasklet.java
@@ -58,7 +58,8 @@ public class InlineLoanCOBBuildExecutionContextTasklet implements Tasklet {
         ThreadLocalContextUtil.setActionContext(ActionContext.COB);
         Set<BusinessStepNameAndOrder> cobBusinessSteps = cobBusinessStepService.getCOBBusinessSteps(LoanCOBBusinessStep.class,
                 LoanCOBConstant.LOAN_COB_JOB_NAME);
-        contribution.getStepExecution().getExecutionContext().put(LoanCOBConstant.LOAN_IDS, getLoanIdsFromJobParameters(chunkContext));
+        contribution.getStepExecution().getExecutionContext().put(LoanCOBConstant.LOAN_COB_PARAMETER,
+                getLoanIdsFromJobParameters(chunkContext));
         contribution.getStepExecution().getExecutionContext().put(LoanCOBConstant.BUSINESS_STEPS, cobBusinessSteps);
         String businessDateString = getBusinessDateFromJobParameters(chunkContext);
         contribution.getStepExecution().getExecutionContext().put(LoanCOBConstant.BUSINESS_DATE_PARAMETER_NAME, businessDateString);
diff --git a/fineract-provider/src/main/java/org/apache/fineract/cob/loan/LoanCOBConstant.java b/fineract-provider/src/main/java/org/apache/fineract/cob/loan/LoanCOBConstant.java
index 35563e02b..d7e5fedd7 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/cob/loan/LoanCOBConstant.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/cob/loan/LoanCOBConstant.java
@@ -23,11 +23,10 @@ public final class LoanCOBConstant {
     public static final String JOB_NAME = "LOAN_COB";
     public static final String JOB_HUMAN_READABLE_NAME = "Loan COB";
     public static final String LOAN_COB_JOB_NAME = "LOAN_CLOSE_OF_BUSINESS";
-    public static final String LOAN_IDS = "loanIds";
+    public static final String LOAN_COB_PARAMETER = "loanCobParameter";
     public static final String BUSINESS_STEPS = "businessSteps";
     public static final String LOAN_COB_WORKER_STEP = "loanCOBWorkerStep";
 
-    public static final String ALREADY_LOCKED_BY_INLINE_COB_OR_PROCESSED_LOAN_IDS = "alreadyLockedOrProcessedLoanIds";
     public static final String INLINE_LOAN_COB_JOB_NAME = "INLINE_LOAN_COB";
     public static final String BUSINESS_DATE_PARAMETER_NAME = "BusinessDate";
     public static final String IS_CATCH_UP_PARAMETER_NAME = "IS_CATCH_UP";
diff --git a/fineract-provider/src/main/java/org/apache/fineract/cob/loan/LoanCOBManagerConfiguration.java b/fineract-provider/src/main/java/org/apache/fineract/cob/loan/LoanCOBManagerConfiguration.java
index 27a9cf91c..bb944c509 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/cob/loan/LoanCOBManagerConfiguration.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/cob/loan/LoanCOBManagerConfiguration.java
@@ -18,12 +18,10 @@
  */
 package org.apache.fineract.cob.loan;
 
-import java.util.List;
 import org.apache.fineract.cob.COBBusinessStepService;
 import org.apache.fineract.cob.common.CustomJobParameterResolver;
-import org.apache.fineract.cob.domain.LoanAccountLockRepository;
+import org.apache.fineract.cob.data.LoanCOBParameter;
 import org.apache.fineract.cob.listener.COBExecutionListenerRunner;
-import org.apache.fineract.infrastructure.core.config.FineractProperties;
 import org.apache.fineract.infrastructure.event.business.service.BusinessEventNotifierService;
 import org.apache.fineract.infrastructure.jobs.service.JobName;
 import org.apache.fineract.infrastructure.springbatch.PropertyService;
@@ -74,23 +72,19 @@ public class LoanCOBManagerConfiguration {
     @Autowired
     private RetrieveLoanIdService retrieveLoanIdService;
     @Autowired
-    private LoanAccountLockRepository accountLockRepository;
-    @Autowired
     private BusinessEventNotifierService businessEventNotifierService;
     @Autowired
     private CustomJobParameterResolver customJobParameterResolver;
     @Autowired
     private LoanRepository loanRepository;
-    @Autowired
-    private FineractProperties fineractProperties;
 
     @Autowired
     private JdbcTemplate jdbcTemplate;
 
     @Bean
     @JobScope
-    public LoanCOBPartitioner partitioner(@Value("#{jobExecutionContext['loanIds']}") List<Long> loanIds) {
-        return new LoanCOBPartitioner(propertyService, cobBusinessStepService, jobOperator, jobExplorer, loanIds);
+    public LoanCOBPartitioner partitioner(@Value("#{jobExecutionContext['loanIds']}") LoanCOBParameter minAndMaxLoanId) {
+        return new LoanCOBPartitioner(propertyService, cobBusinessStepService, jobOperator, jobExplorer, minAndMaxLoanId);
     }
 
     @Bean
@@ -100,8 +94,13 @@ public class LoanCOBManagerConfiguration {
     }
 
     @Bean
-    public Step fetchAndLockStep() {
-        return localStepBuilderFactory.get("Fetch and Lock loan accounts - Step").tasklet(fetchAndLockLoanTasklet()).build();
+    public Step loanIdParameterStep() {
+        return localStepBuilderFactory.get("Set loan ID parameter - Step").tasklet(loanIdParameterTasklet()).build();
+    }
+
+    @Bean
+    public Step lockStep() {
+        return localStepBuilderFactory.get("Lock loan accounts - Step").tasklet(lockLoanTasklet()).build();
     }
 
     @Bean
@@ -117,8 +116,14 @@ public class LoanCOBManagerConfiguration {
 
     @Bean
     @JobScope
-    public FetchAndLockLoanTasklet fetchAndLockLoanTasklet() {
-        return new FetchAndLockLoanTasklet(accountLockRepository, retrieveLoanIdService, fineractProperties, jdbcTemplate);
+    public LoanIdParameterTasklet loanIdParameterTasklet() {
+        return new LoanIdParameterTasklet(retrieveLoanIdService);
+    }
+
+    @Bean
+    @JobScope
+    public LockLoanTasklet lockLoanTasklet() {
+        return new LockLoanTasklet(jdbcTemplate);
     }
 
     @Bean
@@ -138,7 +143,7 @@ public class LoanCOBManagerConfiguration {
         return jobBuilderFactory.get(JobName.LOAN_COB.name()) //
                 .listener(new COBExecutionListenerRunner(applicationContext, JobName.LOAN_COB.name())) //
                 .start(resolveCustomJobParametersStep()) //
-                .next(fetchAndLockStep()).next(loanCOBStep()).next(stayedLockedStep()) //
+                .next(loanIdParameterStep()).next(lockStep()).next(loanCOBStep()).next(stayedLockedStep()) //
                 .incrementer(new RunIdIncrementer()) //
                 .build();
     }
diff --git a/fineract-provider/src/main/java/org/apache/fineract/cob/loan/LoanCOBPartitioner.java b/fineract-provider/src/main/java/org/apache/fineract/cob/loan/LoanCOBPartitioner.java
index 2a851130a..25733d7cd 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/cob/loan/LoanCOBPartitioner.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/cob/loan/LoanCOBPartitioner.java
@@ -18,15 +18,15 @@
  */
 package org.apache.fineract.cob.loan;
 
-import java.util.ArrayList;
 import java.util.HashMap;
-import java.util.List;
 import java.util.Map;
+import java.util.Objects;
 import java.util.Set;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
 import org.apache.fineract.cob.COBBusinessStepService;
 import org.apache.fineract.cob.data.BusinessStepNameAndOrder;
+import org.apache.fineract.cob.data.LoanCOBParameter;
 import org.apache.fineract.infrastructure.jobs.service.JobName;
 import org.apache.fineract.infrastructure.springbatch.PropertyService;
 import org.jetbrains.annotations.NotNull;
@@ -49,7 +49,7 @@ public class LoanCOBPartitioner implements Partitioner {
     private final JobOperator jobOperator;
     private final JobExplorer jobExplorer;
 
-    private final List<Long> loanIds;
+    private final LoanCOBParameter minAndMaxLoanId;
 
     @NotNull
     @Override
@@ -67,19 +67,25 @@ public class LoanCOBPartitioner implements Partitioner {
             return Map.of();
         }
         int partitionIndex = 1;
-        int remainingSpace = 0;
         createNewPartition(partitions, partitionIndex, cobBusinessSteps);
-        for (Long loanId : loanIds) {
-            if (remainingSpace == partitionSize) {
-                partitionIndex++;
-                createNewPartition(partitions, partitionIndex, cobBusinessSteps);
-                remainingSpace = 0;
-            }
-            String key = PARTITION_PREFIX + partitionIndex;
-            ExecutionContext executionContext = partitions.get(key);
-            List<Long> data = (List<Long>) executionContext.get(LoanCOBConstant.LOAN_IDS);
-            data.add(loanId);
-            remainingSpace++;
+        if (!Objects.isNull(minAndMaxLoanId)) {
+            long remainingLoanIdCount = minAndMaxLoanId.getMaxLoanId() - minAndMaxLoanId.getMinLoanId() + 1;
+            long startLoanId = minAndMaxLoanId.getMinLoanId();
+            long endLoanId;
+            do {
+                String key = PARTITION_PREFIX + partitionIndex;
+                ExecutionContext executionContext = partitions.get(key);
+                if (remainingLoanIdCount > partitionSize) {
+                    endLoanId = startLoanId + partitionSize - 1;
+                    partitionIndex++;
+                    createNewPartition(partitions, partitionIndex, cobBusinessSteps);
+                } else {
+                    endLoanId = minAndMaxLoanId.getMaxLoanId();
+                }
+                executionContext.put(LoanCOBConstant.LOAN_COB_PARAMETER, new LoanCOBParameter(startLoanId, endLoanId));
+                startLoanId = startLoanId + partitionSize;
+                remainingLoanIdCount = minAndMaxLoanId.getMaxLoanId() - endLoanId;
+            } while (remainingLoanIdCount > 0);
         }
         return partitions;
     }
@@ -87,7 +93,6 @@ public class LoanCOBPartitioner implements Partitioner {
     private void createNewPartition(Map<String, ExecutionContext> partitions, int partitionIndex,
             Set<BusinessStepNameAndOrder> cobBusinessSteps) {
         ExecutionContext executionContext = new ExecutionContext();
-        executionContext.put(LoanCOBConstant.LOAN_IDS, new ArrayList<Long>());
         executionContext.put(LoanCOBConstant.BUSINESS_STEPS, cobBusinessSteps);
         executionContext.put("partition", PARTITION_PREFIX + partitionIndex);
         partitions.put(PARTITION_PREFIX + partitionIndex, executionContext);
diff --git a/fineract-provider/src/main/java/org/apache/fineract/cob/loan/LoanCOBWorkerConfiguration.java b/fineract-provider/src/main/java/org/apache/fineract/cob/loan/LoanCOBWorkerConfiguration.java
index 3cc751867..252fb9dce 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/cob/loan/LoanCOBWorkerConfiguration.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/cob/loan/LoanCOBWorkerConfiguration.java
@@ -123,7 +123,7 @@ public class LoanCOBWorkerConfiguration {
 
     @Bean
     public ApplyLoanLockTasklet applyLock() {
-        return new ApplyLoanLockTasklet(accountLockRepository, fineractProperties, jdbcTemplate);
+        return new ApplyLoanLockTasklet(accountLockRepository, fineractProperties, jdbcTemplate, loanRepository);
     }
 
     @Bean
diff --git a/fineract-provider/src/main/java/org/apache/fineract/cob/loan/LoanIdParameterTasklet.java b/fineract-provider/src/main/java/org/apache/fineract/cob/loan/LoanIdParameterTasklet.java
new file mode 100644
index 000000000..a463503b9
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/cob/loan/LoanIdParameterTasklet.java
@@ -0,0 +1,56 @@
+/**
+ * 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 java.time.LocalDate;
+import java.util.Objects;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.fineract.cob.data.LoanCOBParameter;
+import org.springframework.batch.core.StepContribution;
+import org.springframework.batch.core.scope.context.ChunkContext;
+import org.springframework.batch.core.step.tasklet.Tasklet;
+import org.springframework.batch.repeat.RepeatStatus;
+
+@Slf4j
+@RequiredArgsConstructor
+public class LoanIdParameterTasklet implements Tasklet {
+
+    private static final Long NUMBER_OF_DAYS_BEHIND = 1L;
+
+    private final RetrieveLoanIdService retrieveLoanIdService;
+
+    @Override
+    public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception {
+        String businessDateParameter = (String) contribution.getStepExecution().getJobExecution().getExecutionContext()
+                .get(LoanCOBConstant.BUSINESS_DATE_PARAMETER_NAME);
+        LocalDate businessDate = LocalDate.parse(Objects.requireNonNull(businessDateParameter));
+        LoanCOBParameter minAndMaxLoanId = retrieveLoanIdService.retrieveMinAndMaxLoanIdsNDaysBehind(NUMBER_OF_DAYS_BEHIND, businessDate);
+        if (Objects.isNull(minAndMaxLoanId)
+                || (Objects.isNull(minAndMaxLoanId.getMinLoanId()) && Objects.isNull(minAndMaxLoanId.getMaxLoanId()))) {
+            contribution.getStepExecution().getJobExecution().getExecutionContext().put(LoanCOBConstant.LOAN_COB_PARAMETER,
+                    new LoanCOBParameter(0L, 0L));
+            return RepeatStatus.FINISHED;
+        }
+
+        contribution.getStepExecution().getJobExecution().getExecutionContext().put(LoanCOBConstant.LOAN_COB_PARAMETER, minAndMaxLoanId);
+
+        return RepeatStatus.FINISHED;
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/cob/loan/LoanInlineCOBConfig.java b/fineract-provider/src/main/java/org/apache/fineract/cob/loan/LoanInlineCOBConfig.java
index 64dcecfcb..78a9aa28d 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/cob/loan/LoanInlineCOBConfig.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/cob/loan/LoanInlineCOBConfig.java
@@ -121,8 +121,8 @@ public class LoanInlineCOBConfig {
     @Bean
     public ExecutionContextPromotionListener inlineCobPromotionListener() {
         ExecutionContextPromotionListener listener = new ExecutionContextPromotionListener();
-        listener.setKeys(
-                new String[] { LoanCOBConstant.LOAN_IDS, LoanCOBConstant.BUSINESS_STEPS, LoanCOBConstant.BUSINESS_DATE_PARAMETER_NAME });
+        listener.setKeys(new String[] { LoanCOBConstant.LOAN_COB_PARAMETER, LoanCOBConstant.BUSINESS_STEPS,
+                LoanCOBConstant.BUSINESS_DATE_PARAMETER_NAME });
         return listener;
     }
 }
diff --git a/fineract-provider/src/main/java/org/apache/fineract/cob/loan/LoanItemReader.java b/fineract-provider/src/main/java/org/apache/fineract/cob/loan/LoanItemReader.java
index ecb737e83..8dd6b3b2d 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/cob/loan/LoanItemReader.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/cob/loan/LoanItemReader.java
@@ -19,7 +19,12 @@
 package org.apache.fineract.cob.loan;
 
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.List;
+import java.util.Objects;
+import org.apache.fineract.cob.data.LoanCOBParameter;
+import org.apache.fineract.infrastructure.businessdate.domain.BusinessDateType;
+import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil;
 import org.apache.fineract.portfolio.loanaccount.domain.LoanRepository;
 import org.jetbrains.annotations.NotNull;
 import org.springframework.batch.core.StepExecution;
@@ -35,8 +40,17 @@ public class LoanItemReader extends AbstractLoanItemReader {
     @BeforeStep
     @SuppressWarnings({ "unchecked" })
     public void beforeStep(@NotNull StepExecution stepExecution) {
-        ExecutionContext executionContext = stepExecution.getExecutionContext();
-        List<Long> loanIds = (List<Long>) executionContext.get(LoanCOBConstant.LOAN_IDS);
+        ExecutionContext executionContext = stepExecution.getJobExecution().getExecutionContext();
+        LoanCOBParameter loanCOBParameter = (LoanCOBParameter) executionContext.get(LoanCOBConstant.LOAN_COB_PARAMETER);
+        List<Long> loanIds;
+        if (Objects.isNull(loanCOBParameter)
+                || (Objects.isNull(loanCOBParameter.getMinLoanId()) && Objects.isNull(loanCOBParameter.getMaxLoanId()))
+                || (loanCOBParameter.getMinLoanId().equals(0L) && loanCOBParameter.getMaxLoanId().equals(0L))) {
+            loanIds = Collections.emptyList();
+        } else {
+            loanIds = loanRepository.findAllNonClosedLoansBehindOrNullByMinAndMaxLoanId(loanCOBParameter.getMinLoanId(),
+                    loanCOBParameter.getMaxLoanId(), ThreadLocalContextUtil.getBusinessDateByType(BusinessDateType.COB_DATE));
+        }
         setRemainingData(new ArrayList<>(loanIds));
     }
 }
diff --git a/fineract-provider/src/main/java/org/apache/fineract/cob/loan/LockLoanTasklet.java b/fineract-provider/src/main/java/org/apache/fineract/cob/loan/LockLoanTasklet.java
new file mode 100644
index 000000000..efe944f65
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/cob/loan/LockLoanTasklet.java
@@ -0,0 +1,78 @@
+/**
+ * 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 java.time.LocalDate;
+import java.util.Objects;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.fineract.cob.data.LoanCOBParameter;
+import org.apache.fineract.cob.domain.LockOwner;
+import org.apache.fineract.infrastructure.businessdate.domain.BusinessDateType;
+import org.apache.fineract.infrastructure.core.service.DateUtils;
+import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil;
+import org.jetbrains.annotations.NotNull;
+import org.springframework.batch.core.StepContribution;
+import org.springframework.batch.core.scope.context.ChunkContext;
+import org.springframework.batch.core.step.tasklet.Tasklet;
+import org.springframework.batch.repeat.RepeatStatus;
+import org.springframework.jdbc.core.JdbcTemplate;
+
+@Slf4j
+@RequiredArgsConstructor
+public class LockLoanTasklet implements Tasklet {
+
+    private final JdbcTemplate jdbcTemplate;
+
+    @Override
+    public RepeatStatus execute(@NotNull StepContribution contribution, @NotNull ChunkContext chunkContext) throws Exception {
+        String businessDateParameter = (String) contribution.getStepExecution().getJobExecution().getExecutionContext()
+                .get(LoanCOBConstant.BUSINESS_DATE_PARAMETER_NAME);
+        LocalDate businessDate = LocalDate.parse(Objects.requireNonNull(businessDateParameter));
+        LoanCOBParameter loanCOBParameter = (LoanCOBParameter) contribution.getStepExecution().getJobExecution().getExecutionContext()
+                .get(LoanCOBConstant.LOAN_COB_PARAMETER);
+        if (Objects.isNull(loanCOBParameter)
+                || (Objects.isNull(loanCOBParameter.getMinLoanId()) && Objects.isNull(loanCOBParameter.getMaxLoanId()))) {
+            loanCOBParameter = new LoanCOBParameter(0L, 0L);
+        }
+        applySoftLock(businessDate, loanCOBParameter);
+
+        return RepeatStatus.FINISHED;
+    }
+
+    private void applySoftLock(LocalDate businessDate, LoanCOBParameter loanCOBParameter) {
+        LocalDate cobBusinessDate = ThreadLocalContextUtil.getBusinessDateByType(BusinessDateType.COB_DATE);
+        jdbcTemplate.update("""
+                    INSERT INTO m_loan_account_locks (loan_id, version, lock_owner, lock_placed_on, lock_placed_on_cob_business_date)
+                    SELECT loan.id, ?, ?, ?, ? FROM m_loan loan
+                        WHERE loan.id NOT IN (SELECT loan_id FROM m_loan_account_locks)
+                        AND loan.id BETWEEN ? AND ?
+                        AND loan.loan_status_id IN (100,200,300,303,304)
+                        AND (? = loan.last_closed_business_date OR loan.last_closed_business_date IS NULL)
+                """, ps -> {
+            ps.setLong(1, 1);
+            ps.setString(2, LockOwner.LOAN_COB_PARTITIONING.name());
+            ps.setObject(3, DateUtils.getOffsetDateTimeOfTenant());
+            ps.setObject(4, cobBusinessDate);
+            ps.setObject(5, loanCOBParameter.getMinLoanId());
+            ps.setObject(6, loanCOBParameter.getMaxLoanId());
+            ps.setObject(7, businessDate);
+        });
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/cob/loan/RetrieveAllNonClosedLoanIdServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/cob/loan/RetrieveAllNonClosedLoanIdServiceImpl.java
index 24a484e0c..c175b2f7b 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/cob/loan/RetrieveAllNonClosedLoanIdServiceImpl.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/cob/loan/RetrieveAllNonClosedLoanIdServiceImpl.java
@@ -21,6 +21,7 @@ package org.apache.fineract.cob.loan;
 import java.time.LocalDate;
 import java.util.List;
 import lombok.RequiredArgsConstructor;
+import org.apache.fineract.cob.data.LoanCOBParameter;
 import org.apache.fineract.cob.data.LoanIdAndLastClosedBusinessDate;
 import org.apache.fineract.portfolio.loanaccount.domain.LoanRepository;
 
@@ -30,8 +31,8 @@ public class RetrieveAllNonClosedLoanIdServiceImpl implements RetrieveLoanIdServ
     private final LoanRepository loanRepository;
 
     @Override
-    public List<Long> retrieveLoanIdsNDaysBehind(Long numberOfDays, LocalDate businessDate) {
-        return loanRepository.findAllNonClosedLoanIdsByLastClosedBusinessDate(businessDate.minusDays(numberOfDays));
+    public LoanCOBParameter retrieveMinAndMaxLoanIdsNDaysBehind(Long numberOfDays, LocalDate businessDate) {
+        return loanRepository.findMinAndMaxNonClosedLoanIdsByLastClosedBusinessDate(businessDate.minusDays(numberOfDays));
     }
 
     @Override
diff --git a/fineract-provider/src/main/java/org/apache/fineract/cob/loan/RetrieveLoanIdService.java b/fineract-provider/src/main/java/org/apache/fineract/cob/loan/RetrieveLoanIdService.java
index f5a9c7b52..b89fad5d7 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/cob/loan/RetrieveLoanIdService.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/cob/loan/RetrieveLoanIdService.java
@@ -20,11 +20,12 @@ package org.apache.fineract.cob.loan;
 
 import java.time.LocalDate;
 import java.util.List;
+import org.apache.fineract.cob.data.LoanCOBParameter;
 import org.apache.fineract.cob.data.LoanIdAndLastClosedBusinessDate;
 
 public interface RetrieveLoanIdService {
 
-    List<Long> retrieveLoanIdsNDaysBehind(Long numberOfDays, LocalDate businessDate);
+    LoanCOBParameter retrieveMinAndMaxLoanIdsNDaysBehind(Long numberOfDays, LocalDate businessDate);
 
     List<LoanIdAndLastClosedBusinessDate> retrieveLoanIdsBehindDateOrNull(LocalDate businessDate, List<Long> loanIds);
 
diff --git a/fineract-provider/src/main/java/org/apache/fineract/cob/loan/StayedLockedLoansTasklet.java b/fineract-provider/src/main/java/org/apache/fineract/cob/loan/StayedLockedLoansTasklet.java
index f34d80739..471098921 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/cob/loan/StayedLockedLoansTasklet.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/cob/loan/StayedLockedLoansTasklet.java
@@ -53,7 +53,8 @@ public class StayedLockedLoansTasklet implements Tasklet {
 
     private LoanAccountsStayedLockedData buildLoanAccountData() {
         LocalDate cobBusinessDate = ThreadLocalContextUtil.getBusinessDateByType(BusinessDateType.COB_DATE);
-        List<LoanIdAndExternalIdAndAccountNo> stayedLockedLoanAccounts = loanRepository.findAllStayedLockedByLoanIds(cobBusinessDate);
+        List<LoanIdAndExternalIdAndAccountNo> stayedLockedLoanAccounts = loanRepository
+                .findAllStayedLockedByCobBusinessDate(cobBusinessDate);
         List<LoanAccountStayedLockedData> loanAccounts = new ArrayList<>();
         stayedLockedLoanAccounts.forEach(loanAccount -> {
             loanAccounts.add(new LoanAccountStayedLockedData(loanAccount.getId(), loanAccount.getExternalId(), loanAccount.getAccountNo()));
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanRepository.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanRepository.java
index 08e3cd0ef..8204618c7 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanRepository.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanRepository.java
@@ -21,6 +21,7 @@ package org.apache.fineract.portfolio.loanaccount.domain;
 import java.time.LocalDate;
 import java.util.Collection;
 import java.util.List;
+import org.apache.fineract.cob.data.LoanCOBParameter;
 import org.apache.fineract.cob.data.LoanIdAndExternalIdAndAccountNo;
 import org.apache.fineract.cob.data.LoanIdAndLastClosedBusinessDate;
 import org.apache.fineract.infrastructure.core.domain.ExternalId;
@@ -74,7 +75,8 @@ public interface LoanRepository extends JpaRepository<Loan, Long>, JpaSpecificat
 
     String FIND_ALL_NON_CLOSED = "select loan.id from Loan loan where loan.loanStatus in (100,200,300,303,304)";
 
-    String FIND_ALL_NON_CLOSED_LOANS_BY_LAST_CLOSED_BUSINESS_DATE = "select loan.id from Loan loan where loan.loanStatus in (100,200,300,303,304) and (:businessDate = loan.lastClosedBusinessDate or loan.lastClosedBusinessDate is NULL)";
+    String FIND_MIN_AND_MAX_NON_CLOSED_LOAN_IDS_BY_LAST_CLOSED_BUSINESS_DATE = "select new org.apache.fineract.cob.data.LoanCOBParameter(min(loan.id), max(loan.id)) from Loan loan where loan.loanStatus in (100,200,300,303,304) "
+            + "and (:businessDate = loan.lastClosedBusinessDate or loan.lastClosedBusinessDate is NULL)";
 
     String FIND_NON_CLOSED_LOAN_THAT_BELONGS_TO_CLIENT = "select loan from Loan loan where loan.id = :loanId and loan.loanStatus = 300 and loan.client.id = :clientId";
 
@@ -89,9 +91,12 @@ public interface LoanRepository extends JpaRepository<Loan, Long>, JpaSpecificat
     String FIND_ALL_NON_CLOSED_LOANS_BEHIND_OR_NULL_BY_LOAN_IDS = "select loan.id, loan.lastClosedBusinessDate from Loan loan where loan.id IN :loanIds and loan.loanStatus in (100,200,300,303,304) and (loan.lastClosedBusinessDate < :cobBusinessDate or "
             + "loan.lastClosedBusinessDate is null)";
 
+    String FIND_ALL_NON_CLOSED_LOANS_BEHIND_OR_NULL_BY_MIN_AND_MAX_LOAN_ID = "select loan.id from Loan loan where loan.id BETWEEN :minLoanId and :maxLoanId and loan.loanStatus in (100,200,300,303,304) and (loan.lastClosedBusinessDate < "
+            + ":cobBusinessDate or loan.lastClosedBusinessDate is null)";
+
     String FIND_ALL_NON_CLOSED_LOANS_BEHIND_BY_LOAN_IDS = "select loan.id, loan.lastClosedBusinessDate from Loan loan where loan.id IN :loanIds and loan.loanStatus in (100,200,300,303,304) and loan.lastClosedBusinessDate < :cobBusinessDate";
 
-    String FIND_ALL_STAYED_LOCKED_BY_LOAN_IDS = "select loan.id, loan.externalId, loan.accountNumber from LoanAccountLock lock left join Loan loan on lock.loanId = loan.id where lock.lockPlacedOnCobBusinessDate = :cobBusinessDate";
+    String FIND_ALL_STAYED_LOCKED_BY_COB_BUSINESS_DATE = "select loan.id, loan.externalId, loan.accountNumber from LoanAccountLock lock left join Loan loan on lock.loanId = loan.id where lock.lockPlacedOnCobBusinessDate = :cobBusinessDate";
 
     @Query(FIND_GROUP_LOANS_DISBURSED_AFTER)
     List<Loan> getGroupLoansDisbursedAfter(@Param("disbursementDate") LocalDate disbursementDate, @Param("groupId") Long groupId,
@@ -189,8 +194,8 @@ public interface LoanRepository extends JpaRepository<Loan, Long>, JpaSpecificat
     @Query(FIND_ID_BY_EXTERNAL_ID)
     Long findIdByExternalId(@Param("externalId") ExternalId externalId);
 
-    @Query(FIND_ALL_NON_CLOSED_LOANS_BY_LAST_CLOSED_BUSINESS_DATE)
-    List<Long> findAllNonClosedLoanIdsByLastClosedBusinessDate(@Param("businessDate") LocalDate businessDate);
+    @Query(FIND_MIN_AND_MAX_NON_CLOSED_LOAN_IDS_BY_LAST_CLOSED_BUSINESS_DATE)
+    LoanCOBParameter findMinAndMaxNonClosedLoanIdsByLastClosedBusinessDate(@Param("businessDate") LocalDate businessDate);
 
     @Query(FIND_ALL_NON_CLOSED_LOANS_BEHIND_BY_LOAN_IDS)
     List<LoanIdAndLastClosedBusinessDate> findAllNonClosedLoansBehindByLoanIds(@Param("cobBusinessDate") LocalDate cobBusinessDate,
@@ -200,10 +205,14 @@ public interface LoanRepository extends JpaRepository<Loan, Long>, JpaSpecificat
     List<LoanIdAndLastClosedBusinessDate> findAllNonClosedLoansBehindOrNullByLoanIds(@Param("cobBusinessDate") LocalDate cobBusinessDate,
             @Param("loanIds") List<Long> loanIds);
 
+    @Query(FIND_ALL_NON_CLOSED_LOANS_BEHIND_OR_NULL_BY_MIN_AND_MAX_LOAN_ID)
+    List<Long> findAllNonClosedLoansBehindOrNullByMinAndMaxLoanId(@Param("minLoanId") Long minLoanId, @Param("maxLoanId") Long maxLoanId,
+            @Param("cobBusinessDate") LocalDate cobBusinessDate);
+
     @Query(FIND_OLDEST_COB_PROCESSED_LOAN)
     List<LoanIdAndLastClosedBusinessDate> findOldestCOBProcessedLoan(@Param("cobBusinessDate") LocalDate cobBusinessDate);
 
-    @Query(FIND_ALL_STAYED_LOCKED_BY_LOAN_IDS)
-    List<LoanIdAndExternalIdAndAccountNo> findAllStayedLockedByLoanIds(@Param("cobBusinessDate") LocalDate cobBusinessDate);
+    @Query(FIND_ALL_STAYED_LOCKED_BY_COB_BUSINESS_DATE)
+    List<LoanIdAndExternalIdAndAccountNo> findAllStayedLockedByCobBusinessDate(@Param("cobBusinessDate") LocalDate cobBusinessDate);
 
 }
diff --git a/fineract-provider/src/test/java/org/apache/fineract/cob/loan/ApplyLoanLockTaskletStepDefinitions.java b/fineract-provider/src/test/java/org/apache/fineract/cob/loan/ApplyLoanLockTaskletStepDefinitions.java
index bc7271c8e..47165937e 100644
--- a/fineract-provider/src/test/java/org/apache/fineract/cob/loan/ApplyLoanLockTaskletStepDefinitions.java
+++ b/fineract-provider/src/test/java/org/apache/fineract/cob/loan/ApplyLoanLockTaskletStepDefinitions.java
@@ -20,6 +20,8 @@ package org.apache.fineract.cob.loan;
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyLong;
 import static org.mockito.Mockito.lenient;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.verify;
@@ -27,9 +29,9 @@ import static org.mockito.Mockito.verify;
 import io.cucumber.java8.En;
 import java.time.LocalDate;
 import java.time.ZoneId;
-import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
+import org.apache.fineract.cob.data.LoanCOBParameter;
 import org.apache.fineract.cob.domain.LoanAccountLock;
 import org.apache.fineract.cob.domain.LoanAccountLockRepository;
 import org.apache.fineract.cob.domain.LockOwner;
@@ -37,6 +39,7 @@ import org.apache.fineract.infrastructure.businessdate.domain.BusinessDateType;
 import org.apache.fineract.infrastructure.core.config.FineractProperties;
 import org.apache.fineract.infrastructure.core.domain.FineractPlatformTenant;
 import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanRepository;
 import org.mockito.ArgumentCaptor;
 import org.mockito.Mockito;
 import org.springframework.batch.core.StepContribution;
@@ -53,7 +56,9 @@ public class ApplyLoanLockTaskletStepDefinitions implements En {
     private FineractProperties fineractProperties = mock(FineractProperties.class);
     private JdbcTemplate jdbcTemplate = mock(JdbcTemplate.class);
     private FineractProperties.FineractQueryProperties fineractQueryProperties = mock(FineractProperties.FineractQueryProperties.class);
-    private ApplyLoanLockTasklet applyLoanLockTasklet = new ApplyLoanLockTasklet(accountLockRepository, fineractProperties, jdbcTemplate);
+    private LoanRepository loanRepository = mock(LoanRepository.class);
+    private ApplyLoanLockTasklet applyLoanLockTasklet = new ApplyLoanLockTasklet(accountLockRepository, fineractProperties, jdbcTemplate,
+            loanRepository);
     private RepeatStatus resultItem;
     private StepContribution stepContribution;
 
@@ -65,7 +70,9 @@ public class ApplyLoanLockTaskletStepDefinitions implements En {
             ThreadLocalContextUtil.setBusinessDates(businessDateMap);
             StepExecution stepExecution = new StepExecution("test", null);
             ExecutionContext executionContext = new ExecutionContext();
-            executionContext.put(LoanCOBConstant.LOAN_IDS, new ArrayList<>(List.of(1L, 2L, 3L, 4L)));
+            executionContext.put(LoanCOBConstant.LOAN_COB_PARAMETER, new LoanCOBParameter(1L, 4L));
+            lenient().when(this.loanRepository.findAllNonClosedLoansBehindOrNullByMinAndMaxLoanId(anyLong(), anyLong(), any()))
+                    .thenReturn(List.of(1L, 2L, 3L, 4L));
             stepExecution.setExecutionContext(executionContext);
             this.stepContribution = new StepContribution(stepExecution);
 
diff --git a/fineract-provider/src/test/java/org/apache/fineract/cob/loan/FetchAndLockLoanStepDefinitions.java b/fineract-provider/src/test/java/org/apache/fineract/cob/loan/FetchAndLockLoanStepDefinitions.java
index 3f5afdab0..51ef56fa7 100644
--- a/fineract-provider/src/test/java/org/apache/fineract/cob/loan/FetchAndLockLoanStepDefinitions.java
+++ b/fineract-provider/src/test/java/org/apache/fineract/cob/loan/FetchAndLockLoanStepDefinitions.java
@@ -20,8 +20,6 @@ package org.apache.fineract.cob.loan;
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.ArgumentMatchers.anyLong;
 import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.Mockito.lenient;
 import static org.mockito.Mockito.mock;
@@ -33,6 +31,7 @@ import java.time.ZoneId;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.List;
+import org.apache.fineract.cob.data.LoanCOBParameter;
 import org.apache.fineract.cob.domain.LoanAccountLock;
 import org.apache.fineract.cob.domain.LoanAccountLockRepository;
 import org.apache.fineract.cob.domain.LockOwner;
@@ -46,17 +45,17 @@ import org.springframework.batch.core.StepContribution;
 import org.springframework.batch.core.StepExecution;
 import org.springframework.batch.repeat.RepeatStatus;
 import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.jdbc.core.PreparedStatementSetter;
 
 public class FetchAndLockLoanStepDefinitions implements En {
 
     private final LoanAccountLockRepository loanAccountLockRepository = mock(LoanAccountLockRepository.class);
 
-    private final RetrieveLoanIdService retrieveLoanIdService = mock(RetrieveLoanIdService.class);
     private final FineractProperties fineractProperties = mock(FineractProperties.class);
     private final FineractProperties.FineractQueryProperties fineractQueryProperties = mock(
             FineractProperties.FineractQueryProperties.class);
     StepContribution contribution;
-    private FetchAndLockLoanTasklet fetchAndLockLoanTasklet;
+    private LockLoanTasklet lockLoanTasklet;
     private String action;
     private RepeatStatus result;
     private JdbcTemplate jdbcTemplate = mock(JdbcTemplate.class);
@@ -69,105 +68,87 @@ public class FetchAndLockLoanStepDefinitions implements En {
             ThreadLocalContextUtil.setBusinessDates(businessDateMap);
             this.action = action;
 
+            JobExecution jobExecution = new JobExecution(1L);
+            StepExecution stepExecution = new StepExecution("step", jobExecution);
+            contribution = new StepContribution(stepExecution);
+            contribution.getStepExecution().getJobExecution().getExecutionContext().put(LoanCOBConstant.BUSINESS_DATE_PARAMETER_NAME,
+                    LocalDate.now(ZoneId.systemDefault()).toString());
+
             if ("empty loanIds".equals(action)) {
-                lenient().when(retrieveLoanIdService.retrieveLoanIdsNDaysBehind(anyLong(), any())).thenReturn(Collections.emptyList());
+                contribution.getStepExecution().getJobExecution().getExecutionContext().put(LoanCOBConstant.LOAN_COB_PARAMETER,
+                        new LoanCOBParameter(0L, 0L));
             } else if ("good".equals(action)) {
-                lenient().when(retrieveLoanIdService.retrieveLoanIdsNDaysBehind(anyLong(), any())).thenReturn(List.of(1L, 2L, 3L));
+                contribution.getStepExecution().getJobExecution().getExecutionContext().put(LoanCOBConstant.LOAN_COB_PARAMETER,
+                        new LoanCOBParameter(1L, 3L));
                 lenient().when(fineractProperties.getQuery()).thenReturn(fineractQueryProperties);
                 lenient().when(fineractQueryProperties.getInClauseParameterSizeLimit()).thenReturn(65000);
                 lenient().when(loanAccountLockRepository.findAllByLoanIdIn(Mockito.anyList())).thenReturn(Collections.emptyList());
             } else if ("soft lock".equals(action)) {
-                lenient().when(retrieveLoanIdService.retrieveLoanIdsNDaysBehind(anyLong(), any())).thenReturn(List.of(1L, 2L, 3L));
+                contribution.getStepExecution().getJobExecution().getExecutionContext().put(LoanCOBConstant.LOAN_COB_PARAMETER,
+                        new LoanCOBParameter(1L, 3L));
                 lenient().when(fineractProperties.getQuery()).thenReturn(fineractQueryProperties);
                 lenient().when(fineractQueryProperties.getInClauseParameterSizeLimit()).thenReturn(65000);
                 lenient().when(loanAccountLockRepository.findAllByLoanIdIn(Mockito.anyList())).thenReturn(
                         List.of(new LoanAccountLock(1L, LockOwner.LOAN_COB_PARTITIONING, LocalDate.now(ZoneId.systemDefault()))));
             } else if ("inline cob".equals(action)) {
-                lenient().when(retrieveLoanIdService.retrieveLoanIdsNDaysBehind(anyLong(), any())).thenReturn(List.of(1L, 2L, 3L));
+                contribution.getStepExecution().getJobExecution().getExecutionContext().put(LoanCOBConstant.LOAN_COB_PARAMETER,
+                        new LoanCOBParameter(1L, 3L));
                 lenient().when(fineractProperties.getQuery()).thenReturn(fineractQueryProperties);
                 lenient().when(fineractQueryProperties.getInClauseParameterSizeLimit()).thenReturn(65000);
                 lenient().when(loanAccountLockRepository.findAllByLoanIdIn(Mockito.anyList())).thenReturn(
                         List.of(new LoanAccountLock(2L, LockOwner.LOAN_INLINE_COB_PROCESSING, LocalDate.now(ZoneId.systemDefault()))));
             } else if ("chunk processing".equals(action)) {
-                lenient().when(retrieveLoanIdService.retrieveLoanIdsNDaysBehind(anyLong(), any())).thenReturn(List.of(1L, 2L, 3L));
+                contribution.getStepExecution().getJobExecution().getExecutionContext().put(LoanCOBConstant.LOAN_COB_PARAMETER,
+                        new LoanCOBParameter(1L, 3L));
                 lenient().when(fineractProperties.getQuery()).thenReturn(fineractQueryProperties);
                 lenient().when(fineractQueryProperties.getInClauseParameterSizeLimit()).thenReturn(65000);
                 lenient().when(loanAccountLockRepository.findAllByLoanIdIn(Mockito.anyList())).thenReturn(
                         List.of(new LoanAccountLock(3L, LockOwner.LOAN_COB_CHUNK_PROCESSING, LocalDate.now(ZoneId.systemDefault()))));
             }
 
-            JobExecution jobExecution = new JobExecution(1L);
-            StepExecution stepExecution = new StepExecution("step", jobExecution);
-            contribution = new StepContribution(stepExecution);
-            contribution.getStepExecution().getJobExecution().getExecutionContext().put(LoanCOBConstant.BUSINESS_DATE_PARAMETER_NAME,
-                    LocalDate.now(ZoneId.systemDefault()).toString());
-            fetchAndLockLoanTasklet = new FetchAndLockLoanTasklet(loanAccountLockRepository, retrieveLoanIdService, fineractProperties,
-                    jdbcTemplate);
+            lockLoanTasklet = new LockLoanTasklet(jdbcTemplate);
         });
 
         When("FetchAndLockLoanTasklet.execute method executed", () -> {
-            result = this.fetchAndLockLoanTasklet.execute(contribution, null);
+            result = this.lockLoanTasklet.execute(contribution, null);
         });
 
         Then("FetchAndLockLoanTasklet.execute result should match", () -> {
             if ("empty steps".equals(action)) {
                 assertEquals(RepeatStatus.FINISHED, result);
             } else if ("good".equals(action)) {
-                verify(jdbcTemplate).batchUpdate(anyString(), any(), anyInt(), any());
+                verify(jdbcTemplate).update(anyString(), (PreparedStatementSetter) any());
                 assertEquals(RepeatStatus.FINISHED, result);
-                assertEquals(3,
-                        ((List) contribution.getStepExecution().getJobExecution().getExecutionContext().get(LoanCOBConstant.LOAN_IDS))
-                                .size());
-                assertEquals(1L,
-                        ((List) contribution.getStepExecution().getJobExecution().getExecutionContext().get(LoanCOBConstant.LOAN_IDS))
-                                .get(0));
-                assertEquals(2L,
-                        ((List) contribution.getStepExecution().getJobExecution().getExecutionContext().get(LoanCOBConstant.LOAN_IDS))
-                                .get(1));
-                assertEquals(3L,
-                        ((List) contribution.getStepExecution().getJobExecution().getExecutionContext().get(LoanCOBConstant.LOAN_IDS))
-                                .get(2));
+                LoanCOBParameter loanCOBParameter = (LoanCOBParameter) contribution.getStepExecution().getJobExecution()
+                        .getExecutionContext().get(LoanCOBConstant.LOAN_COB_PARAMETER);
+                assertEquals(2, loanCOBParameter.getMaxLoanId() - loanCOBParameter.getMinLoanId());
+                assertEquals(1L, loanCOBParameter.getMinLoanId());
+                assertEquals(3L, loanCOBParameter.getMaxLoanId());
             } else if ("soft lock".equals(action)) {
-                verify(jdbcTemplate).batchUpdate(anyString(), any(), anyInt(), any());
+                verify(jdbcTemplate).update(anyString(), (PreparedStatementSetter) any());
                 assertEquals(RepeatStatus.FINISHED, result);
-                assertEquals(3,
-                        ((List) contribution.getStepExecution().getJobExecution().getExecutionContext().get(LoanCOBConstant.LOAN_IDS))
-                                .size());
-                assertEquals(1L,
-                        ((List) contribution.getStepExecution().getJobExecution().getExecutionContext().get(LoanCOBConstant.LOAN_IDS))
-                                .get(0));
-                assertEquals(2L,
-                        ((List) contribution.getStepExecution().getJobExecution().getExecutionContext().get(LoanCOBConstant.LOAN_IDS))
-                                .get(1));
-                assertEquals(3L,
-                        ((List) contribution.getStepExecution().getJobExecution().getExecutionContext().get(LoanCOBConstant.LOAN_IDS))
-                                .get(2));
+                LoanCOBParameter loanCOBParameter = (LoanCOBParameter) contribution.getStepExecution().getJobExecution()
+                        .getExecutionContext().get(LoanCOBConstant.LOAN_COB_PARAMETER);
+                assertEquals(2, loanCOBParameter.getMaxLoanId() - loanCOBParameter.getMinLoanId());
+                assertEquals(1L, loanCOBParameter.getMinLoanId());
+                assertEquals(3L, loanCOBParameter.getMaxLoanId());
             } else if ("inline cob".equals(action)) {
-                verify(jdbcTemplate).batchUpdate(anyString(), any(), anyInt(), any());
+                verify(jdbcTemplate).update(anyString(), (PreparedStatementSetter) any());
                 assertEquals(RepeatStatus.FINISHED, result);
-                assertEquals(2,
-                        ((List) contribution.getStepExecution().getJobExecution().getExecutionContext().get(LoanCOBConstant.LOAN_IDS))
-                                .size());
-                assertEquals(1L,
-                        ((List) contribution.getStepExecution().getJobExecution().getExecutionContext().get(LoanCOBConstant.LOAN_IDS))
-                                .get(0));
-                assertEquals(3L,
-                        ((List) contribution.getStepExecution().getJobExecution().getExecutionContext().get(LoanCOBConstant.LOAN_IDS))
-                                .get(1));
+                LoanCOBParameter loanCOBParameter = (LoanCOBParameter) contribution.getStepExecution().getJobExecution()
+                        .getExecutionContext().get(LoanCOBConstant.LOAN_COB_PARAMETER);
+                assertEquals(2, loanCOBParameter.getMaxLoanId() - loanCOBParameter.getMinLoanId());
+                assertEquals(1L, loanCOBParameter.getMinLoanId());
+                assertEquals(3L, loanCOBParameter.getMaxLoanId());
             } else if ("chunk processing".equals(action)) {
-                verify(jdbcTemplate).batchUpdate(anyString(), any(), anyInt(), any());
+                verify(jdbcTemplate).update(anyString(), (PreparedStatementSetter) any());
                 assertEquals(RepeatStatus.FINISHED, result);
-                assertEquals(2,
-                        ((List) contribution.getStepExecution().getJobExecution().getExecutionContext().get(LoanCOBConstant.LOAN_IDS))
-                                .size());
-                assertEquals(1L,
-                        ((List) contribution.getStepExecution().getJobExecution().getExecutionContext().get(LoanCOBConstant.LOAN_IDS))
-                                .get(0));
-                assertEquals(2L,
-                        ((List) contribution.getStepExecution().getJobExecution().getExecutionContext().get(LoanCOBConstant.LOAN_IDS))
-                                .get(1));
+                LoanCOBParameter loanCOBParameter = (LoanCOBParameter) contribution.getStepExecution().getJobExecution()
+                        .getExecutionContext().get(LoanCOBConstant.LOAN_COB_PARAMETER);
+                assertEquals(2, loanCOBParameter.getMaxLoanId() - loanCOBParameter.getMinLoanId());
+                assertEquals(1L, loanCOBParameter.getMinLoanId());
+                assertEquals(3L, loanCOBParameter.getMaxLoanId());
             }
         });
-
     }
 }
diff --git a/fineract-provider/src/test/java/org/apache/fineract/cob/loan/LoanCOBPartitionerStepDefinitions.java b/fineract-provider/src/test/java/org/apache/fineract/cob/loan/LoanCOBPartitionerStepDefinitions.java
index 03a045ac3..012dbd317 100644
--- a/fineract-provider/src/test/java/org/apache/fineract/cob/loan/LoanCOBPartitionerStepDefinitions.java
+++ b/fineract-provider/src/test/java/org/apache/fineract/cob/loan/LoanCOBPartitionerStepDefinitions.java
@@ -28,11 +28,11 @@ import com.google.gson.Gson;
 import io.cucumber.java8.En;
 import java.util.Collections;
 import java.util.HashSet;
-import java.util.List;
 import java.util.Map;
 import java.util.Set;
 import org.apache.fineract.cob.COBBusinessStepService;
 import org.apache.fineract.cob.data.BusinessStepNameAndOrder;
+import org.apache.fineract.cob.data.LoanCOBParameter;
 import org.apache.fineract.infrastructure.core.serialization.GoogleGsonSerializerHelper;
 import org.apache.fineract.infrastructure.jobs.service.JobName;
 import org.apache.fineract.infrastructure.springbatch.PropertyService;
@@ -50,7 +50,7 @@ public class LoanCOBPartitionerStepDefinitions implements En {
     JobExplorer jobExplorer = mock(JobExplorer.class);
     private final Gson gson = GoogleGsonSerializerHelper.createSimpleGson();
 
-    List<Long> loanIds;
+    LoanCOBParameter loanIds;
     private LoanCOBPartitioner loanCOBPartitioner;
 
     private Set<BusinessStepNameAndOrder> cobBusinessSteps = new HashSet<>();
@@ -68,17 +68,17 @@ public class LoanCOBPartitionerStepDefinitions implements En {
                         .thenReturn(Collections.emptySet());
                 lenient().when(jobExplorer.findRunningJobExecutions(JobName.LOAN_COB.name())).thenReturn(Set.of(new JobExecution(3L)));
                 lenient().when(jobOperator.stop(3L)).thenReturn(Boolean.TRUE);
-                loanIds = Collections.emptyList();
+                loanIds = null;
             } else if ("empty loanIds".equals(action)) {
                 cobBusinessSteps.add(new BusinessStepNameAndOrder("Business step", 1L));
                 lenient().when(cobBusinessStepService.getCOBBusinessSteps(LoanCOBBusinessStep.class, LoanCOBConstant.LOAN_COB_JOB_NAME))
                         .thenReturn(cobBusinessSteps);
-                loanIds = Collections.emptyList();
+                loanIds = null;
             } else if ("good".equals(action)) {
                 cobBusinessSteps.add(new BusinessStepNameAndOrder("Business step", 1L));
                 lenient().when(cobBusinessStepService.getCOBBusinessSteps(LoanCOBBusinessStep.class, LoanCOBConstant.LOAN_COB_JOB_NAME))
                         .thenReturn(cobBusinessSteps);
-                loanIds = List.of(1L, 2L, 3L);
+                loanIds = new LoanCOBParameter(1L, 3L);
             }
             loanCOBPartitioner = new LoanCOBPartitioner(propertyService, cobBusinessStepService, jobOperator, jobExplorer, loanIds);
         });
@@ -101,16 +101,21 @@ public class LoanCOBPartitionerStepDefinitions implements En {
                         businessSteps.stream().findFirst().get().getStepOrder());
                 assertEquals(cobBusinessSteps.stream().findFirst().get().getStepName(),
                         businessSteps.stream().findFirst().get().getStepName());
-                assertEquals(2, ((List) resultItem.get(LoanCOBPartitioner.PARTITION_PREFIX + "1").get(LoanCOBConstant.LOAN_IDS)).size());
-                assertEquals(1L, ((List) resultItem.get(LoanCOBPartitioner.PARTITION_PREFIX + "1").get(LoanCOBConstant.LOAN_IDS)).get(0));
-                assertEquals(2L, ((List) resultItem.get(LoanCOBPartitioner.PARTITION_PREFIX + "1").get(LoanCOBConstant.LOAN_IDS)).get(1));
+                LoanCOBParameter loanCOBParameter = (LoanCOBParameter) resultItem.get(LoanCOBPartitioner.PARTITION_PREFIX + "1")
+                        .get(LoanCOBConstant.LOAN_COB_PARAMETER);
+                assertEquals(1, loanCOBParameter.getMaxLoanId() - loanCOBParameter.getMinLoanId());
+                assertEquals(1L, loanCOBParameter.getMinLoanId());
+                assertEquals(2L, loanCOBParameter.getMaxLoanId());
                 assertTrue(resultItem.containsKey(LoanCOBPartitioner.PARTITION_PREFIX + "2"));
                 assertEquals(cobBusinessSteps.stream().findFirst().get().getStepOrder(),
                         businessSteps.stream().findFirst().get().getStepOrder());
                 assertEquals(cobBusinessSteps.stream().findFirst().get().getStepName(),
                         businessSteps.stream().findFirst().get().getStepName());
-                assertEquals(1, ((List) resultItem.get(LoanCOBPartitioner.PARTITION_PREFIX + "2").get(LoanCOBConstant.LOAN_IDS)).size());
-                assertEquals(3L, ((List) resultItem.get(LoanCOBPartitioner.PARTITION_PREFIX + "2").get(LoanCOBConstant.LOAN_IDS)).get(0));
+                loanCOBParameter = (LoanCOBParameter) resultItem.get(LoanCOBPartitioner.PARTITION_PREFIX + "2")
+                        .get(LoanCOBConstant.LOAN_COB_PARAMETER);
+                assertEquals(0, loanCOBParameter.getMaxLoanId() - loanCOBParameter.getMinLoanId());
+                assertEquals(3L, loanCOBParameter.getMinLoanId());
+                assertEquals(3L, loanCOBParameter.getMaxLoanId());
             } else if ("empty loanIds".equals(action)) {
                 verify(jobOperator, Mockito.times(0)).stop(Mockito.anyLong());
                 assertEquals(1, resultItem.size());
@@ -121,7 +126,9 @@ public class LoanCOBPartitionerStepDefinitions implements En {
                         businessSteps.stream().findFirst().get().getStepOrder());
                 assertEquals(cobBusinessSteps.stream().findFirst().get().getStepName(),
                         businessSteps.stream().findFirst().get().getStepName());
-                assertEquals(0, ((List) resultItem.get(LoanCOBPartitioner.PARTITION_PREFIX + "1").get(LoanCOBConstant.LOAN_IDS)).size());
+                LoanCOBParameter loanCOBParameter = (LoanCOBParameter) resultItem.get(LoanCOBPartitioner.PARTITION_PREFIX + "1")
+                        .get(LoanCOBConstant.LOAN_COB_PARAMETER);
+                assertEquals(null, loanCOBParameter);
             }
         });
     }
diff --git a/fineract-provider/src/test/java/org/apache/fineract/cob/loan/LoanItemReaderStepDefinitions.java b/fineract-provider/src/test/java/org/apache/fineract/cob/loan/LoanItemReaderStepDefinitions.java
index c2b0a8700..b35b0ec38 100644
--- a/fineract-provider/src/test/java/org/apache/fineract/cob/loan/LoanItemReaderStepDefinitions.java
+++ b/fineract-provider/src/test/java/org/apache/fineract/cob/loan/LoanItemReaderStepDefinitions.java
@@ -21,15 +21,23 @@ package org.apache.fineract.cob.loan;
 import static org.junit.Assert.assertNull;
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyLong;
 import static org.mockito.Mockito.lenient;
 import static org.mockito.Mockito.mock;
 
 import com.google.common.base.Splitter;
 import io.cucumber.java8.En;
+import java.time.LocalDate;
+import java.time.ZoneId;
 import java.util.ArrayList;
+import java.util.HashMap;
 import java.util.List;
 import java.util.Optional;
+import org.apache.fineract.cob.data.LoanCOBParameter;
 import org.apache.fineract.cob.exceptions.LoanReadException;
+import org.apache.fineract.infrastructure.businessdate.domain.BusinessDateType;
+import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil;
 import org.apache.fineract.portfolio.loanaccount.domain.Loan;
 import org.apache.fineract.portfolio.loanaccount.domain.LoanRepository;
 import org.springframework.batch.core.JobExecution;
@@ -53,18 +61,29 @@ public class LoanItemReaderStepDefinitions implements En {
             jobExecution.setExecutionContext(jobExecutionContext);
             StepExecution stepExecution = new StepExecution("test", jobExecution);
             ExecutionContext stepExecutionContext = new ExecutionContext();
-            List<Long> splitAccounts;
-            if (loanIds.isEmpty()) {
-                splitAccounts = new ArrayList<>();
-            } else {
+            Long minLoanId = null;
+            Long maxLoanId = null;
+            List<Long> splitAccounts = new ArrayList<>();
+            if (!loanIds.isEmpty()) {
                 List<String> splitStr = Splitter.on(',').splitToList(loanIds);
                 splitAccounts = splitStr.stream().map(Long::parseLong).toList();
+                minLoanId = splitAccounts.get(0);
+                maxLoanId = splitAccounts.get(splitAccounts.size() - 1);
             }
-            stepExecutionContext.put(LoanCOBConstant.LOAN_IDS, new ArrayList<>(splitAccounts));
-            stepExecution.setExecutionContext(stepExecutionContext);
+            stepExecutionContext.put(LoanCOBConstant.LOAN_COB_PARAMETER, new LoanCOBParameter(minLoanId, maxLoanId));
+            jobExecution.setExecutionContext(stepExecutionContext);
+
+            lenient().when(this.loanRepository.findAllNonClosedLoansBehindOrNullByMinAndMaxLoanId(anyLong(), anyLong(), any()))
+                    .thenReturn(splitAccounts);
+
+            HashMap<BusinessDateType, LocalDate> businessDates = new HashMap<>();
+            LocalDate businessDate = LocalDate.now(ZoneId.systemDefault());
+            businessDates.put(BusinessDateType.BUSINESS_DATE, businessDate);
+            businessDates.put(BusinessDateType.COB_DATE, businessDate.minusDays(1));
+            ThreadLocalContextUtil.setBusinessDates(businessDates);
+
             loanItemReader.beforeStep(stepExecution);
 
-            lenient().when(this.loanRepository.findById(0L)).thenReturn(Optional.empty());
             lenient().when(this.loanRepository.findById(1L)).thenReturn(Optional.of(loan));
             lenient().when(this.loanRepository.findById(-1L)).thenThrow(new RuntimeException("fail"));
 
diff --git a/fineract-provider/src/test/resources/features/cob/loan/cob.loan.reader.feature b/fineract-provider/src/test/resources/features/cob/loan/cob.loan.reader.feature
index e775f3a86..2453f779a 100644
--- a/fineract-provider/src/test/resources/features/cob/loan/cob.loan.reader.feature
+++ b/fineract-provider/src/test/resources/features/cob/loan/cob.loan.reader.feature
@@ -48,4 +48,3 @@ Feature: COB Reader
     Examples:
       |loanIds|
       |-1     |
-      |0      |