You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@fineract.apache.org by ar...@apache.org on 2022/11/28 14:27:13 UTC

[fineract] branch develop updated: [FINERACT-1678] Introducing `Last COB Business Date` for loans

This is an automated email from the ASF dual-hosted git repository.

arnold 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 ca51084e8 [FINERACT-1678] Introducing `Last COB Business Date` for loans
ca51084e8 is described below

commit ca51084e863395356396b4dac9d1d89ba7116078
Author: taskain7 <ta...@gmail.com>
AuthorDate: Thu Nov 24 03:21:59 2022 +0100

    [FINERACT-1678] Introducing `Last COB Business Date` for loans
---
 .../cob/common/CustomJobParameterResolver.java     | 69 ++++++++++++++++++
 .../CustomJobParameterNotFoundException.java       | 39 ++++++++++
 .../cob/loan/AbstractLoanItemProcessor.java        | 15 +++-
 .../fineract/cob/loan/FetchAndLockLoanTasklet.java |  9 ++-
 .../cob/loan/InlineCOBLoanItemProcessor.java       |  1 +
 .../apache/fineract/cob/loan/LoanCOBConstant.java  |  1 +
 .../cob/loan/LoanCOBManagerConfiguration.java      | 26 ++++++-
 .../fineract/cob/loan/LoanItemProcessor.java       |  1 +
 .../ResolveLoanCOBCustomJobParametersTasklet.java} | 27 ++-----
 .../RetrieveAllNonClosedLoanIdServiceImpl.java     |  5 +-
 .../fineract/cob/loan/RetrieveLoanIdService.java   |  3 +-
 .../service/InlineLoanCOBExecutorServiceImpl.java  | 13 +++-
 .../jobs/api/SchedulerJobApiResource.java          |  6 +-
 .../jobs/api/SchedulerJobApiResourceSwagger.java   | 13 ++++
 .../jobs/data/JobParameterDTO.java}                | 14 +++-
 .../jobs/service/JobParameterDataParser.java       | 59 +++++++++++++++
 .../jobs/service/JobRegisterService.java           |  2 +-
 .../jobs/service/JobRegisterServiceImpl.java       | 26 +++++--
 .../infrastructure/jobs/service/JobStarter.java    | 25 ++++++-
 .../ExecuteAllDirtyJobsTasklet.java                |  2 +-
 .../AbstractJobParameterProvider.java}             | 11 ++-
 .../JobParameterProvider.java}                     | 13 +++-
 .../LoanCOBJobParameterProvider.java               | 85 ++++++++++++++++++++++
 .../loanaccount/api/LoansApiResource.java          |  9 ++-
 .../loanaccount/api/LoansApiResourceSwagger.java   |  1 +
 .../loanaccount/data/LoanAccountData.java          |  9 ++-
 .../portfolio/loanaccount/domain/Loan.java         | 10 +++
 .../loanaccount/domain/LoanRepository.java         |  6 ++
 .../service/LoanReadPlatformServiceImpl.java       |  6 +-
 .../db/changelog/tenant/changelog-tenant.xml       |  2 +-
 ...4_add_last_cob_business_date_column_to_loan.xml | 30 ++++++++
 .../cob/loan/FetchAndLockLoanStepDefinitions.java  | 22 ++++--
 .../cob/loan/LoanItemProcessorStepDefinitions.java |  9 ++-
 .../DelinquencyBucketsIntegrationTest.java         |  3 +-
 .../integrationtests/SchedulerJobsTestResults.java | 61 +++++++++++++++-
 .../common/SchedulerJobHelper.java                 | 11 +++
 36 files changed, 565 insertions(+), 79 deletions(-)

diff --git a/fineract-provider/src/main/java/org/apache/fineract/cob/common/CustomJobParameterResolver.java b/fineract-provider/src/main/java/org/apache/fineract/cob/common/CustomJobParameterResolver.java
new file mode 100644
index 000000000..9e156856c
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/cob/common/CustomJobParameterResolver.java
@@ -0,0 +1,69 @@
+/**
+ * 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.common;
+
+import com.google.common.reflect.TypeToken;
+import com.google.gson.Gson;
+import java.util.HashSet;
+import java.util.Set;
+import lombok.RequiredArgsConstructor;
+import org.apache.fineract.cob.exceptions.CustomJobParameterNotFoundException;
+import org.apache.fineract.infrastructure.core.serialization.GoogleGsonSerializerHelper;
+import org.apache.fineract.infrastructure.jobs.data.JobParameterDTO;
+import org.apache.fineract.infrastructure.jobs.domain.CustomJobParameter;
+import org.apache.fineract.infrastructure.jobs.domain.CustomJobParameterRepository;
+import org.apache.fineract.infrastructure.springbatch.SpringBatchJobConstants;
+import org.springframework.batch.core.StepContribution;
+import org.springframework.batch.core.scope.context.ChunkContext;
+import org.springframework.beans.factory.InitializingBean;
+import org.springframework.stereotype.Component;
+
+@RequiredArgsConstructor
+@Component
+public class CustomJobParameterResolver implements InitializingBean {
+
+    private final GoogleGsonSerializerHelper gsonFactory;
+    private final CustomJobParameterRepository customJobParameterRepository;
+
+    protected Gson gson;
+
+    @Override
+    public void afterPropertiesSet() throws Exception {
+        this.gson = gsonFactory.createSimpleGson();
+    }
+
+    public void resolve(StepContribution contribution, ChunkContext chunkContext, String customJobParameterKey,
+            String parameterNameInExecutionContext) {
+        Set<JobParameterDTO> jobParameterDTOList = getCustomJobParameterSet(chunkContext);
+        JobParameterDTO businessDateParameter = jobParameterDTOList.stream()
+                .filter(jobParameterDTO -> customJobParameterKey.equals(jobParameterDTO.getParameterName())) //
+                .findFirst().orElseThrow(() -> new CustomJobParameterNotFoundException(customJobParameterKey));
+        contribution.getStepExecution().getExecutionContext().put(parameterNameInExecutionContext,
+                businessDateParameter.getParameterValue());
+    }
+
+    private Set<JobParameterDTO> getCustomJobParameterSet(ChunkContext chunkContext) {
+        Long customJobParameterId = (Long) chunkContext.getStepContext().getJobParameters()
+                .get(SpringBatchJobConstants.CUSTOM_JOB_PARAMETER_ID_KEY);
+        CustomJobParameter customJobParameter = customJobParameterRepository.findById(customJobParameterId)
+                .orElseThrow(() -> new CustomJobParameterNotFoundException(customJobParameterId));
+        String parameterJson = customJobParameter.getParameterJson();
+        return gson.fromJson(parameterJson, new TypeToken<HashSet<JobParameterDTO>>() {}.getType());
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/cob/exceptions/CustomJobParameterNotFoundException.java b/fineract-provider/src/main/java/org/apache/fineract/cob/exceptions/CustomJobParameterNotFoundException.java
new file mode 100644
index 000000000..812f0f33d
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/cob/exceptions/CustomJobParameterNotFoundException.java
@@ -0,0 +1,39 @@
+/**
+ * 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.exceptions;
+
+import org.apache.fineract.infrastructure.core.exception.AbstractPlatformResourceNotFoundException;
+
+public class CustomJobParameterNotFoundException extends AbstractPlatformResourceNotFoundException {
+
+    public CustomJobParameterNotFoundException(String globalisationMessageCode, String defaultUserMessage,
+            Object... defaultUserMessageArgs) {
+        super(globalisationMessageCode, defaultUserMessage, defaultUserMessageArgs);
+    }
+
+    public CustomJobParameterNotFoundException(Long customJobParameterId) {
+        super("error.msg.customjobparameter.not.found", "Custom job parameter not found with the given id: " + customJobParameterId,
+                customJobParameterId);
+    }
+
+    public CustomJobParameterNotFoundException(String customJobParameterName) {
+        super("error.msg.customjobparameter.not.found", "Custom job parameter not found with the given name: " + customJobParameterName,
+                customJobParameterName);
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/cob/loan/AbstractLoanItemProcessor.java b/fineract-provider/src/main/java/org/apache/fineract/cob/loan/AbstractLoanItemProcessor.java
index 9c936f991..65ddcfff0 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/cob/loan/AbstractLoanItemProcessor.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/cob/loan/AbstractLoanItemProcessor.java
@@ -18,6 +18,9 @@
  */
 package org.apache.fineract.cob.loan;
 
+import java.time.LocalDate;
+import java.time.format.DateTimeFormatter;
+import java.util.Objects;
 import java.util.TreeMap;
 import lombok.AccessLevel;
 import lombok.RequiredArgsConstructor;
@@ -40,12 +43,15 @@ public abstract class AbstractLoanItemProcessor implements ItemProcessor<Loan, L
 
     @Setter(AccessLevel.PROTECTED)
     private ExecutionContext executionContext;
