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/09/13 14:47:17 UTC

[fineract] branch develop updated: FINERACT-1678: Soft-lock Loan COB relevant loan accounts upon startup

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 44db1d7b9 FINERACT-1678: Soft-lock Loan COB relevant loan accounts upon startup
44db1d7b9 is described below

commit 44db1d7b9484cce7761d9343254f9a87ee2b2451
Author: Adam Saghy <ad...@gmail.com>
AuthorDate: Tue Sep 13 12:34:17 2022 +0200

    FINERACT-1678: Soft-lock Loan COB relevant loan accounts upon startup
---
 .../fineract/cob/loan/ApplyLoanLockTasklet.java    |   2 +-
 ...ckTasklet.java => FetchAndLockLoanTasklet.java} |  58 ++++----
 .../apache/fineract/cob/loan/LoanCOBConstant.java  |   2 +
 .../cob/loan/LoanCOBManagerConfiguration.java      |  32 ++++-
 .../fineract/cob/loan/LoanCOBPartitioner.java      |  13 +-
 .../cob/loan/LoanCOBWorkerConfiguration.java       |  29 ++--
 .../apache/fineract/cob/loan/LoanItemReader.java   |   2 +-
 .../loan/ApplyLoanLockTaskletStepDefinitions.java  |   4 +-
 .../cob/loan/FetchAndLockLoanStepDefinitions.java  | 146 +++++++++++++++++++++
 .../loan/LoanCOBPartitionerStepDefinitions.java    |  13 +-
 .../cob/loan/LoanItemReaderStepDefinitions.java    |   2 +-
 .../core}/auditing/CustomAuditingHandlerTest.java  |   3 +-
 ...tep.feature => cob.loan.applylock.step.feature} |   0
 ...ck.step.feature => cob.loan.fetch.lock.feature} |  23 ++--
 ...tioner.feature => cob.loan.partitioner.feature} |   0
 15 files changed, 247 insertions(+), 82 deletions(-)

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 9699d3caa..41f3b6110 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
@@ -67,7 +67,7 @@ public class ApplyLoanLockTasklet implements Tasklet {
             accountLockRepository.save(loanAccountLock);
         }
 
-        executionContext.put(LoanCOBWorkerConfiguration.ALREADY_LOCKED_LOAN_IDS, new ArrayList<>(alreadyUnderProcessingAccountIds));
+        executionContext.put(LoanCOBConstant.ALREADY_LOCKED_LOAN_IDS, new ArrayList<>(alreadyUnderProcessingAccountIds));
         return RepeatStatus.FINISHED;
     }
 
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/FetchAndLockLoanTasklet.java
similarity index 50%
copy from fineract-provider/src/main/java/org/apache/fineract/cob/loan/ApplyLoanLockTasklet.java
copy to fineract-provider/src/main/java/org/apache/fineract/cob/loan/FetchAndLockLoanTasklet.java
index 9699d3caa..176823ebb 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/FetchAndLockLoanTasklet.java
@@ -20,9 +20,6 @@ package org.apache.fineract.cob.loan;
 
 import java.util.ArrayList;
 import java.util.List;
-import java.util.Map;
-import java.util.function.Function;
-import java.util.stream.Collectors;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
 import org.apache.fineract.cob.domain.LoanAccountLock;
@@ -32,54 +29,51 @@ 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.item.ExecutionContext;
 import org.springframework.batch.repeat.RepeatStatus;
 
 @Slf4j
 @RequiredArgsConstructor