+    private LocalDate businessDate;
 
     @Override
     public Loan process(@NotNull Loan item) throws Exception {
         TreeMap<Long, String> businessStepMap = (TreeMap<Long, String>) executionContext.get(LoanCOBConstant.BUSINESS_STEP_MAP);
 
-        return cobBusinessStepService.run(businessStepMap, item);
+        Loan alreadyProcessedLoan = cobBusinessStepService.run(businessStepMap, item);
+        alreadyProcessedLoan.setLastClosedBusinessDate(businessDate);
+        return alreadyProcessedLoan;
     }
 
     @AfterStep
@@ -53,4 +59,11 @@ public abstract class AbstractLoanItemProcessor implements ItemProcessor<Loan, L
         return ExitStatus.COMPLETED;
     }
 
+    protected void setBusinessDate(StepExecution stepExecution) {
+        this.businessDate = LocalDate.parse(
+                Objects.requireNonNull(
+                        (String) stepExecution.getJobExecution().getExecutionContext().get(LoanCOBConstant.BUSINESS_DATE_PARAMETER_NAME)),
+                DateTimeFormatter.ISO_DATE);
+    }
+
 }
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
index 176823ebb..c864ba396 100644
--- 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
@@ -18,8 +18,10 @@
  */
 package org.apache.fineract.cob.loan;
 
+import java.time.LocalDate;
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Objects;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
 import org.apache.fineract.cob.domain.LoanAccountLock;
@@ -35,13 +37,18 @@ import org.springframework.batch.repeat.RepeatStatus;
 @RequiredArgsConstructor
 public class FetchAndLockLoanTasklet implements Tasklet {
 
+    private static final Long NUMBER_OF_DAYS_BEHIND = 1L;
+
     private final LoanAccountLockRepository loanAccountLockRepository;
 
     private final RetrieveLoanIdService retrieveLoanIdService;
 
     @Override
     public RepeatStatus execute(@NotNull StepContribution contribution, @NotNull ChunkContext chunkContext) throws Exception {
-        List<Long> allNonClosedLoanIds = retrieveLoanIdService.retrieveLoanIds();
+        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()) {
             return RepeatStatus.FINISHED;
         }
diff --git a/fineract-provider/src/main/java/org/apache/fineract/cob/loan/InlineCOBLoanItemProcessor.java b/fineract-provider/src/main/java/org/apache/fineract/cob/loan/InlineCOBLoanItemProcessor.java
index 88e365373..75894dd4c 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/cob/loan/InlineCOBLoanItemProcessor.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/cob/loan/InlineCOBLoanItemProcessor.java
@@ -31,5 +31,6 @@ public class InlineCOBLoanItemProcessor extends AbstractLoanItemProcessor {
     @BeforeStep
     public void beforeStep(StepExecution stepExecution) {
         setExecutionContext(stepExecution.getJobExecution().getExecutionContext());
+        setBusinessDate(stepExecution);
     }
 }
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 bce9222af..8c7a4f66d 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
@@ -28,6 +28,7 @@ public final class LoanCOBConstant {
 
     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";
 
     private LoanCOBConstant() {
 
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 8c20a4b12..0f86c67a0 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
@@ -20,6 +20,7 @@ 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.listener.COBExecutionListenerRunner;
 import org.apache.fineract.infrastructure.event.business.service.BusinessEventNotifierService;
@@ -33,6 +34,7 @@ import org.springframework.batch.core.configuration.annotation.StepBuilderFactor
 import org.springframework.batch.core.explore.JobExplorer;
 import org.springframework.batch.core.launch.JobOperator;
 import org.springframework.batch.core.launch.support.RunIdIncrementer;
+import org.springframework.batch.core.listener.ExecutionContextPromotionListener;
 import org.springframework.batch.integration.config.annotation.EnableBatchIntegration;
 import org.springframework.batch.integration.partition.RemotePartitioningManagerStepBuilderFactory;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -72,6 +74,8 @@ public class LoanCOBManagerConfiguration {
     private LoanAccountLockRepository accountLockRepository;
     @Autowired
     private BusinessEventNotifierService businessEventNotifierService;
+    @Autowired
+    private CustomJobParameterResolver customJobParameterResolver;
 
     @Bean
     @JobScope
@@ -90,6 +94,12 @@ public class LoanCOBManagerConfiguration {
         return localStepBuilderFactory.get("Fetch and Lock loan accounts - Step").tasklet(fetchAndLockLoanTasklet()).build();
     }
 
+    @Bean
+    public Step resolveCustomJobParametersStep() {
+        return localStepBuilderFactory.get("Resolve custom job parameters - Step").tasklet(resolveCustomJobParametersTasklet())
+                .listener(customJobParametersPromotionListener()).build();
+    }
+
     @Bean
     public Step stayedLockedStep() {
         return localStepBuilderFactory.get("Stayed locked loan accounts - Step").tasklet(stayedLockedTasklet()).build();
@@ -101,6 +111,12 @@ public class LoanCOBManagerConfiguration {
         return new FetchAndLockLoanTasklet(accountLockRepository, retrieveLoanIdService);
     }
 
+    @Bean
+    @JobScope
+    public ResolveLoanCOBCustomJobParametersTasklet resolveCustomJobParametersTasklet() {
+        return new ResolveLoanCOBCustomJobParametersTasklet(customJobParameterResolver);
+    }
+
     @Bean
     @JobScope
     public StayedLockedLoansTasklet stayedLockedTasklet() {
@@ -111,8 +127,16 @@ public class LoanCOBManagerConfiguration {
     public Job loanCOBJob() {
         return jobBuilderFactory.get(JobName.LOAN_COB.name()) //
                 .listener(new COBExecutionListenerRunner(applicationContext, JobName.LOAN_COB.name())) //
-                .start(fetchAndLockStep()).next(loanCOBStep()).next(stayedLockedStep()) //
+                .start(resolveCustomJobParametersStep()) //
+                .next(fetchAndLockStep()).next(loanCOBStep()).next(stayedLockedStep()) //
                 .incrementer(new RunIdIncrementer()) //
                 .build();
     }
+
+    @Bean
+    public ExecutionContextPromotionListener customJobParametersPromotionListener() {
+        ExecutionContextPromotionListener listener = new ExecutionContextPromotionListener();
+        listener.setKeys(new String[] { LoanCOBConstant.BUSINESS_DATE_PARAMETER_NAME });
+        return listener;
+    }
 }
diff --git a/fineract-provider/src/main/java/org/apache/fineract/cob/loan/LoanItemProcessor.java b/fineract-provider/src/main/java/org/apache/fineract/cob/loan/LoanItemProcessor.java
index daea5f970..2aa95d27b 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/cob/loan/LoanItemProcessor.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/cob/loan/LoanItemProcessor.java
@@ -31,5 +31,6 @@ public class LoanItemProcessor extends AbstractLoanItemProcessor {
     @BeforeStep
     public void beforeStep(StepExecution stepExecution) {
         setExecutionContext(stepExecution.getExecutionContext());
+        setBusinessDate(stepExecution);
     }
 }
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/service/executealldirtyjobs/ExecuteAllDirtyJobsTasklet.java b/fineract-provider/src/main/java/org/apache/fineract/cob/loan/ResolveLoanCOBCustomJobParametersTasklet.java
similarity index 53%
copy from fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/service/executealldirtyjobs/ExecuteAllDirtyJobsTasklet.java
copy to fineract-provider/src/main/java/org/apache/fineract/cob/loan/ResolveLoanCOBCustomJobParametersTasklet.java
index 28feabe88..62d3a339f 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/service/executealldirtyjobs/ExecuteAllDirtyJobsTasklet.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/cob/loan/ResolveLoanCOBCustomJobParametersTasklet.java
@@ -16,37 +16,26 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.fineract.infrastructure.jobs.service.executealldirtyjobs;
+package org.apache.fineract.cob.loan;
 
-import java.util.List;
 import lombok.RequiredArgsConstructor;
-import org.apache.fineract.infrastructure.core.config.FineractProperties;
-import org.apache.fineract.infrastructure.jobs.domain.ScheduledJobDetail;
-import org.apache.fineract.infrastructure.jobs.domain.ScheduledJobDetailRepository;
-import org.apache.fineract.infrastructure.jobs.service.JobRegisterService;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.fineract.cob.common.CustomJobParameterResolver;
 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.stereotype.Component;
 
+@Slf4j
 @RequiredArgsConstructor
-@Component
-public class ExecuteAllDirtyJobsTasklet implements Tasklet {
+public class ResolveLoanCOBCustomJobParametersTasklet implements Tasklet {
 
-    private final JobRegisterService jobRegisterService;
-    private final ScheduledJobDetailRepository scheduledJobDetailsRepository;
-    private final FineractProperties fineractProperties;
+    private final CustomJobParameterResolver customJobParameterResolver;
 
     @Override
     public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception {
-        List<ScheduledJobDetail> jobDetails = scheduledJobDetailsRepository.findAllMismatchedJobs(true);
-
-        for (ScheduledJobDetail scheduledJobDetail : jobDetails) {
-            if (scheduledJobDetail.getNodeId().toString().equals(fineractProperties.getNodeId())) {
-                jobRegisterService.executeJob(scheduledJobDetail.getId());
-            }
-        }
+        customJobParameterResolver.resolve(contribution, chunkContext, LoanCOBConstant.BUSINESS_DATE_PARAMETER_NAME,
+                LoanCOBConstant.BUSINESS_DATE_PARAMETER_NAME);
         return RepeatStatus.FINISHED;
     }
 }
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 f4efc788e..a62de399a 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
@@ -18,6 +18,7 @@
  */
 package org.apache.fineract.cob.loan;
 
+import java.time.LocalDate;
 import java.util.List;
 import lombok.RequiredArgsConstructor;
 import org.apache.fineract.portfolio.loanaccount.domain.LoanRepository;
@@ -28,7 +29,7 @@ public class RetrieveAllNonClosedLoanIdServiceImpl implements RetrieveLoanIdServ
     private final LoanRepository loanRepository;
 
     @Override
-    public List<Long> retrieveLoanIds() {
-        return loanRepository.findAllNonClosedLoanIds();
+    public List<Long> retrieveLoanIdsNDaysBehind(Long numberOfDays, LocalDate businessDate) {
+        return loanRepository.findAllNonClosedLoanIdsOneDayBehind(businessDate.minusDays(numberOfDays));
     }
 }
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 266489ecb..e1c7d9fc0 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
@@ -18,9 +18,10 @@
  */
 package org.apache.fineract.cob.loan;
 
+import java.time.LocalDate;
 import java.util.List;
 
 public interface RetrieveLoanIdService {
 
-    List<Long> retrieveLoanIds();
+    List<Long> retrieveLoanIdsNDaysBehind(Long numberOfDays, LocalDate businessDate);
 }
diff --git a/fineract-provider/src/main/java/org/apache/fineract/cob/service/InlineLoanCOBExecutorServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/cob/service/InlineLoanCOBExecutorServiceImpl.java
index 5e2f4aac1..5cb920edf 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/cob/service/InlineLoanCOBExecutorServiceImpl.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/cob/service/InlineLoanCOBExecutorServiceImpl.java
@@ -33,11 +33,14 @@ 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.cob.exceptions.LoanAccountLockCannotBeOverruledException;
+import org.apache.fineract.cob.loan.LoanCOBConstant;
+import org.apache.fineract.infrastructure.businessdate.domain.BusinessDateType;
 import org.apache.fineract.infrastructure.core.api.JsonCommand;
 import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
 import org.apache.fineract.infrastructure.core.data.CommandProcessingResultBuilder;
 import org.apache.fineract.infrastructure.core.exception.PlatformInternalServerException;
 import org.apache.fineract.infrastructure.core.serialization.GoogleGsonSerializerHelper;
+import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil;
 import org.apache.fineract.infrastructure.jobs.domain.CustomJobParameter;
 import org.apache.fineract.infrastructure.jobs.domain.CustomJobParameterRepository;
 import org.apache.fineract.infrastructure.jobs.exception.JobNotFoundException;
@@ -141,11 +144,13 @@ public class InlineLoanCOBExecutorServiceImpl implements InlineExecutorService<L
     private Map<String, JobParameter> getJobParametersMap(List<Long> loanIds) {
         // TODO: refactor for a more generic solution
         String parameterJson = gson.toJson(loanIds);
-        CustomJobParameter customJobParameter = new CustomJobParameter();
-        customJobParameter.setParameterJson(parameterJson);
-        Long customJobParameterId = customJobParameterRepository.saveAndFlush(customJobParameter).getId();
+        CustomJobParameter loanIdsJobParameter = new CustomJobParameter();
+        loanIdsJobParameter.setParameterJson(parameterJson);
+        Long loanIdsJobParameterId = customJobParameterRepository.saveAndFlush(loanIdsJobParameter).getId();
         Map<String, JobParameter> jobParameterMap = new HashMap<>();
-        jobParameterMap.put(SpringBatchJobConstants.CUSTOM_JOB_PARAMETER_ID_KEY, new JobParameter(customJobParameterId));
+        jobParameterMap.put(SpringBatchJobConstants.CUSTOM_JOB_PARAMETER_ID_KEY, new JobParameter(loanIdsJobParameterId));
+        String businessDate = ThreadLocalContextUtil.getBusinessDateByType(BusinessDateType.COB_DATE).toString();
+        jobParameterMap.put(LoanCOBConstant.BUSINESS_DATE_PARAMETER_NAME, new JobParameter(businessDate));
         return jobParameterMap;
     }
 
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/api/SchedulerJobApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/api/SchedulerJobApiResource.java
index 7746d8c70..37750755d 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/api/SchedulerJobApiResource.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/api/SchedulerJobApiResource.java
@@ -128,9 +128,11 @@ public class SchedulerJobApiResource {
     @POST
     @Path("{" + SchedulerJobApiConstants.JOB_ID + "}")
     @Operation(summary = "Run a Job", description = "Manually Execute Specific Job.")
+    @RequestBody(content = @Content(schema = @Schema(implementation = SchedulerJobApiResourceSwagger.ExecuteJobRequest.class)))
     @ApiResponses({ @ApiResponse(responseCode = "200", description = "POST: jobs/1?command=executeJob") })
     public Response executeJob(@PathParam(SchedulerJobApiConstants.JOB_ID) @Parameter(description = "jobId") final Long jobId,
-            @QueryParam(SchedulerJobApiConstants.COMMAND) @Parameter(description = "command") final String commandParam) {
+            @QueryParam(SchedulerJobApiConstants.COMMAND) @Parameter(description = "command") final String commandParam,
+            @Parameter(hidden = true) final String jsonRequestBody) {
         // check the logged in user have permissions to execute scheduler jobs
         Response response;
         if (fineractProperties.getMode().isBatchManagerEnabled()) {
@@ -142,7 +144,7 @@ public class SchedulerJobApiResource {
             }
             response = Response.status(400).build();
             if (is(commandParam, SchedulerJobApiConstants.COMMAND_EXECUTE_JOB)) {
-                this.jobRegisterService.executeJob(jobId);
+                jobRegisterService.executeJobWithParameters(jobId, jsonRequestBody);
                 response = Response.status(202).build();
             } else {
                 throw new UnrecognizedQueryParamException(SchedulerJobApiConstants.COMMAND, commandParam);
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/api/SchedulerJobApiResourceSwagger.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/api/SchedulerJobApiResourceSwagger.java
index 1224abe06..81bfec247 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/api/SchedulerJobApiResourceSwagger.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/api/SchedulerJobApiResourceSwagger.java
@@ -22,6 +22,7 @@ import io.swagger.v3.oas.annotations.media.Schema;
 import java.util.Date;
 import java.util.List;
 import org.apache.fineract.infrastructure.jobs.data.JobDetailHistoryData;
+import org.apache.fineract.infrastructure.jobs.data.JobParameterDTO;
 
 /**
  * Created by sanyam on 12/8/17.
@@ -100,4 +101,16 @@ final class SchedulerJobApiResourceSwagger {
         public List<JobDetailHistoryDataSwagger> pageItems;
 
     }
+
+    @Schema(description = "ExecuteJobRequest")
+    public static final class ExecuteJobRequest {
+
+        private ExecuteJobRequest() {
+
+        }
+
+        @Schema(example = "Update loan Summary")
+        public List<JobParameterDTO> jobParameters;
+
+    }
 }
diff --git a/fineract-provider/src/main/java/org/apache/fineract/cob/loan/RetrieveLoanIdService.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/data/JobParameterDTO.java
similarity index 73%
copy from fineract-provider/src/main/java/org/apache/fineract/cob/loan/RetrieveLoanIdService.java
copy to fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/data/JobParameterDTO.java
index 266489ecb..59a24df70 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/cob/loan/RetrieveLoanIdService.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/data/JobParameterDTO.java
@@ -16,11 +16,17 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.fineract.cob.loan;
+package org.apache.fineract.infrastructure.jobs.data;
 
-import java.util.List;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
 
-public interface RetrieveLoanIdService {
+@Data
+@AllArgsConstructor
+@EqualsAndHashCode
+public class JobParameterDTO {
 
-    List<Long> retrieveLoanIds();
+    private String parameterName;
+    private String parameterValue;
 }
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/service/JobParameterDataParser.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/service/JobParameterDataParser.java
new file mode 100644
index 000000000..8aab8271c
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/service/JobParameterDataParser.java
@@ -0,0 +1,59 @@
+/**
+ * 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.infrastructure.jobs.service;
+
+import com.google.gson.JsonArray;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+import com.google.gson.JsonParser;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Objects;
+import java.util.Set;
+import lombok.RequiredArgsConstructor;
+import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
+import org.apache.fineract.infrastructure.jobs.data.JobParameterDTO;
+import org.springframework.stereotype.Component;
+
+@Component
+@RequiredArgsConstructor
+public class JobParameterDataParser {
+
+    private final FromJsonHelper jsonHelper;
+
+    public Set<JobParameterDTO> parseExecution(String requestBody) {
+        if (Objects.isNull(requestBody)) {
+            return Collections.emptySet();
+        }
+        JsonObject element = JsonParser.parseString(requestBody).getAsJsonObject();
+        if (Objects.isNull(element)) {
+            return Collections.emptySet();
+        }
+        JsonArray jobParametersJsonArray = jsonHelper.extractJsonArrayNamed("jobParameters", element);
+        if (Objects.isNull(jobParametersJsonArray)) {
+            return Collections.emptySet();
+        }
+        Set<JobParameterDTO> jobParameters = new HashSet<>();
+        for (final JsonElement jobParameterElement : jobParametersJsonArray) {
+            jobParameters.add(new JobParameterDTO(jobParameterElement.getAsJsonObject().get("parameterName").getAsString(),
+                    jobParameterElement.getAsJsonObject().get("parameterValue").getAsString()));
+        }
+        return Collections.unmodifiableSet(jobParameters);
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/service/JobRegisterService.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/service/JobRegisterService.java
index 67dc8c2a4..71810c714 100755
--- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/service/JobRegisterService.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/service/JobRegisterService.java
@@ -22,7 +22,7 @@ import org.apache.fineract.infrastructure.jobs.domain.ScheduledJobDetail;
 
 public interface JobRegisterService {
 
-    void executeJob(Long jobId);
+    void executeJobWithParameters(Long jobId, String jobParametersJson);
 
     void rescheduleJob(Long jobId);
 
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/service/JobRegisterServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/service/JobRegisterServiceImpl.java
index d5ae619f8..37f67a200 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/service/JobRegisterServiceImpl.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/service/JobRegisterServiceImpl.java
@@ -20,15 +20,18 @@ package org.apache.fineract.infrastructure.jobs.service;
 
 import com.google.common.base.Splitter;
 import java.text.ParseException;
+import java.util.Collections;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Properties;
+import java.util.Set;
 import java.util.TimeZone;
 import lombok.extern.slf4j.Slf4j;
 import org.apache.fineract.infrastructure.core.config.FineractProperties;
 import org.apache.fineract.infrastructure.core.domain.FineractPlatformTenant;
 import org.apache.fineract.infrastructure.core.exception.PlatformInternalServerException;
 import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil;
+import org.apache.fineract.infrastructure.jobs.data.JobParameterDTO;
 import org.apache.fineract.infrastructure.jobs.domain.ScheduledJobDetail;
 import org.apache.fineract.infrastructure.jobs.domain.SchedulerDetail;
 import org.apache.fineract.infrastructure.jobs.exception.JobNodeIdMismatchingException;
@@ -61,6 +64,8 @@ import org.springframework.stereotype.Service;
 @Slf4j
 public class JobRegisterServiceImpl implements JobRegisterService, ApplicationListener<ContextClosedEvent> {
 
+    private static final String JOB_EXECUTION_FAILED_MESSAGE = "Job execution failed for job with name: ";
+
     @Autowired
     private SchedularWritePlatformService schedularWritePlatformService;
 
@@ -81,12 +86,15 @@ public class JobRegisterServiceImpl implements JobRegisterService, ApplicationLi
     @Autowired
     private JobStarter jobStarter;
 
+    @Autowired
+    private JobParameterDataParser dataParser;
+
     @Autowired
     private JobNameService jobNameService;
 
     private static final String JOB_STARTER_METHOD_NAME = "run";
 
-    public void executeJob(final ScheduledJobDetail scheduledJobDetail, String triggerType) {
+    public void executeJob(final ScheduledJobDetail scheduledJobDetail, String triggerType, Set<JobParameterDTO> jobParameterDTOSet) {
         try {
             final JobDataMap jobDataMap = new JobDataMap();
             if (triggerType == null) {
@@ -96,7 +104,7 @@ public class JobRegisterServiceImpl implements JobRegisterService, ApplicationLi
             jobDataMap.put(SchedulerServiceConstants.TENANT_IDENTIFIER, ThreadLocalContextUtil.getTenant().getTenantIdentifier());
             final String schedulerName = getSchedulerName(scheduledJobDetail);
             final Scheduler scheduler = SCHEDULERS.get(schedulerName);
-            final JobDetail jobDetail = createJobDetail(scheduledJobDetail);
+            final JobDetail jobDetail = createJobDetail(scheduledJobDetail, jobParameterDTOSet);
             JobKey jobKey = jobDetail.getKey();
             if (scheduler == null || !scheduler.checkExists(jobKey)) {
                 SchedulerStopListener schedulerStopListener = new SchedulerStopListener(this);
@@ -157,7 +165,7 @@ public class JobRegisterServiceImpl implements JobRegisterService, ApplicationLi
                 for (final ScheduledJobDetail jobDetail : scheduledJobDetails) {
                     if (jobDetail.isTriggerMisfired() || jobDetail.isMismatchedJob()) {
                         if (jobDetail.isActiveSchedular()) {
-                            executeJob(jobDetail, SchedulerServiceConstants.TRIGGER_TYPE_CRON);
+                            executeJob(jobDetail, SchedulerServiceConstants.TRIGGER_TYPE_CRON, Collections.emptySet());
                             jobDetail.setMismatchedJob(false);
                         }
                         final String schedulerName = getSchedulerName(jobDetail);
@@ -198,7 +206,8 @@ public class JobRegisterServiceImpl implements JobRegisterService, ApplicationLi
     }
 
     @Override
-    public void executeJob(final Long jobId) {
+    public void executeJobWithParameters(final Long jobId, String jobParametersJson) {
+        Set<JobParameterDTO> jobParameterDTOSet = dataParser.parseExecution(jobParametersJson);
         final ScheduledJobDetail scheduledJobDetail = this.schedularWritePlatformService.findByJobId(jobId);
         if (scheduledJobDetail == null) {
             throw new JobNotFoundException(String.valueOf(jobId));
@@ -206,7 +215,7 @@ public class JobRegisterServiceImpl implements JobRegisterService, ApplicationLi
         final String nodeIdStored = scheduledJobDetail.getNodeId().toString();
 
         if (nodeIdStored.equals(fineractProperties.getNodeId()) || nodeIdStored.equals("0")) {
-            executeJob(scheduledJobDetail, null);
+            executeJob(scheduledJobDetail, null, jobParameterDTOSet);
             scheduledJobDetail.setMismatchedJob(false);
             this.schedularWritePlatformService.saveOrUpdate(scheduledJobDetail);
         } else {
@@ -234,7 +243,7 @@ public class JobRegisterServiceImpl implements JobRegisterService, ApplicationLi
     @Override
     public void scheduleJob(final ScheduledJobDetail scheduledJobDetails) {
         try {
-            final JobDetail jobDetail = createJobDetail(scheduledJobDetails);
+            final JobDetail jobDetail = createJobDetail(scheduledJobDetails, Collections.emptySet());
             scheduledJobDetails.setJobKey(getJobKeyAsString(jobDetail.getKey()));
             if (!scheduledJobDetails.isActiveSchedular()) {
                 scheduledJobDetails.setNextRunTime(null);
@@ -315,7 +324,8 @@ public class JobRegisterServiceImpl implements JobRegisterService, ApplicationLi
         return schedulerFactoryBean.getScheduler();
     }
 
-    private JobDetail createJobDetail(final ScheduledJobDetail scheduledJobDetail) throws Exception {
+    private JobDetail createJobDetail(final ScheduledJobDetail scheduledJobDetail, Set<JobParameterDTO> jobParameterDTOSet)
+            throws Exception {
         final FineractPlatformTenant tenant = ThreadLocalContextUtil.getTenant();
 
         JobNameData jobName = jobNameService.getJobByHumanReadableName(scheduledJobDetail.getJobName());
@@ -328,7 +338,7 @@ public class JobRegisterServiceImpl implements JobRegisterService, ApplicationLi
         jobDetailFactoryBean.setGroup(scheduledJobDetail.getGroupName());
         jobDetailFactoryBean.setConcurrent(false);
 
-        jobDetailFactoryBean.setArguments(job, scheduledJobDetail, ThreadLocalContextUtil.getContext());
+        jobDetailFactoryBean.setArguments(job, scheduledJobDetail, ThreadLocalContextUtil.getContext(), jobParameterDTOSet);
         jobDetailFactoryBean.afterPropertiesSet();
         return jobDetailFactoryBean.getObject();
     }
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/service/JobStarter.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/service/JobStarter.java
index 9e72cd54b..43e0e3df5 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/service/JobStarter.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/service/JobStarter.java
@@ -18,14 +18,20 @@
  */
 package org.apache.fineract.infrastructure.jobs.service;
 
+import java.util.Collections;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
 import lombok.RequiredArgsConstructor;
 import org.apache.fineract.infrastructure.core.domain.FineractContext;
 import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil;
+import org.apache.fineract.infrastructure.jobs.data.JobParameterDTO;
 import org.apache.fineract.infrastructure.jobs.domain.JobParameterRepository;
 import org.apache.fineract.infrastructure.jobs.domain.ScheduledJobDetail;
+import org.apache.fineract.infrastructure.jobs.service.jobname.JobNameService;
+import org.apache.fineract.infrastructure.jobs.service.jobparameterprovider.JobParameterProvider;
 import org.springframework.batch.core.Job;
 import org.springframework.batch.core.JobParameter;
 import org.springframework.batch.core.JobParameters;
@@ -45,14 +51,19 @@ public class JobStarter {
     private final JobExplorer jobExplorer;
     private final JobLauncher jobLauncher;
     private final JobParameterRepository jobParameterRepository;
+    private final List<JobParameterProvider> jobParameterProviders;
+    private final JobNameService jobNameService;
 
-    public void run(Job job, ScheduledJobDetail scheduledJobDetail, FineractContext fineractContext)
-            throws JobInstanceAlreadyCompleteException, JobExecutionAlreadyRunningException, JobParametersInvalidException,
-            JobRestartException {
+    public void run(Job job, ScheduledJobDetail scheduledJobDetail, FineractContext fineractContext,
+            Set<JobParameterDTO> jobParameterDTOSet) throws JobInstanceAlreadyCompleteException, JobExecutionAlreadyRunningException,
+            JobParametersInvalidException, JobRestartException {
         ThreadLocalContextUtil.init(fineractContext);
         Map<String, JobParameter> jobParameterMap = getJobParameter(scheduledJobDetail);
         JobParameters jobParameters = new JobParametersBuilder(jobExplorer).getNextJobParameters(job)
-                .addJobParameters(new JobParameters(jobParameterMap)).toJobParameters();
+                .addJobParameters(new JobParameters(jobParameterMap))
+                .addJobParameters(new JobParameters(provideCustomJobParameters(
+                        jobNameService.getJobByHumanReadableName(scheduledJobDetail.getJobName()).getEnumStyleName(), jobParameterDTOSet)))
+                .toJobParameters();
         jobLauncher.run(job, jobParameters);
     }
 
@@ -65,4 +76,10 @@ public class JobStarter {
         }
         return jobParameterMap;
     }
+
+    private Map<String, JobParameter> provideCustomJobParameters(String jobName, Set<JobParameterDTO> jobParameterDTOSet) {
+        Optional<JobParameterProvider> jobParameterProvider = jobParameterProviders.stream()
+                .filter(provider -> provider.canProvideParametersForJob(jobName)).findFirst();
+        return jobParameterProvider.map(parameterProvider -> parameterProvider.provide(jobParameterDTOSet)).orElse(Collections.emptyMap());
+    }
 }
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/service/executealldirtyjobs/ExecuteAllDirtyJobsTasklet.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/service/executealldirtyjobs/ExecuteAllDirtyJobsTasklet.java
index 28feabe88..53c878245 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/service/executealldirtyjobs/ExecuteAllDirtyJobsTasklet.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/service/executealldirtyjobs/ExecuteAllDirtyJobsTasklet.java
@@ -44,7 +44,7 @@ public class ExecuteAllDirtyJobsTasklet implements Tasklet {
 
         for (ScheduledJobDetail scheduledJobDetail : jobDetails) {
             if (scheduledJobDetail.getNodeId().toString().equals(fineractProperties.getNodeId())) {
-                jobRegisterService.executeJob(scheduledJobDetail.getId());
+                jobRegisterService.executeJobWithParameters(scheduledJobDetail.getId(), null);
             }
         }
         return RepeatStatus.FINISHED;
diff --git a/fineract-provider/src/main/java/org/apache/fineract/cob/loan/RetrieveLoanIdService.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/service/jobparameterprovider/AbstractJobParameterProvider.java
similarity index 70%
copy from fineract-provider/src/main/java/org/apache/fineract/cob/loan/RetrieveLoanIdService.java
copy to fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/service/jobparameterprovider/AbstractJobParameterProvider.java
index 266489ecb..17c478572 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/cob/loan/RetrieveLoanIdService.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/service/jobparameterprovider/AbstractJobParameterProvider.java
@@ -16,11 +16,14 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.fineract.cob.loan;
+package org.apache.fineract.infrastructure.jobs.service.jobparameterprovider;
 
-import java.util.List;
+public abstract class AbstractJobParameterProvider implements JobParameterProvider {
 
-public interface RetrieveLoanIdService {
+    @Override
+    public boolean canProvideParametersForJob(String jobName) {
+        return jobName.equals(getJobName());
+    }
 
-    List<Long> retrieveLoanIds();
+    protected abstract String getJobName();
 }
diff --git a/fineract-provider/src/main/java/org/apache/fineract/cob/loan/RetrieveLoanIdService.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/service/jobparameterprovider/JobParameterProvider.java
similarity index 65%
copy from fineract-provider/src/main/java/org/apache/fineract/cob/loan/RetrieveLoanIdService.java
copy to fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/service/jobparameterprovider/JobParameterProvider.java
index 266489ecb..e96145039 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/cob/loan/RetrieveLoanIdService.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/service/jobparameterprovider/JobParameterProvider.java
@@ -16,11 +16,16 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.fineract.cob.loan;
+package org.apache.fineract.infrastructure.jobs.service.jobparameterprovider;
 
-import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import org.apache.fineract.infrastructure.jobs.data.JobParameterDTO;
+import org.springframework.batch.core.JobParameter;
 
-public interface RetrieveLoanIdService {
+public interface JobParameterProvider {
 
-    List<Long> retrieveLoanIds();
+    Map<String, JobParameter> provide(Set<JobParameterDTO> jobParameterDTOList);
+
+    boolean canProvideParametersForJob(String jobName);
 }
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/service/jobparameterprovider/LoanCOBJobParameterProvider.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/service/jobparameterprovider/LoanCOBJobParameterProvider.java
new file mode 100644
index 000000000..f84be7816
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/service/jobparameterprovider/LoanCOBJobParameterProvider.java
@@ -0,0 +1,85 @@
+/**
+ * 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.infrastructure.jobs.service.jobparameterprovider;
+
+import com.google.gson.Gson;
+import java.time.format.DateTimeFormatter;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+import lombok.RequiredArgsConstructor;
+import org.apache.fineract.cob.loan.LoanCOBConstant;
+import org.apache.fineract.infrastructure.businessdate.domain.BusinessDateType;
+import org.apache.fineract.infrastructure.core.serialization.GoogleGsonSerializerHelper;
+import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil;
+import org.apache.fineract.infrastructure.jobs.data.JobParameterDTO;
+import org.apache.fineract.infrastructure.jobs.domain.CustomJobParameter;
+import org.apache.fineract.infrastructure.jobs.domain.CustomJobParameterRepository;
+import org.apache.fineract.infrastructure.jobs.service.JobName;
+import org.apache.fineract.infrastructure.springbatch.SpringBatchJobConstants;
+import org.springframework.batch.core.JobParameter;
+import org.springframework.beans.factory.InitializingBean;
+import org.springframework.stereotype.Component;
+import org.springframework.transaction.annotation.Transactional;
+
+@Component
+@RequiredArgsConstructor
+public class LoanCOBJobParameterProvider extends AbstractJobParameterProvider implements InitializingBean {
+
+    private final CustomJobParameterRepository customJobParameterRepository;
+    private final GoogleGsonSerializerHelper gsonFactory;
+
+    private Gson gson;
+
+    @Override
+    public void afterPropertiesSet() throws Exception {
+        this.gson = gsonFactory.createSimpleGson();
+    }
+
+    @Override
+    @Transactional
+    public Map<String, JobParameter> provide(Set<JobParameterDTO> jobParameterDTOSet) {
+        Map<String, JobParameter> jobParameterMap = new HashMap<>();
+        CustomJobParameter customJobParameter = new CustomJobParameter();
+        customJobParameter.setParameterJson(gson.toJson(getJobParameterDTOListWithCorrectBusinessDate(jobParameterDTOSet)));
+        CustomJobParameter savedCustomJobParameter = customJobParameterRepository.saveAndFlush(customJobParameter);
+        jobParameterMap.put(SpringBatchJobConstants.CUSTOM_JOB_PARAMETER_ID_KEY, new JobParameter(savedCustomJobParameter.getId()));
+        return jobParameterMap;
+    }
+
+    @Override
+    public String getJobName() {
+        return JobName.LOAN_COB.name();
+    }
+
+    private Set<JobParameterDTO> getJobParameterDTOListWithCorrectBusinessDate(Set<JobParameterDTO> jobParameterDTOset) {
+        Set<JobParameterDTO> jobParameterDTOListWithCorrectBusinessDate = jobParameterDTOset.isEmpty() ? new HashSet<>()
+                : new HashSet<>(jobParameterDTOset);
+        Optional<JobParameterDTO> optionalBusinessDateJobParameter = jobParameterDTOListWithCorrectBusinessDate.stream()
+                .filter(jobParameterDTO -> LoanCOBConstant.BUSINESS_DATE_PARAMETER_NAME.equals(jobParameterDTO.getParameterName()))
+                .findFirst();
+        if (optionalBusinessDateJobParameter.isEmpty()) {
+            jobParameterDTOListWithCorrectBusinessDate.add(new JobParameterDTO(LoanCOBConstant.BUSINESS_DATE_PARAMETER_NAME,
+                    ThreadLocalContextUtil.getBusinessDateByType(BusinessDateType.COB_DATE).format(DateTimeFormatter.ISO_DATE)));
+        }
+        return jobParameterDTOListWithCorrectBusinessDate;
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoansApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoansApiResource.java
index 95d6a082a..ad4148635 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoansApiResource.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoansApiResource.java
@@ -227,10 +227,11 @@ public class LoansApiResource {
             "interestRateFrequencyTypeOptions", "fundOptions", "repaymentStrategyOptions", "chargeOptions", "loanOfficerOptions",
             "loanPurposeOptions", "loanCollateralOptions", "chargeTemplate", "calendarOptions", "syncDisbursementWithMeeting",
             "loanCounter", "loanProductCounter", "notes", "accountLinkingOptions", "linkedAccount", "interestRateDifferential",
-            "isFloatingInterestRate", "interestRatesPeriods", LoanApiConstants.canUseForTopup, LoanApiConstants.isTopup,
-            LoanApiConstants.loanIdToClose, LoanApiConstants.topupAmount, LoanApiConstants.clientActiveLoanOptions,
-            LoanApiConstants.datatables, LoanProductConstants.RATES_PARAM_NAME, LoanApiConstants.MULTIDISBURSE_DETAILS_PARAMNAME,
-            LoanApiConstants.EMI_AMOUNT_VARIATIONS_PARAMNAME, LoanApiConstants.COLLECTION_PARAMNAME));
+            "isFloatingInterestRate", "interestRatesPeriods", "lastClosedBusinessDate", LoanApiConstants.canUseForTopup,
+            LoanApiConstants.isTopup, LoanApiConstants.loanIdToClose, LoanApiConstants.topupAmount,
+            LoanApiConstants.clientActiveLoanOptions, LoanApiConstants.datatables, LoanProductConstants.RATES_PARAM_NAME,
+            LoanApiConstants.MULTIDISBURSE_DETAILS_PARAMNAME, LoanApiConstants.EMI_AMOUNT_VARIATIONS_PARAMNAME,
+            LoanApiConstants.COLLECTION_PARAMNAME));
 
     private static final Set<String> LOAN_APPROVAL_DATA_PARAMETERS = new HashSet<>(Arrays.asList("approvalDate", "approvalAmount"));
     private static final Set<String> GLIM_ACCOUNTS_DATA_PARAMETERS = new HashSet<>(Arrays.asList("glimId", "groupId", "clientId",
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoansApiResourceSwagger.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoansApiResourceSwagger.java
index 5e5829594..fbd8777b0 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoansApiResourceSwagger.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoansApiResourceSwagger.java
@@ -978,6 +978,7 @@ final class LoansApiResourceSwagger {
         public Boolean fraud;
         @Schema(example = "250.000000")
         public Double totalOverpaid;
+        public LocalDate lastClosedBusinessDate;
     }
 
     @Schema(description = "GetLoansResponse")
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanAccountData.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanAccountData.java
index 5b149027b..caf3dc0f8 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanAccountData.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanAccountData.java
@@ -250,6 +250,7 @@ public class LoanAccountData {
 
     private CollectionData delinquent;
     private DelinquencyRangeData delinquencyRange;
+    private LocalDate lastClosedBusinessDate;
 
     public static LoanAccountData importInstanceIndividual(EnumOptionData loanTypeEnumOption, Long clientId, Long productId,
             Long loanOfficerId, LocalDate submittedOnDate, Long fundId, BigDecimal principal, Integer numberOfRepayments,
@@ -648,7 +649,8 @@ public class LoanAccountData {
             final Boolean isVariableInstallmentsAllowed, Integer minimumGap, Integer maximumGap, final EnumOptionData subStatus,
             final boolean canUseForTopup, final boolean isTopup, final Long closureLoanId, final String closureLoanAccountNo,
             final BigDecimal topupAmount, final boolean isEqualAmortization, final BigDecimal fixedPrincipalPercentagePerInstallment,
-            final DelinquencyRangeData delinquencyRange, final boolean disallowExpectedDisbursements, final boolean fraud) {
+            final DelinquencyRangeData delinquencyRange, final boolean disallowExpectedDisbursements, final boolean fraud,
+            LocalDate lastClosedBusinessDate) {
 
         final CollectionData delinquent = CollectionData.template();
 
@@ -687,7 +689,8 @@ public class LoanAccountData {
                 .setSubStatus(subStatus).setCanUseForTopup(canUseForTopup).setTopup(isTopup).setClosureLoanId(closureLoanId)
                 .setClosureLoanAccountNo(closureLoanAccountNo).setTopupAmount(topupAmount).setIsEqualAmortization(isEqualAmortization)
                 .setFixedPrincipalPercentagePerInstallment(fixedPrincipalPercentagePerInstallment).setDelinquent(delinquent)
-                .setDelinquencyRange(delinquencyRange).setDisallowExpectedDisbursements(disallowExpectedDisbursements).setFraud(fraud);
+                .setDelinquencyRange(delinquencyRange).setDisallowExpectedDisbursements(disallowExpectedDisbursements).setFraud(fraud)
+                .setLastClosedBusinessDate(lastClosedBusinessDate);
     }
 
     /*
@@ -771,7 +774,7 @@ public class LoanAccountData {
                 .setIsEqualAmortization(acc.isEqualAmortization)
                 .setFixedPrincipalPercentagePerInstallment(acc.fixedPrincipalPercentagePerInstallment)
                 .setDelinquencyRange(acc.delinquencyRange).setDisallowExpectedDisbursements(acc.disallowExpectedDisbursements)
-                .setFraud(acc.fraud);
+                .setFraud(acc.fraud).setLastClosedBusinessDate(acc.getLastClosedBusinessDate());
     }
 
     public static LoanAccountData associationsAndTemplate(final LoanAccountData acc, final Collection<LoanProductData> productOptions,
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java
index 7df1cc288..ac4b0a997 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java
@@ -432,6 +432,9 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom {
     @Column(name = "fixed_principal_percentage_per_installment", scale = 2, precision = 5, nullable = true)
     private BigDecimal fixedPrincipalPercentagePerInstallment;
 
+    @Column(name = "last_closed_business_date")
+    private LocalDate lastClosedBusinessDate;
+
     public static Loan newIndividualLoanApplication(final String accountNo, final Client client, final Integer loanType,
             final LoanProduct loanProduct, final Fund fund, final Staff officer, final CodeValue loanPurpose,
             final String transactionProcessingStrategyCode, final LoanProductRelatedDetail loanRepaymentScheduleDetail,
@@ -6920,4 +6923,11 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom {
         return ageOfOverdueDays;
     }
 
+    public LocalDate getLastClosedBusinessDate() {
+        return this.lastClosedBusinessDate;
+    }
+
+    public void setLastClosedBusinessDate(LocalDate lastClosedBusinessDate) {
+        this.lastClosedBusinessDate = lastClosedBusinessDate;
+    }
 }
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 0ddeb8d2b..6e3a1e3ed 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
@@ -71,6 +71,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_ONE_DAY_BEHIND = "select loan.id from Loan loan where loan.loanStatus in (100,200,300,303,304) and (:last_closed_business_date = 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";
 
     String FIND_BY_ACCOUNT_NUMBER = "select loan from Loan loan where loan.accountNumber = :accountNumber";
@@ -172,4 +174,8 @@ public interface LoanRepository extends JpaRepository<Loan, Long>, JpaSpecificat
 
     @Query(FIND_ID_BY_EXTERNAL_ID)
     Long findIdByExternalId(@Param("externalId") String externalId);
+
+    @Query(FIND_ALL_NON_CLOSED_ONE_DAY_BEHIND)
+    List<Long> findAllNonClosedLoanIdsOneDayBehind(@Param("last_closed_business_date") LocalDate businessDate);
+
 }
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 b8ae00ea8..16ecd7028 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
@@ -661,7 +661,7 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService {
                     + " lpvi.minimum_gap as minimuminstallmentgap, lpvi.maximum_gap as maximuminstallmentgap, "
                     + " lp.can_use_for_topup as canUseForTopup, l.is_topup as isTopup, topup.closure_loan_id as closureLoanId, "
                     + " l.total_recovered_derived as totalRecovered, topuploan.account_no as closureLoanAccountNo, "
-                    + " topup.topup_amount as topupAmount from m_loan l" //
+                    + " topup.topup_amount as topupAmount, l.last_closed_business_date as lastClosedBusinessDate from m_loan l" //
                     + " join m_product_loan lp on lp.id = l.product_id" //
                     + " left join m_loan_recalculation_details lir on lir.loan_id = l.id join m_currency rc on rc."
                     + sqlGenerator.escape("code") + " = l.currency_code" //
@@ -987,6 +987,7 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService {
             DelinquencyRangeData delinquencyRange = this.delinquencyReadPlatformService.retrieveCurrentDelinquencyTag(id);
 
             final boolean isFraud = rs.getBoolean("isFraud");
+            final LocalDate lastClosedBusinessDate = JdbcSupport.getLocalDate(rs, "lastClosedBusinessDate");
 
             return LoanAccountData.basicLoanDetails(id, accountNo, status, externalId, clientId, clientAccountNo, clientName,
                     clientOfficeId, clientExternalId, groupData, loanType, loanProductId, loanProductName, loanProductDescription,
@@ -1002,7 +1003,8 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService {
                     isNPA, daysInMonthType, daysInYearType, isInterestRecalculationEnabled, interestRecalculationData,
                     createStandingInstructionAtDisbursement, isvariableInstallmentsAllowed, minimumGap, maximumGap, loanSubStatus,
                     canUseForTopup, isTopup, closureLoanId, closureLoanAccountNo, topupAmount, isEqualAmortization,
-                    fixedPrincipalPercentagePerInstallment, delinquencyRange, disallowExpectedDisbursements, isFraud);
+                    fixedPrincipalPercentagePerInstallment, delinquencyRange, disallowExpectedDisbursements, isFraud,
+                    lastClosedBusinessDate);
         }
     }
 
diff --git a/fineract-provider/src/main/resources/db/changelog/tenant/changelog-tenant.xml b/fineract-provider/src/main/resources/db/changelog/tenant/changelog-tenant.xml
index c521010ca..f9185c1d3 100644
--- a/fineract-provider/src/main/resources/db/changelog/tenant/changelog-tenant.xml
+++ b/fineract-provider/src/main/resources/db/changelog/tenant/changelog-tenant.xml
@@ -93,5 +93,5 @@
     <include file="parts/0071_add_external_id_support_for_loan_transaction.xml" relativeToChangelogFile="true"/>
     <include file="parts/0072_add_result_and status_to_command_source.xml" relativeToChangelogFile="true" />
     <include file="parts/0073_add_result_status_code_to_command_source.xml" relativeToChangelogFile="true" />
-
+    <include file="parts/0074_add_last_cob_business_date_column_to_loan.xml" relativeToChangelogFile="true"/>
 </databaseChangeLog>
diff --git a/fineract-provider/src/main/resources/db/changelog/tenant/parts/0074_add_last_cob_business_date_column_to_loan.xml b/fineract-provider/src/main/resources/db/changelog/tenant/parts/0074_add_last_cob_business_date_column_to_loan.xml
new file mode 100644
index 000000000..9bf126b79
--- /dev/null
+++ b/fineract-provider/src/main/resources/db/changelog/tenant/parts/0074_add_last_cob_business_date_column_to_loan.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+    Licensed to the Apache Software Foundation (ASF) under one
+    or more contributor license agreements. See the NOTICE file
+    distributed with this work for additional information
+    regarding copyright ownership. The ASF licenses this file
+    to you under the Apache License, Version 2.0 (the
+    "License"); you may not use this file except in compliance
+    with the License. You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing,
+    software distributed under the License is distributed on an
+    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+    KIND, either express or implied. See the License for the
+    specific language governing permissions and limitations
+    under the License.
+
+-->
+<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
+                   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+                   xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.3.xsd">
+    <changeSet author="fineract" id="1">
+        <addColumn tableName="m_loan">
+            <column name="last_closed_business_date" type="DATE"/>
+        </addColumn>
+    </changeSet>
+</databaseChangeLog>
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 44ab858c5..2a4a16f7b 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
@@ -19,16 +19,22 @@
 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.anyLong;
 import static org.mockito.Mockito.lenient;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.verify;
 
 import io.cucumber.java8.En;
+import java.time.LocalDate;
+import java.time.ZoneId;
 import java.util.Collections;
+import java.util.HashMap;
 import java.util.List;
 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.domain.FineractPlatformTenant;
 import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil;
 import org.mockito.Mockito;
@@ -50,23 +56,26 @@ public class FetchAndLockLoanStepDefinitions implements En {
     public FetchAndLockLoanStepDefinitions() {
         Given("/^The FetchAndLockLoanTasklet.execute method with action (.*)$/", (String action) -> {
             ThreadLocalContextUtil.setTenant(new FineractPlatformTenant(1L, "default", "Default", "Asia/Kolkata", null));
+            HashMap<BusinessDateType, LocalDate> businessDateMap = new HashMap<>();
+            businessDateMap.put(BusinessDateType.COB_DATE, LocalDate.now(ZoneId.systemDefault()));
+            ThreadLocalContextUtil.setBusinessDates(businessDateMap);
             this.action = action;
 
             if ("empty loanIds".equals(action)) {
-                lenient().when(retrieveLoanIdService.retrieveLoanIds()).thenReturn(Collections.emptyList());
+                lenient().when(retrieveLoanIdService.retrieveLoanIdsNDaysBehind(anyLong(), any())).thenReturn(Collections.emptyList());
             } else if ("good".equals(action)) {
-                lenient().when(retrieveLoanIdService.retrieveLoanIds()).thenReturn(List.of(1L, 2L, 3L));
+                lenient().when(retrieveLoanIdService.retrieveLoanIdsNDaysBehind(anyLong(), any())).thenReturn(List.of(1L, 2L, 3L));
                 lenient().when(loanAccountLockRepository.findAllByLoanIdIn(Mockito.anyList())).thenReturn(Collections.emptyList());
             } else if ("soft lock".equals(action)) {
-                lenient().when(retrieveLoanIdService.retrieveLoanIds()).thenReturn(List.of(1L, 2L, 3L));
+                lenient().when(retrieveLoanIdService.retrieveLoanIdsNDaysBehind(anyLong(), any())).thenReturn(List.of(1L, 2L, 3L));
                 lenient().when(loanAccountLockRepository.findAllByLoanIdIn(Mockito.anyList()))
                         .thenReturn(List.of(new LoanAccountLock(1L, LockOwner.LOAN_COB_PARTITIONING)));
             } else if ("inline cob".equals(action)) {
-                lenient().when(retrieveLoanIdService.retrieveLoanIds()).thenReturn(List.of(1L, 2L, 3L));
+                lenient().when(retrieveLoanIdService.retrieveLoanIdsNDaysBehind(anyLong(), any())).thenReturn(List.of(1L, 2L, 3L));
                 lenient().when(loanAccountLockRepository.findAllByLoanIdIn(Mockito.anyList()))
                         .thenReturn(List.of(new LoanAccountLock(2L, LockOwner.LOAN_INLINE_COB_PROCESSING)));
             } else if ("chunk processing".equals(action)) {
-                lenient().when(retrieveLoanIdService.retrieveLoanIds()).thenReturn(List.of(1L, 2L, 3L));
+                lenient().when(retrieveLoanIdService.retrieveLoanIdsNDaysBehind(anyLong(), any())).thenReturn(List.of(1L, 2L, 3L));
                 lenient().when(loanAccountLockRepository.findAllByLoanIdIn(Mockito.anyList()))
                         .thenReturn(List.of(new LoanAccountLock(3L, LockOwner.LOAN_COB_CHUNK_PROCESSING)));
             }
@@ -74,7 +83,8 @@ public class FetchAndLockLoanStepDefinitions implements En {
             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);
         });
 
diff --git a/fineract-provider/src/test/java/org/apache/fineract/cob/loan/LoanItemProcessorStepDefinitions.java b/fineract-provider/src/test/java/org/apache/fineract/cob/loan/LoanItemProcessorStepDefinitions.java
index 543f1c03e..3c2e2f842 100644
--- a/fineract-provider/src/test/java/org/apache/fineract/cob/loan/LoanItemProcessorStepDefinitions.java
+++ b/fineract-provider/src/test/java/org/apache/fineract/cob/loan/LoanItemProcessorStepDefinitions.java
@@ -24,9 +24,12 @@ import static org.mockito.Mockito.lenient;
 import static org.mockito.Mockito.mock;
 
 import io.cucumber.java8.En;
+import java.time.LocalDate;
+import java.time.ZoneId;
 import java.util.TreeMap;
 import org.apache.fineract.cob.COBBusinessStepService;
 import org.apache.fineract.portfolio.loanaccount.domain.Loan;
+import org.springframework.batch.core.JobExecution;
 import org.springframework.batch.core.StepExecution;
 import org.springframework.batch.item.ExecutionContext;
 
@@ -47,8 +50,10 @@ public class LoanItemProcessorStepDefinitions implements En {
 
     public LoanItemProcessorStepDefinitions() {
         Given("/^The LoanItemProcessor.process method with item (.*)$/", (String loanItem) -> {
-
-            StepExecution stepExecution = new StepExecution("test", null);
+            JobExecution jobExecution = new JobExecution(1L);
+            jobExecution.getExecutionContext().put(LoanCOBConstant.BUSINESS_DATE_PARAMETER_NAME,
+                    LocalDate.now(ZoneId.systemDefault()).toString());
+            StepExecution stepExecution = new StepExecution("test", jobExecution);
             ExecutionContext stepExecutionContext = new ExecutionContext();
             stepExecutionContext.put(LoanCOBConstant.BUSINESS_STEP_MAP, treeMap);
             stepExecution.setExecutionContext(stepExecutionContext);
diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/DelinquencyBucketsIntegrationTest.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/DelinquencyBucketsIntegrationTest.java
index fbc2e73ed..63b6c627c 100644
--- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/DelinquencyBucketsIntegrationTest.java
+++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/DelinquencyBucketsIntegrationTest.java
@@ -781,7 +781,9 @@ public class DelinquencyBucketsIntegrationTest {
         }
 
         // Move the Business date to get older the loan and to have an overdue loan
+        LocalDate lastLoanCOBBusinessDate = businessDate;
         businessDate = businessDate.plusDays(50);
+        schedulerJobHelper.fastForwardTime(lastLoanCOBBusinessDate, businessDate, jobName, responseSpec);
         log.info("Current date {}", businessDate);
         BusinessDateHelper.updateBusinessDate(requestSpec, responseSpec, BusinessDateType.BUSINESS_DATE, businessDate);
         // Run Second time the Job
@@ -838,5 +840,4 @@ public class DelinquencyBucketsIntegrationTest {
         log.info("{}", chargeApplyJSON);
         return chargeApplyJSON;
     }
-
 }
diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/SchedulerJobsTestResults.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/SchedulerJobsTestResults.java
index 96d44fc25..ac8604444 100644
--- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/SchedulerJobsTestResults.java
+++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/SchedulerJobsTestResults.java
@@ -46,6 +46,7 @@ import java.util.List;
 import java.util.Locale;
 import java.util.Map;
 import java.util.TimeZone;
+import org.apache.fineract.client.models.GetLoansLoanIdResponse;
 import org.apache.fineract.infrastructure.businessdate.domain.BusinessDateType;
 import org.apache.fineract.integrationtests.common.BusinessDateHelper;
 import org.apache.fineract.integrationtests.common.ClientHelper;
@@ -648,6 +649,58 @@ public class SchedulerJobsTestResults {
         }
     }
 
+    @Test
+    public void testLoanCOBRunsOnlyOnLoansOneDayBehind() {
+        GlobalConfigurationHelper.updateIsBusinessDateEnabled(requestSpec, responseSpec, Boolean.TRUE);
+        loanTransactionHelper = new LoanTransactionHelper(requestSpec, responseSpec);
+
+        final Integer clientID = ClientHelper.createClient(requestSpec, responseSpec);
+        Assertions.assertNotNull(clientID);
+
+        Integer overdueFeeChargeId = ChargesHelper.createCharges(requestSpec, responseSpec,
+                ChargesHelper.getLoanOverdueFeeJSONWithCalculationTypePercentage("1"));
+        Assertions.assertNotNull(overdueFeeChargeId);
+
+        final Integer loanProductID = createLoanProduct(overdueFeeChargeId.toString());
+        Assertions.assertNotNull(loanProductID);
+        HashMap loanStatusHashMap;
+
+        final Integer loanID = applyForLoanApplication(clientID.toString(), loanProductID.toString(), null, "10 January 2020");
+
+        Assertions.assertNotNull(loanID);
+
+        loanStatusHashMap = LoanStatusChecker.getStatusOfLoan(requestSpec, responseSpec, loanID);
+        LoanStatusChecker.verifyLoanIsPending(loanStatusHashMap);
+
+        loanStatusHashMap = loanTransactionHelper.approveLoan("01 March 2020", loanID);
+        LoanStatusChecker.verifyLoanIsApproved(loanStatusHashMap);
+
+        String loanDetails = loanTransactionHelper.getLoanDetails(requestSpec, responseSpec, loanID);
+        loanStatusHashMap = loanTransactionHelper.disburseLoanWithNetDisbursalAmount("02 March 2020", loanID,
+                JsonPath.from(loanDetails).get("netDisbursalAmount").toString());
+        LoanStatusChecker.verifyLoanIsActive(loanStatusHashMap);
+        BusinessDateHelper.updateBusinessDate(requestSpec, responseSpec, BusinessDateType.COB_DATE, LocalDate.of(2020, 3, 2));
+        String jobName = "Loan COB";
+
+        schedulerJobHelper.executeAndAwaitJob(jobName);
+        GetLoansLoanIdResponse loan = loanTransactionHelper.getLoan(requestSpec, responseSpec, loanID);
+        Assertions.assertEquals(LocalDate.of(2020, 3, 2), loan.getLastClosedBusinessDate());
+
+        BusinessDateHelper.updateBusinessDate(requestSpec, responseSpec, BusinessDateType.COB_DATE, LocalDate.of(2020, 3, 3));
+        schedulerJobHelper.executeAndAwaitJob(jobName);
+
+        loan = loanTransactionHelper.getLoan(requestSpec, responseSpec, loanID);
+        Assertions.assertEquals(LocalDate.of(2020, 3, 3), loan.getLastClosedBusinessDate());
+
+        BusinessDateHelper.updateBusinessDate(requestSpec, responseSpec, BusinessDateType.COB_DATE, LocalDate.of(2020, 3, 5));
+        schedulerJobHelper.executeAndAwaitJob(jobName);
+
+        loan = loanTransactionHelper.getLoan(requestSpec, responseSpec, loanID);
+        Assertions.assertEquals(LocalDate.of(2020, 3, 3), loan.getLastClosedBusinessDate());
+
+        GlobalConfigurationHelper.updateIsBusinessDateEnabled(requestSpec, responseSpec, Boolean.FALSE);
+    }
+
     @Test
     public void testLoanCOBApplyPenaltyOnDue() {
         GlobalConfigurationHelper.updateIsBusinessDateEnabled(requestSpec, responseSpec, Boolean.TRUE);
@@ -688,15 +741,17 @@ public class SchedulerJobsTestResults {
         Assertions.assertEquals(0, (Integer) repaymentScheduleDataAfter.get(1).get("penaltyChargesDue"),
                 "Verifying From Penalty Charges due fot first Repayment after Successful completion of Scheduler Job");
 
-        BusinessDateHelper.updateBusinessDate(requestSpec, responseSpec, BusinessDateType.COB_DATE, LocalDate.of(2020, 4, 2));
+        LocalDate lastBusinessDateBeforeFastForward = LocalDate.of(2020, 4, 2);
+        BusinessDateHelper.updateBusinessDate(requestSpec, responseSpec, BusinessDateType.COB_DATE, lastBusinessDateBeforeFastForward);
         this.schedulerJobHelper.executeAndAwaitJob(jobName);
         repaymentScheduleDataAfter = this.loanTransactionHelper.getLoanRepaymentSchedule(requestSpec, responseSpec, loanID);
         Assertions.assertEquals(39.39f, (Float) repaymentScheduleDataAfter.get(1).get("penaltyChargesDue"),
                 "Verifying From Penalty Charges due fot first Repayment after Successful completion of Scheduler Job");
 
         GlobalConfigurationHelper.updateValueForGlobalConfiguration(this.requestSpec, this.responseSpec, "10", "1");
-
-        BusinessDateHelper.updateBusinessDate(requestSpec, responseSpec, BusinessDateType.COB_DATE, LocalDate.of(2020, 5, 2));
+        LocalDate dateToFastForward = LocalDate.of(2020, 5, 2);
+        schedulerJobHelper.fastForwardTime(lastBusinessDateBeforeFastForward, dateToFastForward, jobName, responseSpec);
+        BusinessDateHelper.updateBusinessDate(requestSpec, responseSpec, BusinessDateType.COB_DATE, dateToFastForward);
         this.schedulerJobHelper.executeAndAwaitJob(jobName);
         repaymentScheduleDataAfter = this.loanTransactionHelper.getLoanRepaymentSchedule(requestSpec, responseSpec, loanID);
         Assertions.assertEquals(0, (Integer) repaymentScheduleDataAfter.get(2).get("penaltyChargesDue"),
diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/SchedulerJobHelper.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/SchedulerJobHelper.java
index 6d6155afc..1488485bf 100644
--- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/SchedulerJobHelper.java
+++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/SchedulerJobHelper.java
@@ -31,6 +31,7 @@ import io.restassured.specification.RequestSpecification;
 import io.restassured.specification.ResponseSpecification;
 import java.time.Duration;
 import java.time.Instant;
+import java.time.LocalDate;
 import java.time.format.DateTimeFormatter;
 import java.time.temporal.ChronoUnit;
 import java.util.HashMap;
@@ -39,6 +40,7 @@ import java.util.Map;
 import java.util.concurrent.Callable;
 import java.util.function.Function;
 import java.util.stream.Collectors;
+import org.apache.fineract.infrastructure.businessdate.domain.BusinessDateType;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -203,6 +205,15 @@ public class SchedulerJobHelper {
         // JobDetailData anyway.
     }
 
+    public void fastForwardTime(LocalDate lastBusinessDateBeforeFastForward, LocalDate dateToFastForward, String jobName,
+            ResponseSpecification responseSpec) {
+        while (lastBusinessDateBeforeFastForward.isBefore(dateToFastForward)) {
+            BusinessDateHelper.updateBusinessDate(requestSpec, responseSpec, BusinessDateType.COB_DATE, lastBusinessDateBeforeFastForward);
+            executeAndAwaitJob(jobName);
+            lastBusinessDateBeforeFastForward = lastBusinessDateBeforeFastForward.plusDays(1);
+        }
+    }
+
     @SuppressWarnings("unchecked")
     private Callable<Map<String, String>> jobLastRunHistorySupplier(int jobId) {
         return () -> {