-public class ApplyLoanLockTasklet implements Tasklet {
+public class FetchAndLockLoanTasklet implements Tasklet {
 
-    private final LoanAccountLockRepository accountLockRepository;
+    private final LoanAccountLockRepository loanAccountLockRepository;
+
+    private final RetrieveLoanIdService retrieveLoanIdService;
 
     @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);
-        List<Long> remainingLoanIds = new ArrayList<>(loanIds);
+        List<Long> allNonClosedLoanIds = retrieveLoanIdService.retrieveLoanIds();
+        if (allNonClosedLoanIds.isEmpty()) {
+            return RepeatStatus.FINISHED;
+        }
+        List<Long> remainingIds = new ArrayList<>(allNonClosedLoanIds);
 
-        List<LoanAccountLock> accountLocks = accountLockRepository.findAllByLoanIdIn(remainingLoanIds);
+        List<LoanAccountLock> loanAccountLocks = loanAccountLockRepository.findAllByLoanIdIn(remainingIds);
 
-        List<Long> alreadyHardLockedAccountIds = accountLocks.stream()
+        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();
 
-        List<Long> alreadyUnderProcessingAccountIds = accountLocks.stream()
-                .filter(e -> LockOwner.LOAN_INLINE_COB_PROCESSING.equals(e.getLockOwner())).map(LoanAccountLock::getLoanId).toList();
+        // Remove already hard locked accounts
+        remainingIds.removeAll(alreadyMarkedForChunkProcessingLockedAccounts);
+        remainingIds.removeAll(alreadyMarkedForInlineCOBLockedAccounts);
 
-        Map<Long, LoanAccountLock> alreadySoftLockedAccountsMap = accountLocks.stream()
-                .filter(e -> LockOwner.LOAN_COB_PARTITIONING.equals(e.getLockOwner()))
-                .collect(Collectors.toMap(LoanAccountLock::getLoanId, Function.identity()));
+        List<Long> lockableLoanAccounts = new ArrayList<>(remainingIds);
+        lockableLoanAccounts.removeAll(alreadySoftLockedAccounts);
 
-        remainingLoanIds.removeAll(alreadyHardLockedAccountIds);
-        remainingLoanIds.removeAll(alreadyUnderProcessingAccountIds);
+        applySoftLock(lockableLoanAccounts);
 
-        for (Long loanId : remainingLoanIds) {
-            LoanAccountLock loanAccountLock = addLock(loanId, alreadySoftLockedAccountsMap);
-            accountLockRepository.save(loanAccountLock);
-        }
+        contribution.getStepExecution().getJobExecution().getExecutionContext().put(LoanCOBConstant.LOAN_IDS, remainingIds);
 
-        executionContext.put(LoanCOBWorkerConfiguration.ALREADY_LOCKED_LOAN_IDS, new ArrayList<>(alreadyUnderProcessingAccountIds));
         return RepeatStatus.FINISHED;
     }
 
-    private LoanAccountLock addLock(Long loanId, Map<Long, LoanAccountLock> alreadySoftLockedAccountsMap) {
-        LoanAccountLock loanAccountLock;
-        if (alreadySoftLockedAccountsMap.containsKey(loanId)) {
-            // Upgrade lock
-            loanAccountLock = alreadySoftLockedAccountsMap.get(loanId);
-            loanAccountLock.setNewLockOwner(LockOwner.LOAN_COB_CHUNK_PROCESSING);
-        } else {
-            loanAccountLock = new LoanAccountLock(loanId, LockOwner.LOAN_COB_CHUNK_PROCESSING);
+    private void applySoftLock(List<Long> alreadySoftLockedAccounts) {
+        for (Long loanId : alreadySoftLockedAccounts) {
+            LoanAccountLock loanAccountLock = new LoanAccountLock(loanId, LockOwner.LOAN_COB_PARTITIONING);
+            loanAccountLockRepository.save(loanAccountLock);
         }
-        return loanAccountLock;
     }
 }
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 e9527d6c0..067eaf05f 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
@@ -26,6 +26,8 @@ public final class LoanCOBConstant {
     public static final String BUSINESS_STEP_MAP = "businessStepMap";
     public static final String LOAN_COB_WORKER_STEP = "loanCOBWorkerStep";
 
+    public static final String ALREADY_LOCKED_LOAN_IDS = "alreadyLockedLoanIds";
+
     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 c4911703c..de3988c9b 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,19 +18,24 @@
  */
 package org.apache.fineract.cob.loan;
 
+import java.util.List;
 import org.apache.fineract.cob.COBBusinessStepService;
+import org.apache.fineract.cob.domain.LoanAccountLockRepository;
 import org.apache.fineract.cob.listener.COBExecutionListenerRunner;
 import org.apache.fineract.infrastructure.jobs.service.JobName;
 import org.apache.fineract.infrastructure.springbatch.PropertyService;
 import org.springframework.batch.core.Job;
 import org.springframework.batch.core.Step;
 import org.springframework.batch.core.configuration.annotation.JobBuilderFactory;
+import org.springframework.batch.core.configuration.annotation.JobScope;
+import org.springframework.batch.core.configuration.annotation.StepBuilderFactory;
 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.integration.config.annotation.EnableBatchIntegration;
 import org.springframework.batch.integration.partition.RemotePartitioningManagerStepBuilderFactory;
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
 import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
 import org.springframework.context.ApplicationContext;
 import org.springframework.context.annotation.Bean;
@@ -47,6 +52,8 @@ public class LoanCOBManagerConfiguration {
     @Autowired
     private RemotePartitioningManagerStepBuilderFactory stepBuilderFactory;
     @Autowired
+    private StepBuilderFactory localStepBuilderFactory;
+    @Autowired
     private PropertyService propertyService;
     @Autowired
     private DirectChannel outboundRequests;
@@ -58,27 +65,40 @@ public class LoanCOBManagerConfiguration {
     private JobExplorer jobExplorer;
     @Autowired
     private ApplicationContext applicationContext;
-    @Autowired(required = false)
+    @Autowired
     private RetrieveLoanIdService retrieveLoanIdService;
+    @Autowired
+    private LoanAccountLockRepository accountLockRepository;
 
     @Bean
-    public LoanCOBPartitioner partitioner() {
-        return new LoanCOBPartitioner(propertyService, cobBusinessStepService, jobOperator, jobExplorer, retrieveLoanIdService);
+    @JobScope
+    public LoanCOBPartitioner partitioner(@Value("#{jobExecutionContext['loanIds']}") List<Long> loanIds) {
+        return new LoanCOBPartitioner(propertyService, cobBusinessStepService, jobOperator, jobExplorer, loanIds);
     }
 
     @Bean
     public Step loanCOBStep() {
-        return stepBuilderFactory.get(JobName.LOAN_COB.name()).partitioner(LoanCOBConstant.LOAN_COB_WORKER_STEP, partitioner())
+        return stepBuilderFactory.get("Loan COB partition - Step").partitioner(LoanCOBConstant.LOAN_COB_WORKER_STEP, partitioner(null))
                 .outputChannel(outboundRequests).build();
     }
 
+    @Bean
+    public Step fetchAndLockStep() {
+        return localStepBuilderFactory.get("Fetch and Lock loan accounts - Step").tasklet(fetchAndLockLoanTasklet()).build();
+    }
+
+    @Bean
+    @JobScope
+    public FetchAndLockLoanTasklet fetchAndLockLoanTasklet() {
+        return new FetchAndLockLoanTasklet(accountLockRepository, retrieveLoanIdService);
+    }
+
     @Bean(name = "loanCOBJob")
     public Job loanCOBJob() {
         return jobBuilderFactory.get(JobName.LOAN_COB.name()) //
                 .listener(new COBExecutionListenerRunner(applicationContext, JobName.LOAN_COB.name())) //
-                .start(loanCOBStep()) //
+                .start(fetchAndLockStep()).next(loanCOBStep()) //
                 .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 aa675a91e..83abe7dd5 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
@@ -37,6 +37,7 @@ import org.springframework.batch.core.launch.JobOperator;
 import org.springframework.batch.core.launch.NoSuchJobExecutionException;
 import org.springframework.batch.core.partition.support.Partitioner;
 import org.springframework.batch.item.ExecutionContext;
+import org.springframework.util.CollectionUtils;
 
 @Slf4j
 @RequiredArgsConstructor
@@ -48,7 +49,8 @@ public class LoanCOBPartitioner implements Partitioner {
     private final COBBusinessStepService cobBusinessStepService;
     private final JobOperator jobOperator;
     private final JobExplorer jobExplorer;
-    private final RetrieveLoanIdService retrieveLoanIdService;
+
+    private final List<Long> loanIds;
 
     @NotNull
     @Override
@@ -65,15 +67,15 @@ public class LoanCOBPartitioner implements Partitioner {
 
     private Map<String, ExecutionContext> getPartitions(int partitionSize, TreeMap<Long, String> cobBusinessStepMap) {
         Map<String, ExecutionContext> partitions = new HashMap<>();
-        List<Long> allNonClosedLoanIds = retrieveLoanIdService.retrieveLoanIds();
-        if (allNonClosedLoanIds.isEmpty()) {
+
+        if (CollectionUtils.isEmpty(loanIds)) {
             stopJobExecution();
             return Map.of();
         }
         int partitionIndex = 1;
         int remainingSpace = 0;
         createNewPartition(partitions, partitionIndex, cobBusinessStepMap);
-        for (Long allNonClosedLoanId : allNonClosedLoanIds) {
+        for (Long loanId : loanIds) {
             if (remainingSpace == partitionSize) {
                 partitionIndex++;
                 createNewPartition(partitions, partitionIndex, cobBusinessStepMap);
@@ -82,7 +84,7 @@ public class LoanCOBPartitioner implements Partitioner {
             String key = PARTITION_PREFIX + partitionIndex;
             ExecutionContext executionContext = partitions.get(key);
             List<Long> data = (List<Long>) executionContext.get(LoanCOBConstant.LOAN_IDS);
-            data.add(allNonClosedLoanId);
+            data.add(loanId);
             remainingSpace++;
         }
         return partitions;
@@ -93,6 +95,7 @@ public class LoanCOBPartitioner implements Partitioner {
         ExecutionContext executionContext = new ExecutionContext();
         executionContext.put(LoanCOBConstant.LOAN_IDS, new ArrayList<Long>());
         executionContext.put(LoanCOBConstant.BUSINESS_STEP_MAP, cobBusinessStepMap);
+        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 b666dc33b..c74006872 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
@@ -28,12 +28,14 @@ import org.apache.fineract.portfolio.loanaccount.domain.Loan;
 import org.apache.fineract.portfolio.loanaccount.domain.LoanRepository;
 import org.apache.fineract.useradministration.domain.AppUserRepositoryWrapper;
 import org.springframework.batch.core.Step;
+import org.springframework.batch.core.configuration.annotation.StepBuilderFactory;
 import org.springframework.batch.core.configuration.annotation.StepScope;
 import org.springframework.batch.core.job.builder.FlowBuilder;
 import org.springframework.batch.core.job.flow.Flow;
 import org.springframework.batch.core.listener.ExecutionContextPromotionListener;
 import org.springframework.batch.integration.partition.RemotePartitioningWorkerStepBuilderFactory;
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
 import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
@@ -44,9 +46,11 @@ import org.springframework.transaction.support.TransactionTemplate;
 @ConditionalOnProperty(value = "fineract.mode.batch-worker-enabled", havingValue = "true")
 public class LoanCOBWorkerConfiguration {
 
-    public static final String ALREADY_LOCKED_LOAN_IDS = "alreadyLockedLoanIds";
     @Autowired
     private RemotePartitioningWorkerStepBuilderFactory stepBuilderFactory;
+
+    @Autowired
+    private StepBuilderFactory localStepBuilderFactory;
     @Autowired
     private PropertyService propertyService;
     @Autowired
@@ -69,17 +73,20 @@ public class LoanCOBWorkerConfiguration {
 
     @Bean
     public Flow flow() {
-        return new FlowBuilder<Flow>("cobFlow").start(initialisationStep()).next(applyLockStep()).next(loanBusinessStep()).build();
+        return new FlowBuilder<Flow>("cobFlow").start(initialisationStep(null)).next(applyLockStep(null)).next(loanBusinessStep(null))
+                .build();
     }
 
     @Bean
-    public Step initialisationStep() {
-        return stepBuilderFactory.get("Initialisation - Step").inputChannel(inboundRequests).tasklet(initialiseContext()).build();
+    @StepScope
+    public Step initialisationStep(@Value("#{stepExecutionContext['partition']}") String partitionName) {
+        return localStepBuilderFactory.get("Initialisation - Step:" + partitionName).tasklet(initialiseContext()).build();
     }
 
     @Bean
-    public Step loanBusinessStep() {
-        return stepBuilderFactory.get("Loan COB worker - Step").inputChannel(inboundRequests)
+    @StepScope
+    public Step loanBusinessStep(@Value("#{stepExecutionContext['partition']}") String partitionName) {
+        return localStepBuilderFactory.get("Loan Business - Step:" + partitionName)
                 .<Loan, Loan>chunk(propertyService.getChunkSize(JobName.LOAN_COB.name())).reader(cobWorkerItemReader())
                 .processor(cobWorkerItemProcessor()).writer(cobWorkerItemWriter()).faultTolerant().skip(Exception.class)
                 .skipLimit(propertyService.getChunkSize(JobName.LOAN_COB.name()) + 1).listener(loanItemListener())
@@ -87,13 +94,12 @@ public class LoanCOBWorkerConfiguration {
     }
 
     @Bean
-    public Step applyLockStep() {
-        return stepBuilderFactory.get("Apply lock - Step").inputChannel(inboundRequests).tasklet(applyLock()).listener(promotionListener())
-                .build();
+    @StepScope
+    public Step applyLockStep(@Value("#{stepExecutionContext['partition']}") String partitionName) {
+        return localStepBuilderFactory.get("Apply lock - Step:" + partitionName).tasklet(applyLock()).listener(promotionListener()).build();
     }
 
     @Bean
-    @StepScope
     public InitialisationTasklet initialiseContext() {
         return new InitialisationTasklet(userRepository);
     }
@@ -104,7 +110,6 @@ public class LoanCOBWorkerConfiguration {
     }
 
     @Bean
-    @StepScope
     public ApplyLoanLockTasklet applyLock() {
         return new ApplyLoanLockTasklet(accountLockRepository);
     }
@@ -132,7 +137,7 @@ public class LoanCOBWorkerConfiguration {
     @Bean
     public ExecutionContextPromotionListener promotionListener() {
         ExecutionContextPromotionListener listener = new ExecutionContextPromotionListener();
-        listener.setKeys(new String[] { ALREADY_LOCKED_LOAN_IDS });
+        listener.setKeys(new String[] { LoanCOBConstant.ALREADY_LOCKED_LOAN_IDS });
         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 15726e713..e9336f00c 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
@@ -49,7 +49,7 @@ public class LoanItemReader implements ItemReader<Loan> {
         ExecutionContext executionContext = stepExecution.getExecutionContext();
         ExecutionContext jobExecutionContext = stepExecution.getJobExecution().getExecutionContext();
         List<Long> loanIds = (List<Long>) executionContext.get(LoanCOBConstant.LOAN_IDS);
-        alreadyLockedAccounts = (List<Long>) jobExecutionContext.get(LoanCOBWorkerConfiguration.ALREADY_LOCKED_LOAN_IDS);
+        alreadyLockedAccounts = (List<Long>) jobExecutionContext.get(LoanCOBConstant.ALREADY_LOCKED_LOAN_IDS);
         remainingData = new ArrayList<>(loanIds);
     }
 
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 3b5ffa113..aa0b8dce4 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
@@ -73,8 +73,8 @@ public class ApplyLoanLockTaskletStepDefinitions implements En {
 
         Then("ApplyLoanLockTasklet.execute result should match", () -> {
             assertEquals(RepeatStatus.FINISHED, resultItem);
-            assertEquals(3L, ((List) stepContribution.getStepExecution().getExecutionContext()
-                    .get(LoanCOBWorkerConfiguration.ALREADY_LOCKED_LOAN_IDS)).get(0));
+            assertEquals(3L,
+                    ((List) stepContribution.getStepExecution().getExecutionContext().get(LoanCOBConstant.ALREADY_LOCKED_LOAN_IDS)).get(0));
             verify(this.accountLockRepository, Mockito.times(2)).save(valueCaptor.capture());
             List<LoanAccountLock> values = valueCaptor.getAllValues();
             assertEquals(2L, values.get(0).getLoanId());
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
new file mode 100644
index 000000000..44ab858c5
--- /dev/null
+++ b/fineract-provider/src/test/java/org/apache/fineract/cob/loan/FetchAndLockLoanStepDefinitions.java
@@ -0,0 +1,146 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.cob.loan;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+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.util.Collections;
+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.core.domain.FineractPlatformTenant;
+import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil;
+import org.mockito.Mockito;
+import org.springframework.batch.core.JobExecution;
+import org.springframework.batch.core.StepContribution;
+import org.springframework.batch.core.StepExecution;
+import org.springframework.batch.repeat.RepeatStatus;
+
+public class FetchAndLockLoanStepDefinitions implements En {
+
+    private final LoanAccountLockRepository loanAccountLockRepository = mock(LoanAccountLockRepository.class);
+
+    private final RetrieveLoanIdService retrieveLoanIdService = mock(RetrieveLoanIdService.class);
+    StepContribution contribution;
+    private FetchAndLockLoanTasklet fetchAndLockLoanTasklet;
+    private String action;
+    private RepeatStatus result;
+
+    public FetchAndLockLoanStepDefinitions() {
+        Given("/^The FetchAndLockLoanTasklet.execute method with action (.*)$/", (String action) -> {
+            ThreadLocalContextUtil.setTenant(new FineractPlatformTenant(1L, "default", "Default", "Asia/Kolkata", null));
+            this.action = action;
+
+            if ("empty loanIds".equals(action)) {
+                lenient().when(retrieveLoanIdService.retrieveLoanIds()).thenReturn(Collections.emptyList());
+            } else if ("good".equals(action)) {
+                lenient().when(retrieveLoanIdService.retrieveLoanIds()).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(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(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(loanAccountLockRepository.findAllByLoanIdIn(Mockito.anyList()))
+                        .thenReturn(List.of(new LoanAccountLock(3L, LockOwner.LOAN_COB_CHUNK_PROCESSING)));
+            }
+
+            JobExecution jobExecution = new JobExecution(1L);
+            StepExecution stepExecution = new StepExecution("step", jobExecution);
+            contribution = new StepContribution(stepExecution);
+
+            fetchAndLockLoanTasklet = new FetchAndLockLoanTasklet(loanAccountLockRepository, retrieveLoanIdService);
+        });
+
+        When("FetchAndLockLoanTasklet.execute method executed", () -> {
+            result = this.fetchAndLockLoanTasklet.execute(contribution, null);
+        });
+
+        Then("FetchAndLockLoanTasklet.execute result should match", () -> {
+            if ("empty steps".equals(action)) {
+                assertEquals(RepeatStatus.FINISHED, result);
+            } else if ("good".equals(action)) {
+                verify(loanAccountLockRepository, Mockito.times(3)).save(Mockito.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));
+            } else if ("soft lock".equals(action)) {
+                verify(loanAccountLockRepository, Mockito.times(2)).save(Mockito.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));
+            } else if ("inline cob".equals(action)) {
+                verify(loanAccountLockRepository, Mockito.times(2)).save(Mockito.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));
+            } else if ("chunk processing".equals(action)) {
+                verify(loanAccountLockRepository, Mockito.times(2)).save(Mockito.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));
+            }
+        });
+
+    }
+}
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 926564c96..d3892497e 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
@@ -26,7 +26,7 @@ import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.verify;
 
 import io.cucumber.java8.En;
-import java.util.Collections;
+import java.util.ArrayList;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
@@ -46,9 +46,9 @@ public class LoanCOBPartitionerStepDefinitions implements En {
     COBBusinessStepService cobBusinessStepService = mock(COBBusinessStepService.class);
     JobOperator jobOperator = mock(JobOperator.class);
     JobExplorer jobExplorer = mock(JobExplorer.class);
-    RetrieveLoanIdService retrieveLoanIdService = mock(RetrieveLoanIdService.class);
-    private LoanCOBPartitioner loanCOBPartitioner = new LoanCOBPartitioner(propertyService, cobBusinessStepService, jobOperator,
-            jobExplorer, retrieveLoanIdService);
+
+    List<Long> loanIds;
+    private LoanCOBPartitioner loanCOBPartitioner;
 
     private TreeMap<Long, String> cobBusinessMap = new TreeMap<>();
 
@@ -69,14 +69,15 @@ public class LoanCOBPartitionerStepDefinitions implements En {
                 cobBusinessMap.put(1L, "Business step");
                 lenient().when(cobBusinessStepService.getCOBBusinessStepMap(LoanCOBBusinessStep.class, LoanCOBConstant.LOAN_COB_JOB_NAME))
                         .thenReturn(cobBusinessMap);
-                lenient().when(retrieveLoanIdService.retrieveLoanIds()).thenReturn(Collections.emptyList());
+                loanIds = new ArrayList<>();
                 lenient().when(jobExplorer.findRunningJobExecutions(JobName.LOAN_COB.name())).thenThrow(new RuntimeException("fail"));
             } else if ("good".equals(action)) {
                 cobBusinessMap.put(1L, "Business step");
                 lenient().when(cobBusinessStepService.getCOBBusinessStepMap(LoanCOBBusinessStep.class, LoanCOBConstant.LOAN_COB_JOB_NAME))
                         .thenReturn(cobBusinessMap);
-                lenient().when(retrieveLoanIdService.retrieveLoanIds()).thenReturn(List.of(1L, 2L, 3L));
+                loanIds = List.of(1L, 2L, 3L);
             }
+            loanCOBPartitioner = new LoanCOBPartitioner(propertyService, cobBusinessStepService, jobOperator, jobExplorer, loanIds);
         });
 
         When("LoanCOBPartitioner.partition method executed", () -> {
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 b519ee32f..1d30d048d 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
@@ -57,7 +57,7 @@ public class LoanItemReaderStepDefinitions implements En {
                 List<String> splitLockedAccountsStr = Splitter.on(',').splitToList(lockedAccounts);
                 splitLockedAccounts = splitLockedAccountsStr.stream().map(Long::parseLong).toList();
             }
-            jobExecutionContext.put(LoanCOBWorkerConfiguration.ALREADY_LOCKED_LOAN_IDS, new ArrayList<>(splitLockedAccounts));
+            jobExecutionContext.put(LoanCOBConstant.ALREADY_LOCKED_LOAN_IDS, new ArrayList<>(splitLockedAccounts));
             jobExecution.setExecutionContext(jobExecutionContext);
             StepExecution stepExecution = new StepExecution("test", jobExecution);
             ExecutionContext stepExecutionContext = new ExecutionContext();
diff --git a/fineract-provider/src/test/java/org/springframework/data/auditing/CustomAuditingHandlerTest.java b/fineract-provider/src/test/java/org/apache/fineract/infrastructure/core/auditing/CustomAuditingHandlerTest.java
similarity index 98%
rename from fineract-provider/src/test/java/org/springframework/data/auditing/CustomAuditingHandlerTest.java
rename to fineract-provider/src/test/java/org/apache/fineract/infrastructure/core/auditing/CustomAuditingHandlerTest.java
index 39f37c40e..5fc70c283 100644
--- a/fineract-provider/src/test/java/org/springframework/data/auditing/CustomAuditingHandlerTest.java
+++ b/fineract-provider/src/test/java/org/apache/fineract/infrastructure/core/auditing/CustomAuditingHandlerTest.java
@@ -16,7 +16,7 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.springframework.data.auditing;
+package org.apache.fineract.infrastructure.core.auditing;
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertTrue;
@@ -28,7 +28,6 @@ import java.time.ZoneId;
 import java.util.HashMap;
 import java.util.Map;
 import org.apache.fineract.infrastructure.businessdate.domain.BusinessDateType;
-import org.apache.fineract.infrastructure.core.auditing.CustomAuditingHandler;
 import org.apache.fineract.infrastructure.core.domain.AbstractAuditableCustom;
 import org.apache.fineract.infrastructure.core.domain.AbstractAuditableWithUTCDateTimeCustom;
 import org.apache.fineract.infrastructure.core.domain.FineractPlatformTenant;
diff --git a/fineract-provider/src/test/resources/features/cob/loan/cob.applylock.step.feature b/fineract-provider/src/test/resources/features/cob/loan/cob.loan.applylock.step.feature
similarity index 100%
copy from fineract-provider/src/test/resources/features/cob/loan/cob.applylock.step.feature
copy to fineract-provider/src/test/resources/features/cob/loan/cob.loan.applylock.step.feature
diff --git a/fineract-provider/src/test/resources/features/cob/loan/cob.applylock.step.feature b/fineract-provider/src/test/resources/features/cob/loan/cob.loan.fetch.lock.feature
similarity index 61%
rename from fineract-provider/src/test/resources/features/cob/loan/cob.applylock.step.feature
rename to fineract-provider/src/test/resources/features/cob/loan/cob.loan.fetch.lock.feature
index e0423cf61..9df96a0e6 100644
--- a/fineract-provider/src/test/resources/features/cob/loan/cob.applylock.step.feature
+++ b/fineract-provider/src/test/resources/features/cob/loan/cob.loan.fetch.lock.feature
@@ -17,23 +17,18 @@
 # under the License.
 #
 
-Feature: COB Apply Loan Lock Step
+Feature: COB Loan fetch and (soft) lock Step
 
   @cob
-  Scenario Outline: ApplyLoanLockTasklet - run test
-    Given The ApplyLoanLockTasklet.execute method with action <action>
-    When ApplyLoanLockTasklet.execute method executed
-    Then ApplyLoanLockTasklet.execute result should match
+  Scenario Outline: FetchAndLockLoanTasklet - run test
+    Given The FetchAndLockLoanTasklet.execute method with action <action>
+    When FetchAndLockLoanTasklet.execute method executed
+    Then FetchAndLockLoanTasklet.execute result should match
 
     Examples:
       |action|
+      |empty loanIds|
       |good|
-
-  @cob
-  Scenario Outline: ApplyLoanLockTasklet - run test: exception
-    Given The ApplyLoanLockTasklet.execute method with action <action>
-    Then throw exception ApplyLoanLockTasklet.execute method
-
-    Examples:
-      |action|
-      |error|
\ No newline at end of file
+      |soft lock|
+      |inline cob|
+      |chunk processing|
\ No newline at end of file
diff --git a/fineract-provider/src/test/resources/features/cob/loan/cob.partitioner.feature b/fineract-provider/src/test/resources/features/cob/loan/cob.loan.partitioner.feature
similarity index 100%
rename from fineract-provider/src/test/resources/features/cob/loan/cob.partitioner.feature
rename to fineract-provider/src/test/resources/features/cob/loan/cob.loan.partitioner.feature