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

commit aa249cc16fafc91e616681311b6889a5d4d68bf7
Author: Arnold Galovics <[email protected]>
AuthorDate: Fri Jun 2 14:00:41 2023 +0200

    FINERACT-1724: Further fixes
---
 .../api/ExternalAssetOwnersApiResource.java        |   1 -
 .../core/config/OAuth2SecurityConfig.java          |   2 +-
 .../infrastructure/core/config/SecurityConfig.java |  32 +---
 .../infrastructure/core/config/SpringConfig.java   |  10 +-
 .../core/filters/IdempotencyStoreBatchFilter.java  |  63 +++++++
 .../core/filters/IdempotencyStoreFilter.java       |  82 +--------
 .../core/filters/IdempotencyStoreHelper.java       |  73 ++++++++
 .../database/DatabaseSpecificSQLGenerator.java     |  11 ++
 .../jobs/domain/JobExecutionRepository.java        |   4 +-
 .../jobs/filter/LoanCOBApiFilter.java              | 186 +------------------
 .../jobs/filter/LoanCOBBatchPreprocessor.java      |  70 +++++++
 ...nCOBApiFilter.java => LoanCOBFilterHelper.java} | 202 +++++----------------
 .../org/apache/fineract/TestConfiguration.java     |  10 +-
 .../jobs/filter/LoanCOBApiFilterTest.java          |  52 +++---
 integration-tests/dependencies.gradle              |   2 +-
 .../bulkimport/importhandler/loan/Loan.xls         | Bin 2328576 -> 0 bytes
 .../bulkimport/importhandler/office/Office.xls     | Bin 359936 -> 0 bytes
 .../bulkimport/importhandler/savings/Savings.xls   | Bin 1843712 -> 0 bytes
 18 files changed, 344 insertions(+), 456 deletions(-)

diff --git 
a/fineract-investor/src/main/java/org/apache/fineract/investor/api/ExternalAssetOwnersApiResource.java
 
b/fineract-investor/src/main/java/org/apache/fineract/investor/api/ExternalAssetOwnersApiResource.java
index c6b980a44..e1ae283d5 100644
--- 
a/fineract-investor/src/main/java/org/apache/fineract/investor/api/ExternalAssetOwnersApiResource.java
+++ 
b/fineract-investor/src/main/java/org/apache/fineract/investor/api/ExternalAssetOwnersApiResource.java
@@ -26,7 +26,6 @@ import io.swagger.v3.oas.annotations.parameters.RequestBody;
 import io.swagger.v3.oas.annotations.responses.ApiResponse;
 import io.swagger.v3.oas.annotations.responses.ApiResponses;
 import io.swagger.v3.oas.annotations.tags.Tag;
-import java.util.List;
 import jakarta.ws.rs.Consumes;
 import jakarta.ws.rs.GET;
 import jakarta.ws.rs.POST;
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/config/OAuth2SecurityConfig.java
 
b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/config/OAuth2SecurityConfig.java
index 37caef0a9..a59b12375 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/config/OAuth2SecurityConfig.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/config/OAuth2SecurityConfig.java
@@ -61,7 +61,7 @@ import org.springframework.security.web.SecurityFilterChain;
 import 
org.springframework.security.web.authentication.www.BasicAuthenticationFilter;
 import org.springframework.security.web.context.SecurityContextHolderFilter;
 
-@Configuration(proxyBeanMethods = false)
+@Configuration
 @ConditionalOnProperty("fineract.security.oauth.enabled")
 @EnableMethodSecurity
 public class OAuth2SecurityConfig {
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/config/SecurityConfig.java
 
b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/config/SecurityConfig.java
index 7fb093c6b..51ab4a148 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/config/SecurityConfig.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/config/SecurityConfig.java
@@ -23,8 +23,6 @@ import static 
org.springframework.security.authorization.AuthenticatedAuthorizat
 import static 
org.springframework.security.authorization.AuthorityAuthorizationManager.hasAuthority;
 import static 
org.springframework.security.authorization.AuthorizationManagers.allOf;
 
-import org.apache.fineract.cob.service.InlineLoanCOBExecutorServiceImpl;
-import org.apache.fineract.cob.service.LoanAccountLockService;
 import org.apache.fineract.commands.domain.CommandSourceRepository;
 import org.apache.fineract.commands.service.CommandSourceService;
 import 
org.apache.fineract.infrastructure.businessdate.service.BusinessDateReadPlatformService;
@@ -33,12 +31,14 @@ import 
org.apache.fineract.infrastructure.configuration.domain.ConfigurationDoma
 import 
org.apache.fineract.infrastructure.core.domain.FineractRequestContextHolder;
 import org.apache.fineract.infrastructure.core.filters.CorrelationHeaderFilter;
 import org.apache.fineract.infrastructure.core.filters.IdempotencyStoreFilter;
+import org.apache.fineract.infrastructure.core.filters.IdempotencyStoreHelper;
 import org.apache.fineract.infrastructure.core.filters.RequestResponseFilter;
 import org.apache.fineract.infrastructure.core.filters.ResponseCorsFilter;
 import 
org.apache.fineract.infrastructure.core.serialization.ToApiJsonSerializer;
 import org.apache.fineract.infrastructure.core.service.MDCWrapper;
 import 
org.apache.fineract.infrastructure.instancemode.filter.FineractInstanceModeApiFilter;
 import org.apache.fineract.infrastructure.jobs.filter.LoanCOBApiFilter;
+import org.apache.fineract.infrastructure.jobs.filter.LoanCOBFilterHelper;
 import org.apache.fineract.infrastructure.security.data.PlatformRequestLog;
 import 
org.apache.fineract.infrastructure.security.filter.InsecureTwoFactorAuthenticationFilter;
 import 
org.apache.fineract.infrastructure.security.filter.TenantAwareBasicAuthenticationFilter;
@@ -48,9 +48,6 @@ import 
org.apache.fineract.infrastructure.security.service.PlatformSecurityConte
 import 
org.apache.fineract.infrastructure.security.service.TenantAwareJpaPlatformUserDetailsService;
 import org.apache.fineract.infrastructure.security.service.TwoFactorService;
 import org.apache.fineract.notification.service.UserNotificationService;
-import 
org.apache.fineract.portfolio.loanaccount.domain.GLIMAccountInfoRepository;
-import org.apache.fineract.portfolio.loanaccount.domain.LoanRepository;
-import 
org.apache.fineract.portfolio.loanaccount.rescheduleloan.domain.LoanRescheduleRequestRepository;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
 import org.springframework.boot.autoconfigure.web.ServerProperties;
@@ -69,10 +66,10 @@ import 
org.springframework.security.crypto.password.PasswordEncoder;
 import org.springframework.security.web.SecurityFilterChain;
 import org.springframework.security.web.access.ExceptionTranslationFilter;
 import 
org.springframework.security.web.authentication.www.BasicAuthenticationEntryPoint;
+import org.springframework.security.web.context.SecurityContextHolderFilter;
 import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
-import org.springframework.transaction.PlatformTransactionManager;
 
-@Configuration(proxyBeanMethods = false)
+@Configuration
 @ConditionalOnProperty("fineract.security.basicauth.enabled")
 @EnableMethodSecurity
 public class SecurityConfig {
@@ -110,19 +107,11 @@ public class SecurityConfig {
     private FineractRequestContextHolder fineractRequestContextHolder;
 
     @Autowired
-    private GLIMAccountInfoRepository glimAccountInfoRepository;
-    @Autowired
-    private LoanAccountLockService loanAccountLockService;
+    private LoanCOBFilterHelper loanCOBFilterHelper;
     @Autowired
     private PlatformSecurityContext context;
     @Autowired
-    private InlineLoanCOBExecutorServiceImpl inlineLoanCOBExecutorService;
-    @Autowired
-    private LoanRepository loanRepository;
-    @Autowired
-    private LoanRescheduleRequestRepository loanRescheduleRequestRepository;
-    @Autowired
-    private PlatformTransactionManager transactionManager;
+    private IdempotencyStoreHelper idempotencyStoreHelper;
 
     @Bean
     public SecurityFilterChain authorizationFilterChain(HttpSecurity http) 
throws Exception {
@@ -152,14 +141,13 @@ public class SecurityConfig {
     public SecurityFilterChain filterChain(HttpSecurity http) throws Exception 
{
         http //
                 .csrf((csrf) -> csrf.disable()) // NOSONAR only creating a 
service that is used by non-browser clients
-                .securityContext((securityContext) -> 
securityContext.requireExplicitSave(false))
                 .sessionManagement((smc) -> 
smc.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) //
+                .addFilterBefore(tenantAwareBasicAuthenticationFilter(), 
SecurityContextHolderFilter.class)
                 .addFilterAfter(requestResponseFilter(), 
ExceptionTranslationFilter.class)
                 .addFilterAfter(correlationHeaderFilter(), 
RequestResponseFilter.class)
                 .addFilterAfter(responseCorsFilter(), 
CorrelationHeaderFilter.class) //
                 .addFilterAfter(fineractInstanceModeApiFilter(), 
ResponseCorsFilter.class) //
-                .addFilterAfter(tenantAwareBasicAuthenticationFilter(), 
FineractInstanceModeApiFilter.class) //
-                .addFilterAfter(loanCOBApiFilter(), 
TenantAwareBasicAuthenticationFilter.class) //
+                .addFilterAfter(loanCOBApiFilter(), 
FineractInstanceModeApiFilter.class) //
                 .addFilterAfter(idempotencyStoreFilter(), 
LoanCOBApiFilter.class); //
 
         if (fineractProperties.getSecurity().getTwoFactor().isEnabled()) {
@@ -176,7 +164,7 @@ public class SecurityConfig {
     }
 
     public LoanCOBApiFilter loanCOBApiFilter() {
-        return new LoanCOBApiFilter(glimAccountInfoRepository, 
loanAccountLockService, context, inlineLoanCOBExecutorService, loanRepository, 
fineractProperties, loanRescheduleRequestRepository, transactionManager);
+        return new LoanCOBApiFilter(loanCOBFilterHelper);
     }
 
     public TwoFactorAuthenticationFilter twoFactorAuthenticationFilter() {
@@ -193,7 +181,7 @@ public class SecurityConfig {
     }
 
     public IdempotencyStoreFilter idempotencyStoreFilter() {
-        return new IdempotencyStoreFilter(commandSourceRepository, 
commandSourceService, fineractProperties, fineractRequestContextHolder);
+        return new IdempotencyStoreFilter(fineractRequestContextHolder, 
idempotencyStoreHelper, fineractProperties);
     }
 
     public CorrelationHeaderFilter correlationHeaderFilter() {
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/config/SpringConfig.java
 
b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/config/SpringConfig.java
index ef6149fb5..f4ae4a5e4 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/config/SpringConfig.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/config/SpringConfig.java
@@ -22,9 +22,11 @@ package org.apache.fineract.infrastructure.core.config;
 import org.springframework.beans.factory.config.MethodInvokingFactoryBean;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.DependsOn;
 import org.springframework.context.event.SimpleApplicationEventMulticaster;
 import org.springframework.core.task.SimpleAsyncTaskExecutor;
 import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.security.core.context.SecurityContextHolderStrategy;
 
 @Configuration
 public class SpringConfig {
@@ -39,11 +41,17 @@ public class SpringConfig {
     // The application events (for importing) rely on the inheritable thread 
local security context strategy
     // This is NOT compatible with threadpools so if we use threadpools the 
below will need to be reworked
     @Bean
-    public MethodInvokingFactoryBean methodInvokingFactoryBean() {
+    public MethodInvokingFactoryBean overrideSecurityContextHolderStrategy() {
         MethodInvokingFactoryBean mifb = new MethodInvokingFactoryBean();
         mifb.setTargetClass(SecurityContextHolder.class);
         mifb.setTargetMethod("setStrategyName");
         mifb.setArguments("MODE_INHERITABLETHREADLOCAL");
         return mifb;
     }
+
+    @Bean
+    @DependsOn("overrideSecurityContextHolderStrategy")
+    public SecurityContextHolderStrategy securityContextHolderStrategy() {
+        return SecurityContextHolder.getContextHolderStrategy();
+    }
 }
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/filters/IdempotencyStoreBatchFilter.java
 
b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/filters/IdempotencyStoreBatchFilter.java
new file mode 100644
index 000000000..b2e49222a
--- /dev/null
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/filters/IdempotencyStoreBatchFilter.java
@@ -0,0 +1,63 @@
+/**
+ * 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.core.filters;
+
+import jakarta.ws.rs.core.UriInfo;
+import java.util.Optional;
+import lombok.RequiredArgsConstructor;
+import org.apache.fineract.batch.domain.BatchRequest;
+import org.apache.fineract.batch.domain.BatchResponse;
+import org.apache.fineract.batch.domain.Header;
+import 
org.apache.fineract.commands.service.SynchronousCommandProcessingService;
+import org.apache.fineract.infrastructure.core.config.FineractProperties;
+import 
org.apache.fineract.infrastructure.core.domain.FineractRequestContextHolder;
+import org.springframework.stereotype.Component;
+
+@Component
+@RequiredArgsConstructor
+public class IdempotencyStoreBatchFilter implements BatchFilter {
+
+    private final FineractRequestContextHolder fineractRequestContextHolder;
+    private final IdempotencyStoreHelper helper;
+    private final FineractProperties fineractProperties;
+
+    @Override
+    public BatchResponse doFilter(BatchRequest batchRequest, UriInfo uriInfo, 
BatchFilterChain chain) {
+        
extractIdempotentKeyFromBatchRequest(batchRequest).ifPresent(idempotentKey -> 
fineractRequestContextHolder
+                
.setAttribute(SynchronousCommandProcessingService.IDEMPOTENCY_KEY_ATTRIBUTE, 
idempotentKey));
+        BatchResponse result = chain.serviceCall(batchRequest, uriInfo);
+        Optional<Long> commandId = helper.getCommandId(null);
+        boolean isSuccessWithoutStored = helper.isStoreIdempotencyKey(null) && 
commandId.isPresent();
+        if (isSuccessWithoutStored) {
+            helper.storeCommandResult(true, result.getStatusCode(), 
result.getBody(), commandId);
+        }
+        return result;
+    }
+
+    private Optional<String> extractIdempotentKeyFromBatchRequest(BatchRequest 
request) {
+        if (request.getHeaders() == null) {
+            return Optional.empty();
+        }
+        return request.getHeaders() //
+                .stream().filter(header -> 
header.getName().equals(fineractProperties.getIdempotencyKeyHeaderName())) //
+                .map(Header::getValue) //
+                .findAny(); //
+
+    }
+}
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/filters/IdempotencyStoreFilter.java
 
b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/filters/IdempotencyStoreFilter.java
index 6270b8e20..8a809b513 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/filters/IdempotencyStoreFilter.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/filters/IdempotencyStoreFilter.java
@@ -22,7 +22,6 @@ import jakarta.servlet.FilterChain;
 import jakarta.servlet.ServletException;
 import jakarta.servlet.http.HttpServletRequest;
 import jakarta.servlet.http.HttpServletResponse;
-import jakarta.ws.rs.core.UriInfo;
 import java.io.IOException;
 import java.nio.charset.StandardCharsets;
 import java.util.Optional;
@@ -30,11 +29,6 @@ import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
 import org.apache.commons.lang3.mutable.Mutable;
 import org.apache.commons.lang3.mutable.MutableObject;
-import org.apache.fineract.batch.domain.BatchRequest;
-import org.apache.fineract.batch.domain.BatchResponse;
-import org.apache.fineract.batch.domain.Header;
-import org.apache.fineract.commands.domain.CommandSourceRepository;
-import org.apache.fineract.commands.service.CommandSourceService;
 import 
org.apache.fineract.commands.service.SynchronousCommandProcessingService;
 import org.apache.fineract.infrastructure.core.config.FineractProperties;
 import 
org.apache.fineract.infrastructure.core.domain.FineractRequestContextHolder;
@@ -44,31 +38,28 @@ import 
org.springframework.web.util.ContentCachingResponseWrapper;
 
 @RequiredArgsConstructor
 @Slf4j
-public class IdempotencyStoreFilter extends OncePerRequestFilter implements 
BatchFilter {
-
-    private final CommandSourceRepository commandSourceRepository;
-    private final CommandSourceService commandSourceService;
-
-    private final FineractProperties fineractProperties;
+public class IdempotencyStoreFilter extends OncePerRequestFilter {
 
     private final FineractRequestContextHolder fineractRequestContextHolder;
+    private final IdempotencyStoreHelper helper;
+    private final FineractProperties fineractProperties;
 
     @Override
     protected void doFilterInternal(@NotNull HttpServletRequest request, 
@NotNull HttpServletResponse response,
             @NotNull FilterChain filterChain) throws ServletException, 
IOException {
         Mutable<ContentCachingResponseWrapper> wrapper = new MutableObject<>();
-        if (isAllowedContentTypeRequest(request)) {
+        if (helper.isAllowedContentTypeRequest(request)) {
             wrapper.setValue(new ContentCachingResponseWrapper(response));
         }
         
extractIdempotentKeyFromHttpServletRequest(request).ifPresent(idempotentKey -> 
fineractRequestContextHolder
                 
.setAttribute(SynchronousCommandProcessingService.IDEMPOTENCY_KEY_ATTRIBUTE, 
idempotentKey, request));
 
         filterChain.doFilter(request, wrapper.getValue() != null ? 
wrapper.getValue() : response);
-        Optional<Long> commandId = getCommandId(request);
-        boolean isSuccessWithoutStored = isStoreIdempotencyKey(request) && 
commandId.isPresent() && isAllowedContentTypeResponse(response)
+        Optional<Long> commandId = helper.getCommandId(request);
+        boolean isSuccessWithoutStored = helper.isStoreIdempotencyKey(request) 
&& commandId.isPresent() && helper.isAllowedContentTypeResponse(response)
                 && wrapper.getValue() != null;
         if (isSuccessWithoutStored) {
-            storeCommandResult(false, response.getStatus(), 
Optional.ofNullable(wrapper.getValue())
+            helper.storeCommandResult(false, response.getStatus(), 
Optional.ofNullable(wrapper.getValue())
                     
.map(ContentCachingResponseWrapper::getContentAsByteArray).map(s -> new 
String(s, StandardCharsets.UTF_8)).orElse(null),
                     commandId);
         }
@@ -77,66 +68,7 @@ public class IdempotencyStoreFilter extends 
OncePerRequestFilter implements Batc
         }
     }
 
-    private void storeCommandResult(boolean batch, int response, String body, 
Optional<Long> commandId) {
-        
commandSourceRepository.findById(commandId.get()).ifPresent(commandSource -> {
-            commandSource.setResultStatusCode(response);
-            commandSource.setResult(body);
-            if (batch) {
-                commandSourceService.saveResultNoTransaction(commandSource);
-            } else {
-                commandSourceService.saveResult(commandSource);
-            }
-        });
-    }
-
     private Optional<String> 
extractIdempotentKeyFromHttpServletRequest(HttpServletRequest request) {
         return 
Optional.ofNullable(request.getHeader(fineractProperties.getIdempotencyKeyHeaderName()));
     }
-
-    private boolean isAllowedContentTypeResponse(HttpServletResponse response) 
{
-        return 
Optional.ofNullable(response.getContentType()).map(String::toLowerCase).map(ct 
-> ct.contains("application/json"))
-                .orElse(false) || (response.getStatus() > 200 && 
response.getStatus() < 300);
-    }
-
-    private boolean isAllowedContentTypeRequest(HttpServletRequest request) {
-        return 
Optional.ofNullable(request.getContentType()).map(String::toLowerCase).map(ct 
-> ct.contains("application/json"))
-                .orElse(false);
-    }
-
-    private boolean isStoreIdempotencyKey(HttpServletRequest request) {
-        return Optional
-                .ofNullable(
-                        
fineractRequestContextHolder.getAttribute(SynchronousCommandProcessingService.IDEMPOTENCY_KEY_STORE_FLAG,
 request))
-                
.filter(Boolean.class::isInstance).map(Boolean.class::cast).orElse(false);
-    }
-
-    private Optional<Long> getCommandId(HttpServletRequest request) {
-        return Optional
-                
.ofNullable(fineractRequestContextHolder.getAttribute(SynchronousCommandProcessingService.COMMAND_SOURCE_ID,
 request))
-                .filter(Long.class::isInstance).map(Long.class::cast);
-    }
-
-    private Optional<String> extractIdempotentKeyFromBatchRequest(BatchRequest 
request) {
-        if (request.getHeaders() == null) {
-            return Optional.empty();
-        }
-        return request.getHeaders() //
-                .stream().filter(header -> 
header.getName().equals(fineractProperties.getIdempotencyKeyHeaderName())) //
-                .map(Header::getValue) //
-                .findAny(); //
-
-    }
-
-    @Override
-    public BatchResponse doFilter(BatchRequest batchRequest, UriInfo uriInfo, 
BatchFilterChain chain) {
-        
extractIdempotentKeyFromBatchRequest(batchRequest).ifPresent(idempotentKey -> 
fineractRequestContextHolder
-                
.setAttribute(SynchronousCommandProcessingService.IDEMPOTENCY_KEY_ATTRIBUTE, 
idempotentKey));
-        BatchResponse result = chain.serviceCall(batchRequest, uriInfo);
-        Optional<Long> commandId = getCommandId(null);
-        boolean isSuccessWithoutStored = isStoreIdempotencyKey(null) && 
commandId.isPresent();
-        if (isSuccessWithoutStored) {
-            storeCommandResult(true, result.getStatusCode(), result.getBody(), 
commandId);
-        }
-        return result;
-    }
 }
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/filters/IdempotencyStoreHelper.java
 
b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/filters/IdempotencyStoreHelper.java
new file mode 100644
index 000000000..5ca5147a0
--- /dev/null
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/filters/IdempotencyStoreHelper.java
@@ -0,0 +1,73 @@
+/**
+ * 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.core.filters;
+
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+import java.util.Optional;
+import lombok.RequiredArgsConstructor;
+import org.apache.fineract.commands.domain.CommandSourceRepository;
+import org.apache.fineract.commands.service.CommandSourceService;
+import 
org.apache.fineract.commands.service.SynchronousCommandProcessingService;
+import 
org.apache.fineract.infrastructure.core.domain.FineractRequestContextHolder;
+import org.springframework.stereotype.Component;
+
+@Component
+@RequiredArgsConstructor
+public class IdempotencyStoreHelper {
+
+    private final CommandSourceRepository commandSourceRepository;
+    private final CommandSourceService commandSourceService;
+    private final FineractRequestContextHolder fineractRequestContextHolder;
+
+    public void storeCommandResult(boolean batch, int response, String body, 
Optional<Long> commandId) {
+        
commandSourceRepository.findById(commandId.get()).ifPresent(commandSource -> {
+            commandSource.setResultStatusCode(response);
+            commandSource.setResult(body);
+            if (batch) {
+                commandSourceService.saveResultNoTransaction(commandSource);
+            } else {
+                commandSourceService.saveResult(commandSource);
+            }
+        });
+    }
+
+    public boolean isAllowedContentTypeResponse(HttpServletResponse response) {
+        return 
Optional.ofNullable(response.getContentType()).map(String::toLowerCase).map(ct 
-> ct.contains("application/json"))
+                .orElse(false) || (response.getStatus() > 200 && 
response.getStatus() < 300);
+    }
+
+    public boolean isAllowedContentTypeRequest(HttpServletRequest request) {
+        return 
Optional.ofNullable(request.getContentType()).map(String::toLowerCase).map(ct 
-> ct.contains("application/json"))
+                .orElse(false);
+    }
+
+    public boolean isStoreIdempotencyKey(HttpServletRequest request) {
+        return Optional
+                .ofNullable(
+                        
fineractRequestContextHolder.getAttribute(SynchronousCommandProcessingService.IDEMPOTENCY_KEY_STORE_FLAG,
 request))
+                
.filter(Boolean.class::isInstance).map(Boolean.class::cast).orElse(false);
+    }
+
+    public Optional<Long> getCommandId(HttpServletRequest request) {
+        return Optional
+                
.ofNullable(fineractRequestContextHolder.getAttribute(SynchronousCommandProcessingService.COMMAND_SOURCE_ID,
 request))
+                .filter(Long.class::isInstance).map(Long.class::cast);
+    }
+}
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/service/database/DatabaseSpecificSQLGenerator.java
 
b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/service/database/DatabaseSpecificSQLGenerator.java
index 57d511577..135c61dc9 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/service/database/DatabaseSpecificSQLGenerator.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/service/database/DatabaseSpecificSQLGenerator.java
@@ -160,6 +160,17 @@ public class DatabaseSpecificSQLGenerator {
         }
     }
 
+    public String castBigInt(String sql) {
+        if (databaseTypeResolver.isMySQL()) {
+            return format("CAST(%s AS BIGINT)", sql);
+        } else if (databaseTypeResolver.isPostgreSQL()) {
+            return format("%s::BIGINT", sql);
+        } else {
+            throw new IllegalStateException(
+                    "Database type is not supported for casting to bigint " + 
databaseTypeResolver.databaseType());
+        }
+    }
+
     public String currentSchema() {
         if (databaseTypeResolver.isMySQL()) {
             return "SCHEMA()";
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/domain/JobExecutionRepository.java
 
b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/domain/JobExecutionRepository.java
index ca3750a11..8d633e1e7 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/domain/JobExecutionRepository.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/domain/JobExecutionRepository.java
@@ -30,6 +30,7 @@ import java.util.Map;
 import lombok.RequiredArgsConstructor;
 import org.apache.fineract.infrastructure.core.config.FineractProperties;
 import 
org.apache.fineract.infrastructure.core.serialization.GoogleGsonSerializerHelper;
+import 
org.apache.fineract.infrastructure.core.service.database.DatabaseSpecificSQLGenerator;
 import 
org.apache.fineract.infrastructure.core.service.database.DatabaseTypeResolver;
 import org.apache.fineract.infrastructure.jobs.data.JobParameterDTO;
 import org.springframework.beans.factory.InitializingBean;
@@ -43,6 +44,7 @@ public class JobExecutionRepository implements 
InitializingBean {
     private final NamedParameterJdbcTemplate namedParameterJdbcTemplate;
     private final FineractProperties fineractProperties;
     private final DatabaseTypeResolver databaseTypeResolver;
+    private final DatabaseSpecificSQLGenerator sqlGenerator;
     private final GoogleGsonSerializerHelper gsonFactory;
     private Gson gson;
 
@@ -169,7 +171,7 @@ public class JobExecutionRepository implements 
InitializingBean {
         String jsonString = gson.toJson(new JobParameterDTO(parameterKeyName, 
parameterValue));
         sqlStatementBuilder.append(
                 "SELECT bje.JOB_EXECUTION_ID FROM BATCH_JOB_INSTANCE bji INNER 
JOIN BATCH_JOB_EXECUTION bje ON bji.JOB_INSTANCE_ID = bje.JOB_INSTANCE_ID INNER 
JOIN BATCH_JOB_EXECUTION_PARAMS bjep ON bje.JOB_EXECUTION_ID = 
bjep.JOB_EXECUTION_ID"
-                        + " WHERE bje.STATUS IN (:statuses) AND bji.JOB_NAME = 
:jobName AND bjep.KEY_NAME = :jobCustomParamKeyName AND bjep.LONG_VAL IN ("
+                        + " WHERE bje.STATUS IN (:statuses) AND bji.JOB_NAME = 
:jobName AND bjep.PARAMETER_NAME = :jobCustomParamKeyName AND " + 
sqlGenerator.castBigInt("bjep.PARAMETER_VALUE") + " IN ("
                         + getSubQueryForCustomJobParameters()
                         + ") AND bje.JOB_INSTANCE_ID NOT IN (SELECT 
bje.JOB_INSTANCE_ID FROM BATCH_JOB_INSTANCE bji INNER JOIN BATCH_JOB_EXECUTION 
bje ON bji.JOB_INSTANCE_ID = bje.JOB_INSTANCE_ID"
                         + " WHERE bje.STATUS = :completedStatus AND 
bji.JOB_NAME = :jobName)");
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/filter/LoanCOBApiFilter.java
 
b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/filter/LoanCOBApiFilter.java
index 19d51caa0..650dcc428 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/filter/LoanCOBApiFilter.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/filter/LoanCOBApiFilter.java
@@ -18,73 +18,22 @@
  */
 package org.apache.fineract.infrastructure.jobs.filter;
 
-import static 
org.apache.fineract.batch.command.CommandStrategyUtils.isRelativeUrlVersioned;
-
-import com.google.common.collect.Lists;
-import io.github.resilience4j.core.functions.Either;
 import jakarta.servlet.FilterChain;
 import jakarta.servlet.ServletException;
 import jakarta.servlet.http.HttpServletRequest;
 import jakarta.servlet.http.HttpServletResponse;
 import java.io.IOException;
-import java.math.BigDecimal;
-import java.util.ArrayList;
-import java.util.Collections;
 import java.util.List;
-import java.util.function.Predicate;
-import java.util.regex.Pattern;
 import lombok.RequiredArgsConstructor;
-import org.apache.commons.collections4.CollectionUtils;
-import org.apache.commons.lang3.StringUtils;
-import org.apache.fineract.batch.domain.BatchRequest;
-import org.apache.fineract.cob.data.LoanIdAndLastClosedBusinessDate;
-import org.apache.fineract.cob.service.InlineLoanCOBExecutorServiceImpl;
-import org.apache.fineract.cob.service.LoanAccountLockService;
-import org.apache.fineract.infrastructure.businessdate.domain.BusinessDateType;
-import org.apache.fineract.infrastructure.core.config.FineractProperties;
 import org.apache.fineract.infrastructure.core.data.ApiGlobalErrorResponse;
-import org.apache.fineract.infrastructure.core.domain.ExternalId;
-import 
org.apache.fineract.infrastructure.core.filters.BatchRequestPreprocessor;
-import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil;
 import 
org.apache.fineract.infrastructure.jobs.exception.LoanIdsHardLockedException;
-import 
org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
-import 
org.apache.fineract.portfolio.loanaccount.domain.GLIMAccountInfoRepository;
-import 
org.apache.fineract.portfolio.loanaccount.domain.GroupLoanIndividualMonitoringAccount;
-import org.apache.fineract.portfolio.loanaccount.domain.Loan;
-import org.apache.fineract.portfolio.loanaccount.domain.LoanRepository;
-import 
org.apache.fineract.portfolio.loanaccount.exception.LoanNotFoundException;
-import 
org.apache.fineract.portfolio.loanaccount.rescheduleloan.domain.LoanRescheduleRequestRepository;
 import 
org.apache.fineract.useradministration.exception.UnAuthenticatedUserException;
 import org.apache.http.HttpStatus;
-import org.springframework.http.HttpMethod;
-import org.springframework.transaction.PlatformTransactionManager;
-import org.springframework.transaction.TransactionDefinition;
-import org.springframework.transaction.support.TransactionTemplate;
 import org.springframework.web.filter.OncePerRequestFilter;
 
 @RequiredArgsConstructor
-public class LoanCOBApiFilter extends OncePerRequestFilter implements 
BatchRequestPreprocessor {
-
-    private final GLIMAccountInfoRepository glimAccountInfoRepository;
-    private final LoanAccountLockService loanAccountLockService;
-    private final PlatformSecurityContext context;
-    private final InlineLoanCOBExecutorServiceImpl 
inlineLoanCOBExecutorService;
-    private final LoanRepository loanRepository;
-    private final FineractProperties fineractProperties;
-
-    private final LoanRescheduleRequestRepository 
loanRescheduleRequestRepository;
-
-    private static final List<HttpMethod> HTTP_METHODS = 
List.of(HttpMethod.POST, HttpMethod.PUT, HttpMethod.DELETE);
-
-    public static final Pattern IGNORE_LOAN_PATH_PATTERN = 
Pattern.compile("/v[1-9][0-9]*/loans/catch-up");
-    public static final Pattern LOAN_PATH_PATTERN = 
Pattern.compile("/v[1-9][0-9]*/(?:reschedule)?loans/(?:external-id/)?([^/?]+).*");
-
-    public static final Pattern LOAN_GLIMACCOUNT_PATH_PATTERN = 
Pattern.compile("/v[1-9][0-9]*/loans/glimAccount/(\\d+).*");
-    private static final Predicate<String> URL_FUNCTION = s -> 
LOAN_PATH_PATTERN.matcher(s).find()
-            || LOAN_GLIMACCOUNT_PATH_PATTERN.matcher(s).find();
-    private static final String JOB_NAME = "INLINE_LOAN_COB";
-
-    private final PlatformTransactionManager transactionManager;
+public class LoanCOBApiFilter extends OncePerRequestFilter {
+    private final LoanCOBFilterHelper helper;
 
     private static class Reject {
 
@@ -109,18 +58,18 @@ public class LoanCOBApiFilter extends OncePerRequestFilter 
implements BatchReque
     @Override
     protected void doFilterInternal(HttpServletRequest request, 
HttpServletResponse response, FilterChain filterChain)
             throws ServletException, IOException {
-        if (!isOnApiList(request.getPathInfo(), request.getMethod())) {
+        if (!helper.isOnApiList(request.getPathInfo(), request.getMethod())) {
             proceed(filterChain, request, response);
         } else {
             try {
-                boolean bypassUser = isBypassUser();
+                boolean bypassUser = helper.isBypassUser();
                 if (bypassUser) {
                     proceed(filterChain, request, response);
                 } else {
                     try {
-                        List<Long> loanIds = 
calculateRelevantLoanIds(request.getPathInfo());
-                        if (!loanIds.isEmpty() && isLoanBehind(loanIds)) {
-                            executeInlineCob(loanIds);
+                        List<Long> loanIds = 
helper.calculateRelevantLoanIds(request.getPathInfo());
+                        if (!loanIds.isEmpty() && 
helper.isLoanBehind(loanIds)) {
+                            helper.executeInlineCob(loanIds);
                         }
                         proceed(filterChain, request, response);
                     } catch (LoanIdsHardLockedException e) {
@@ -133,130 +82,9 @@ public class LoanCOBApiFilter extends OncePerRequestFilter 
implements BatchReque
         }
     }
 
-    private boolean isLoanBehind(List<Long> loanIds) {
-        List<LoanIdAndLastClosedBusinessDate> loanIdAndLastClosedBusinessDates 
= new ArrayList<>();
-        List<List<Long>> partitions = Lists.partition(loanIds, 
fineractProperties.getQuery().getInClauseParameterSizeLimit());
-        partitions.forEach(partition -> 
loanIdAndLastClosedBusinessDates.addAll(loanRepository
-                
.findAllNonClosedLoansBehindByLoanIds(ThreadLocalContextUtil.getBusinessDateByType(BusinessDateType.COB_DATE),
 partition)));
-        return CollectionUtils.isNotEmpty(loanIdAndLastClosedBusinessDates);
-    }
-
-    private List<Long> calculateRelevantLoanIds(String pathInfo) {
-
-        List<Long> loanIds = getLoanIdList(pathInfo);
-        if (isLoanHardLocked(loanIds)) {
-            throw new LoanIdsHardLockedException(loanIds.get(0));
-        } else {
-            return loanIds;
-        }
-    }
-
-    private List<Long> getLoanIdList(String pathInfo) {
-        boolean isGlim = isGlim(pathInfo);
-        Long loanIdFromRequest = getLoanId(isGlim, pathInfo);
-        if (loanIdFromRequest == null) {
-            return Collections.emptyList();
-        }
-        if (isGlim) {
-            return getGlimChildLoanIds(loanIdFromRequest);
-        } else {
-            return Collections.singletonList(loanIdFromRequest);
-        }
-    }
-
-    private void executeInlineCob(List<Long> loanIds) {
-        inlineLoanCOBExecutorService.execute(loanIds, JOB_NAME);
-    }
-
-    private boolean isBypassUser() {
-        return context.authenticatedUser().isBypassUser();
-    }
-
-    private List<Long> getGlimChildLoanIds(Long loanIdFromRequest) {
-        GroupLoanIndividualMonitoringAccount glimAccount = 
glimAccountInfoRepository.findOneByIsAcceptingChildAndApplicationId(true,
-                BigDecimal.valueOf(loanIdFromRequest));
-        if (glimAccount != null) {
-            return 
glimAccount.getChildLoan().stream().map(Loan::getId).toList();
-        } else {
-            return Collections.emptyList();
-        }
-    }
-
-    private boolean isLoanHardLocked(List<Long> loanIds) {
-        return 
loanIds.stream().anyMatch(loanAccountLockService::isLoanHardLocked);
-    }
-
     private void proceed(FilterChain filterChain, HttpServletRequest request, 
HttpServletResponse response)
             throws IOException, ServletException {
         filterChain.doFilter(request, response);
     }
 
-    private Long getLoanId(boolean isGlim, String pathInfo) {
-        if (!isGlim) {
-            String id = LOAN_PATH_PATTERN.matcher(pathInfo).replaceAll("$1");
-            if (isExternal(pathInfo)) {
-                String externalId = id;
-                return loanRepository.findIdByExternalId(new 
ExternalId(externalId));
-            } else if (isRescheduleLoans(pathInfo)) {
-                return 
loanRescheduleRequestRepository.getLoanIdByRescheduleRequestId(Long.valueOf(id)).orElse(null);
-            } else if (StringUtils.isNumeric(id)) {
-                return Long.valueOf(id);
-            } else {
-                return null;
-            }
-        } else {
-            return 
Long.valueOf(LOAN_GLIMACCOUNT_PATH_PATTERN.matcher(pathInfo).replaceAll("$1"));
-        }
-    }
-
-    private boolean isExternal(String pathInfo) {
-        return LOAN_PATH_PATTERN.matcher(pathInfo).matches() && 
pathInfo.contains("external-id");
-    }
-
-    private boolean isRescheduleLoans(String pathInfo) {
-        return LOAN_PATH_PATTERN.matcher(pathInfo).matches() && 
pathInfo.contains("/v1/rescheduleloans/");
-    }
-
-    private boolean isOnApiList(String pathInfo, String method) {
-        if (StringUtils.isBlank(pathInfo)) {
-            return false;
-        }
-        return HTTP_METHODS.contains(HttpMethod.valueOf(method)) && 
!IGNORE_LOAN_PATH_PATTERN.matcher(pathInfo).find()
-                && URL_FUNCTION.test(pathInfo);
-    }
-
-    private boolean isGlim(String pathInfo) {
-        return LOAN_GLIMACCOUNT_PATH_PATTERN.matcher(pathInfo).matches();
-    }
-
-    @Override
-    public Either<RuntimeException, BatchRequest> preprocess(BatchRequest 
batchRequest) {
-        TransactionTemplate tr = new TransactionTemplate(transactionManager);
-        
tr.setPropagationBehavior(TransactionDefinition.PROPAGATION_NOT_SUPPORTED);
-        return tr.execute(status -> {
-            try {
-                String method = batchRequest.getMethod();
-                String relativeUrl = "/" + batchRequest.getRelativeUrl();
-                if (!isRelativeUrlVersioned(batchRequest.getRelativeUrl())) {
-                    // to support pre-versioned relative paths
-                    relativeUrl = "/v1/" + batchRequest.getRelativeUrl();
-                }
-                if (isOnApiList(relativeUrl, method)) {
-                    boolean bypassUser = isBypassUser();
-                    if (!bypassUser) {
-                        List<Long> result = 
calculateRelevantLoanIds(relativeUrl);
-                        if (!result.isEmpty() && isLoanBehind(result)) {
-                            executeInlineCob(result);
-                        }
-                    }
-                }
-            } catch (LoanNotFoundException e) {
-                return Either.right(batchRequest);
-            } catch (RuntimeException e) {
-                return Either.left(e);
-            }
-            return Either.right(batchRequest);
-        });
-    }
-
 }
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/filter/LoanCOBBatchPreprocessor.java
 
b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/filter/LoanCOBBatchPreprocessor.java
new file mode 100644
index 000000000..a7bacc237
--- /dev/null
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/filter/LoanCOBBatchPreprocessor.java
@@ -0,0 +1,70 @@
+/**
+ * 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.filter;
+
+import static 
org.apache.fineract.batch.command.CommandStrategyUtils.isRelativeUrlVersioned;
+
+import io.github.resilience4j.core.functions.Either;
+import java.util.List;
+import lombok.RequiredArgsConstructor;
+import org.apache.fineract.batch.domain.BatchRequest;
+import 
org.apache.fineract.infrastructure.core.filters.BatchRequestPreprocessor;
+import 
org.apache.fineract.portfolio.loanaccount.exception.LoanNotFoundException;
+import org.springframework.stereotype.Component;
+import org.springframework.transaction.PlatformTransactionManager;
+import org.springframework.transaction.TransactionDefinition;
+import org.springframework.transaction.support.TransactionTemplate;
+
+@Component
+@RequiredArgsConstructor
+public class LoanCOBBatchPreprocessor implements BatchRequestPreprocessor {
+    private final LoanCOBFilterHelper helper;
+
+    private final PlatformTransactionManager transactionManager;
+
+    @Override
+    public Either<RuntimeException, BatchRequest> preprocess(BatchRequest 
batchRequest) {
+        TransactionTemplate tr = new TransactionTemplate(transactionManager);
+        
tr.setPropagationBehavior(TransactionDefinition.PROPAGATION_NOT_SUPPORTED);
+        return tr.execute(status -> {
+            try {
+                String method = batchRequest.getMethod();
+                String relativeUrl = "/" + batchRequest.getRelativeUrl();
+                if (!isRelativeUrlVersioned(batchRequest.getRelativeUrl())) {
+                    // to support pre-versioned relative paths
+                    relativeUrl = "/v1/" + batchRequest.getRelativeUrl();
+                }
+                if (helper.isOnApiList(relativeUrl, method)) {
+                    boolean bypassUser = helper.isBypassUser();
+                    if (!bypassUser) {
+                        List<Long> result = 
helper.calculateRelevantLoanIds(relativeUrl);
+                        if (!result.isEmpty() && helper.isLoanBehind(result)) {
+                            helper.executeInlineCob(result);
+                        }
+                    }
+                }
+            } catch (LoanNotFoundException e) {
+                return Either.right(batchRequest);
+            } catch (RuntimeException e) {
+                return Either.left(e);
+            }
+            return Either.right(batchRequest);
+        });
+    }
+}
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/filter/LoanCOBApiFilter.java
 
b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/filter/LoanCOBFilterHelper.java
similarity index 58%
copy from 
fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/filter/LoanCOBApiFilter.java
copy to 
fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/filter/LoanCOBFilterHelper.java
index 19d51caa0..960808a9c 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/filter/LoanCOBApiFilter.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/filter/LoanCOBFilterHelper.java
@@ -18,15 +18,7 @@
  */
 package org.apache.fineract.infrastructure.jobs.filter;
 
-import static 
org.apache.fineract.batch.command.CommandStrategyUtils.isRelativeUrlVersioned;
-
 import com.google.common.collect.Lists;
-import io.github.resilience4j.core.functions.Either;
-import jakarta.servlet.FilterChain;
-import jakarta.servlet.ServletException;
-import jakarta.servlet.http.HttpServletRequest;
-import jakarta.servlet.http.HttpServletResponse;
-import java.io.IOException;
 import java.math.BigDecimal;
 import java.util.ArrayList;
 import java.util.Collections;
@@ -36,15 +28,12 @@ import java.util.regex.Pattern;
 import lombok.RequiredArgsConstructor;
 import org.apache.commons.collections4.CollectionUtils;
 import org.apache.commons.lang3.StringUtils;
-import org.apache.fineract.batch.domain.BatchRequest;
 import org.apache.fineract.cob.data.LoanIdAndLastClosedBusinessDate;
 import org.apache.fineract.cob.service.InlineLoanCOBExecutorServiceImpl;
 import org.apache.fineract.cob.service.LoanAccountLockService;
 import org.apache.fineract.infrastructure.businessdate.domain.BusinessDateType;
 import org.apache.fineract.infrastructure.core.config.FineractProperties;
-import org.apache.fineract.infrastructure.core.data.ApiGlobalErrorResponse;
 import org.apache.fineract.infrastructure.core.domain.ExternalId;
-import 
org.apache.fineract.infrastructure.core.filters.BatchRequestPreprocessor;
 import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil;
 import 
org.apache.fineract.infrastructure.jobs.exception.LoanIdsHardLockedException;
 import 
org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
@@ -52,19 +41,13 @@ import 
org.apache.fineract.portfolio.loanaccount.domain.GLIMAccountInfoRepositor
 import 
org.apache.fineract.portfolio.loanaccount.domain.GroupLoanIndividualMonitoringAccount;
 import org.apache.fineract.portfolio.loanaccount.domain.Loan;
 import org.apache.fineract.portfolio.loanaccount.domain.LoanRepository;
-import 
org.apache.fineract.portfolio.loanaccount.exception.LoanNotFoundException;
 import 
org.apache.fineract.portfolio.loanaccount.rescheduleloan.domain.LoanRescheduleRequestRepository;
-import 
org.apache.fineract.useradministration.exception.UnAuthenticatedUserException;
-import org.apache.http.HttpStatus;
 import org.springframework.http.HttpMethod;
-import org.springframework.transaction.PlatformTransactionManager;
-import org.springframework.transaction.TransactionDefinition;
-import org.springframework.transaction.support.TransactionTemplate;
-import org.springframework.web.filter.OncePerRequestFilter;
+import org.springframework.stereotype.Component;
 
 @RequiredArgsConstructor
-public class LoanCOBApiFilter extends OncePerRequestFilter implements 
BatchRequestPreprocessor {
-
+@Component
+public class LoanCOBFilterHelper {
     private final GLIMAccountInfoRepository glimAccountInfoRepository;
     private final LoanAccountLockService loanAccountLockService;
     private final PlatformSecurityContext context;
@@ -82,58 +65,65 @@ public class LoanCOBApiFilter extends OncePerRequestFilter 
implements BatchReque
     public static final Pattern LOAN_GLIMACCOUNT_PATH_PATTERN = 
Pattern.compile("/v[1-9][0-9]*/loans/glimAccount/(\\d+).*");
     private static final Predicate<String> URL_FUNCTION = s -> 
LOAN_PATH_PATTERN.matcher(s).find()
             || LOAN_GLIMACCOUNT_PATH_PATTERN.matcher(s).find();
+
     private static final String JOB_NAME = "INLINE_LOAN_COB";
 
-    private final PlatformTransactionManager transactionManager;
+    private Long getLoanId(boolean isGlim, String pathInfo) {
+        if (!isGlim) {
+            String id = LOAN_PATH_PATTERN.matcher(pathInfo).replaceAll("$1");
+            if (isExternal(pathInfo)) {
+                String externalId = id;
+                return loanRepository.findIdByExternalId(new 
ExternalId(externalId));
+            } else if (isRescheduleLoans(pathInfo)) {
+                return 
loanRescheduleRequestRepository.getLoanIdByRescheduleRequestId(Long.valueOf(id)).orElse(null);
+            } else if (StringUtils.isNumeric(id)) {
+                return Long.valueOf(id);
+            } else {
+                return null;
+            }
+        } else {
+            return 
Long.valueOf(LOAN_GLIMACCOUNT_PATH_PATTERN.matcher(pathInfo).replaceAll("$1"));
+        }
+    }
 
-    private static class Reject {
+    private boolean isExternal(String pathInfo) {
+        return LOAN_PATH_PATTERN.matcher(pathInfo).matches() && 
pathInfo.contains("external-id");
+    }
 
-        private final String message;
-        private final Integer statusCode;
+    private boolean isRescheduleLoans(String pathInfo) {
+        return LOAN_PATH_PATTERN.matcher(pathInfo).matches() && 
pathInfo.contains("/v1/rescheduleloans/");
+    }
 
-        Reject(String message, Integer statusCode) {
-            this.message = message;
-            this.statusCode = statusCode;
+    public boolean isOnApiList(String pathInfo, String method) {
+        if (StringUtils.isBlank(pathInfo)) {
+            return false;
         }
+        return HTTP_METHODS.contains(HttpMethod.valueOf(method)) && 
!IGNORE_LOAN_PATH_PATTERN.matcher(pathInfo).find()
+                && URL_FUNCTION.test(pathInfo);
+    }
 
-        public static Reject reject(Long loanId, int status) {
-            return new 
Reject(ApiGlobalErrorResponse.loanIsLocked(loanId).toJson(), status);
-        }
+    private boolean isGlim(String pathInfo) {
+        return LOAN_GLIMACCOUNT_PATH_PATTERN.matcher(pathInfo).matches();
+    }
 
-        public void toServletResponse(HttpServletResponse response) throws 
IOException {
-            response.setStatus(statusCode);
-            response.getWriter().write(message);
-        }
+    public boolean isBypassUser() {
+        return context.authenticatedUser().isBypassUser();
     }
 
-    @Override
-    protected void doFilterInternal(HttpServletRequest request, 
HttpServletResponse response, FilterChain filterChain)
-            throws ServletException, IOException {
-        if (!isOnApiList(request.getPathInfo(), request.getMethod())) {
-            proceed(filterChain, request, response);
+    private List<Long> getGlimChildLoanIds(Long loanIdFromRequest) {
+        GroupLoanIndividualMonitoringAccount glimAccount = 
glimAccountInfoRepository.findOneByIsAcceptingChildAndApplicationId(true,
+                BigDecimal.valueOf(loanIdFromRequest));
+        if (glimAccount != null) {
+            return 
glimAccount.getChildLoan().stream().map(Loan::getId).toList();
         } else {
-            try {
-                boolean bypassUser = isBypassUser();
-                if (bypassUser) {
-                    proceed(filterChain, request, response);
-                } else {
-                    try {
-                        List<Long> loanIds = 
calculateRelevantLoanIds(request.getPathInfo());
-                        if (!loanIds.isEmpty() && isLoanBehind(loanIds)) {
-                            executeInlineCob(loanIds);
-                        }
-                        proceed(filterChain, request, response);
-                    } catch (LoanIdsHardLockedException e) {
-                        Reject.reject(e.getLoanIdFromRequest(), 
HttpStatus.SC_CONFLICT).toServletResponse(response);
-                    }
-                }
-            } catch (UnAuthenticatedUserException e) {
-                Reject.reject(null, 
HttpStatus.SC_UNAUTHORIZED).toServletResponse(response);
-            }
+            return Collections.emptyList();
         }
     }
 
-    private boolean isLoanBehind(List<Long> loanIds) {
+    private boolean isLoanHardLocked(List<Long> loanIds) {
+        return 
loanIds.stream().anyMatch(loanAccountLockService::isLoanHardLocked);
+    }
+    public boolean isLoanBehind(List<Long> loanIds) {
         List<LoanIdAndLastClosedBusinessDate> loanIdAndLastClosedBusinessDates 
= new ArrayList<>();
         List<List<Long>> partitions = Lists.partition(loanIds, 
fineractProperties.getQuery().getInClauseParameterSizeLimit());
         partitions.forEach(partition -> 
loanIdAndLastClosedBusinessDates.addAll(loanRepository
@@ -141,7 +131,7 @@ public class LoanCOBApiFilter extends OncePerRequestFilter 
implements BatchReque
         return CollectionUtils.isNotEmpty(loanIdAndLastClosedBusinessDates);
     }
 
-    private List<Long> calculateRelevantLoanIds(String pathInfo) {
+    public List<Long> calculateRelevantLoanIds(String pathInfo) {
 
         List<Long> loanIds = getLoanIdList(pathInfo);
         if (isLoanHardLocked(loanIds)) {
@@ -164,99 +154,7 @@ public class LoanCOBApiFilter extends OncePerRequestFilter 
implements BatchReque
         }
     }
 
-    private void executeInlineCob(List<Long> loanIds) {
+    public void executeInlineCob(List<Long> loanIds) {
         inlineLoanCOBExecutorService.execute(loanIds, JOB_NAME);
     }
-
-    private boolean isBypassUser() {
-        return context.authenticatedUser().isBypassUser();
-    }
-
-    private List<Long> getGlimChildLoanIds(Long loanIdFromRequest) {
-        GroupLoanIndividualMonitoringAccount glimAccount = 
glimAccountInfoRepository.findOneByIsAcceptingChildAndApplicationId(true,
-                BigDecimal.valueOf(loanIdFromRequest));
-        if (glimAccount != null) {
-            return 
glimAccount.getChildLoan().stream().map(Loan::getId).toList();
-        } else {
-            return Collections.emptyList();
-        }
-    }
-
-    private boolean isLoanHardLocked(List<Long> loanIds) {
-        return 
loanIds.stream().anyMatch(loanAccountLockService::isLoanHardLocked);
-    }
-
-    private void proceed(FilterChain filterChain, HttpServletRequest request, 
HttpServletResponse response)
-            throws IOException, ServletException {
-        filterChain.doFilter(request, response);
-    }
-
-    private Long getLoanId(boolean isGlim, String pathInfo) {
-        if (!isGlim) {
-            String id = LOAN_PATH_PATTERN.matcher(pathInfo).replaceAll("$1");
-            if (isExternal(pathInfo)) {
-                String externalId = id;
-                return loanRepository.findIdByExternalId(new 
ExternalId(externalId));
-            } else if (isRescheduleLoans(pathInfo)) {
-                return 
loanRescheduleRequestRepository.getLoanIdByRescheduleRequestId(Long.valueOf(id)).orElse(null);
-            } else if (StringUtils.isNumeric(id)) {
-                return Long.valueOf(id);
-            } else {
-                return null;
-            }
-        } else {
-            return 
Long.valueOf(LOAN_GLIMACCOUNT_PATH_PATTERN.matcher(pathInfo).replaceAll("$1"));
-        }
-    }
-
-    private boolean isExternal(String pathInfo) {
-        return LOAN_PATH_PATTERN.matcher(pathInfo).matches() && 
pathInfo.contains("external-id");
-    }
-
-    private boolean isRescheduleLoans(String pathInfo) {
-        return LOAN_PATH_PATTERN.matcher(pathInfo).matches() && 
pathInfo.contains("/v1/rescheduleloans/");
-    }
-
-    private boolean isOnApiList(String pathInfo, String method) {
-        if (StringUtils.isBlank(pathInfo)) {
-            return false;
-        }
-        return HTTP_METHODS.contains(HttpMethod.valueOf(method)) && 
!IGNORE_LOAN_PATH_PATTERN.matcher(pathInfo).find()
-                && URL_FUNCTION.test(pathInfo);
-    }
-
-    private boolean isGlim(String pathInfo) {
-        return LOAN_GLIMACCOUNT_PATH_PATTERN.matcher(pathInfo).matches();
-    }
-
-    @Override
-    public Either<RuntimeException, BatchRequest> preprocess(BatchRequest 
batchRequest) {
-        TransactionTemplate tr = new TransactionTemplate(transactionManager);
-        
tr.setPropagationBehavior(TransactionDefinition.PROPAGATION_NOT_SUPPORTED);
-        return tr.execute(status -> {
-            try {
-                String method = batchRequest.getMethod();
-                String relativeUrl = "/" + batchRequest.getRelativeUrl();
-                if (!isRelativeUrlVersioned(batchRequest.getRelativeUrl())) {
-                    // to support pre-versioned relative paths
-                    relativeUrl = "/v1/" + batchRequest.getRelativeUrl();
-                }
-                if (isOnApiList(relativeUrl, method)) {
-                    boolean bypassUser = isBypassUser();
-                    if (!bypassUser) {
-                        List<Long> result = 
calculateRelevantLoanIds(relativeUrl);
-                        if (!result.isEmpty() && isLoanBehind(result)) {
-                            executeInlineCob(result);
-                        }
-                    }
-                }
-            } catch (LoanNotFoundException e) {
-                return Either.right(batchRequest);
-            } catch (RuntimeException e) {
-                return Either.left(e);
-            }
-            return Either.right(batchRequest);
-        });
-    }
-
 }
diff --git 
a/fineract-provider/src/test/java/org/apache/fineract/TestConfiguration.java 
b/fineract-provider/src/test/java/org/apache/fineract/TestConfiguration.java
index 1fb160834..8eb9a36dc 100644
--- a/fineract-provider/src/test/java/org/apache/fineract/TestConfiguration.java
+++ b/fineract-provider/src/test/java/org/apache/fineract/TestConfiguration.java
@@ -44,8 +44,10 @@ import org.mockito.quality.Strictness;
 import org.springframework.batch.core.configuration.ListableJobLocator;
 import org.springframework.batch.core.explore.JobExplorer;
 import org.springframework.batch.core.launch.JobLauncher;
+import org.springframework.batch.core.launch.JobOperator;
 import org.springframework.batch.core.repository.JobRepository;
 import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
+import org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration;
 import org.springframework.boot.autoconfigure.gson.GsonAutoConfiguration;
 import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
 import 
org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration;
@@ -72,7 +74,7 @@ import 
org.springframework.transaction.annotation.EnableTransactionManagement;
 @Configuration
 @EnableAutoConfiguration(exclude = { DataSourceAutoConfiguration.class, 
HibernateJpaAutoConfiguration.class,
         DataSourceTransactionManagerAutoConfiguration.class, 
GsonAutoConfiguration.class, JdbcTemplateAutoConfiguration.class,
-        LiquibaseAutoConfiguration.class })
+        LiquibaseAutoConfiguration.class, BatchAutoConfiguration.class })
 @EnableTransactionManagement
 @EnableWebSecurity
 @EnableConfigurationProperties({ FineractProperties.class, 
LiquibaseProperties.class })
@@ -180,4 +182,10 @@ public class TestConfiguration {
     public JobRepository jobRepository() {
         return mock(JobRepository.class, RETURNS_MOCKS);
     }
+
+    @Primary
+    @Bean
+    public JobOperator jobOperator() {
+        return mock(JobOperator.class, RETURNS_MOCKS);
+    }
 }
diff --git 
a/fineract-provider/src/test/java/org/apache/fineract/infrastructure/jobs/filter/LoanCOBApiFilterTest.java
 
b/fineract-provider/src/test/java/org/apache/fineract/infrastructure/jobs/filter/LoanCOBApiFilterTest.java
index 68726515f..c02a394fe 100644
--- 
a/fineract-provider/src/test/java/org/apache/fineract/infrastructure/jobs/filter/LoanCOBApiFilterTest.java
+++ 
b/fineract-provider/src/test/java/org/apache/fineract/infrastructure/jobs/filter/LoanCOBApiFilterTest.java
@@ -18,6 +18,8 @@
  */
 package org.apache.fineract.infrastructure.jobs.filter;
 
+import static 
org.apache.fineract.infrastructure.jobs.filter.LoanCOBFilterHelper.LOAN_GLIMACCOUNT_PATH_PATTERN;
+import static 
org.apache.fineract.infrastructure.jobs.filter.LoanCOBFilterHelper.LOAN_PATH_PATTERN;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyList;
 import static org.mockito.ArgumentMatchers.eq;
@@ -27,6 +29,8 @@ import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 
 import com.sun.research.ws.wadl.HTTPMethods;
+import jakarta.servlet.FilterChain;
+import jakarta.servlet.ServletException;
 import java.io.IOException;
 import java.io.PrintWriter;
 import java.math.BigDecimal;
@@ -36,8 +40,6 @@ import java.util.Collections;
 import java.util.HashMap;
 import java.util.Optional;
 import java.util.UUID;
-import jakarta.servlet.FilterChain;
-import jakarta.servlet.ServletException;
 import org.apache.fineract.cob.data.LoanIdAndLastClosedBusinessDate;
 import org.apache.fineract.cob.service.InlineLoanCOBExecutorServiceImpl;
 import org.apache.fineract.cob.service.LoanAccountLockService;
@@ -55,6 +57,7 @@ import 
org.apache.fineract.portfolio.loanaccount.rescheduleloan.domain.LoanResch
 import org.apache.fineract.useradministration.domain.AppUser;
 import org.apache.http.HttpStatus;
 import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.extension.ExtendWith;
 import org.mockito.InjectMocks;
@@ -69,8 +72,9 @@ import org.springframework.mock.web.MockHttpServletResponse;
 @MockitoSettings(strictness = Strictness.LENIENT)
 class LoanCOBApiFilterTest {
 
-    @InjectMocks
     private LoanCOBApiFilter testObj;
+    @InjectMocks
+    private LoanCOBFilterHelper helper;
     @Mock
     private LoanAccountLockService loanAccountLockService;
     @Mock
@@ -85,41 +89,45 @@ class LoanCOBApiFilterTest {
     private FineractProperties fineractProperties;
     @Mock
     private FineractProperties.FineractQueryProperties fineractQueryProperties;
-
     @Mock
     private LoanRescheduleRequestRepository loanRescheduleRequestRepository;
 
+    @BeforeEach
+    public void setUp() {
+        testObj = new LoanCOBApiFilter(helper);
+    }
+
     @Test
     void shouldLoanAndExternalMatchToo() {
         String externalId = UUID.randomUUID().toString();
-        
Assertions.assertTrue(LoanCOBApiFilter.LOAN_PATH_PATTERN.matcher("/v1/loans/12").matches());
-        
Assertions.assertTrue(LoanCOBApiFilter.LOAN_PATH_PATTERN.matcher("/v1/loans/12?correct=parameter").matches());
-        
Assertions.assertTrue(LoanCOBApiFilter.LOAN_PATH_PATTERN.matcher("/v1/loans/12?correct=parameter").matches());
-        
Assertions.assertTrue(LoanCOBApiFilter.LOAN_PATH_PATTERN.matcher("/v1/rescheduleloans/12").matches());
-        
Assertions.assertTrue(LoanCOBApiFilter.LOAN_PATH_PATTERN.matcher("/v1/rescheduleloans/12?correct=parameter").matches());
-        
Assertions.assertTrue(LoanCOBApiFilter.LOAN_PATH_PATTERN.matcher("/v1/rescheduleloans/12?correct=parameter").matches());
-        
Assertions.assertTrue(LoanCOBApiFilter.LOAN_PATH_PATTERN.matcher("/v1/loans/external-id/"
 + externalId).matches());
+        
Assertions.assertTrue(LOAN_PATH_PATTERN.matcher("/v1/loans/12").matches());
+        
Assertions.assertTrue(LOAN_PATH_PATTERN.matcher("/v1/loans/12?correct=parameter").matches());
+        
Assertions.assertTrue(LOAN_PATH_PATTERN.matcher("/v1/loans/12?correct=parameter").matches());
+        
Assertions.assertTrue(LOAN_PATH_PATTERN.matcher("/v1/rescheduleloans/12").matches());
+        
Assertions.assertTrue(LOAN_PATH_PATTERN.matcher("/v1/rescheduleloans/12?correct=parameter").matches());
+        
Assertions.assertTrue(LOAN_PATH_PATTERN.matcher("/v1/rescheduleloans/12?correct=parameter").matches());
+        
Assertions.assertTrue(LOAN_PATH_PATTERN.matcher("/v1/loans/external-id/" + 
externalId).matches());
         Assertions.assertTrue(
-                
LoanCOBApiFilter.LOAN_PATH_PATTERN.matcher("/v1/loans/external-id/" + 
externalId + "?additional=parameter").matches());
-        Assertions.assertEquals("12", 
LoanCOBApiFilter.LOAN_PATH_PATTERN.matcher("/v1/loans/12").replaceAll("$1"));
-        Assertions.assertEquals("12", 
LoanCOBApiFilter.LOAN_PATH_PATTERN.matcher("/v1/loans/12?correct=parameter").replaceAll("$1"));
-        Assertions.assertEquals("12", 
LoanCOBApiFilter.LOAN_PATH_PATTERN.matcher("/v1/rescheduleloans/12").replaceAll("$1"));
+                LOAN_PATH_PATTERN.matcher("/v1/loans/external-id/" + 
externalId + "?additional=parameter").matches());
+        Assertions.assertEquals("12", 
LOAN_PATH_PATTERN.matcher("/v1/loans/12").replaceAll("$1"));
+        Assertions.assertEquals("12", 
LOAN_PATH_PATTERN.matcher("/v1/loans/12?correct=parameter").replaceAll("$1"));
+        Assertions.assertEquals("12", 
LOAN_PATH_PATTERN.matcher("/v1/rescheduleloans/12").replaceAll("$1"));
         Assertions.assertEquals("12",
-                
LoanCOBApiFilter.LOAN_PATH_PATTERN.matcher("/v1/rescheduleloans/12?correct=parameter").replaceAll("$1"));
+                
LOAN_PATH_PATTERN.matcher("/v1/rescheduleloans/12?correct=parameter").replaceAll("$1"));
         Assertions.assertEquals(externalId,
-                
LoanCOBApiFilter.LOAN_PATH_PATTERN.matcher("/v1/loans/external-id/" + 
externalId).replaceAll("$1"));
-        Assertions.assertEquals(externalId, LoanCOBApiFilter.LOAN_PATH_PATTERN
+                LOAN_PATH_PATTERN.matcher("/v1/loans/external-id/" + 
externalId).replaceAll("$1"));
+        Assertions.assertEquals(externalId, LOAN_PATH_PATTERN
                 .matcher("/v1/loans/external-id/" + externalId + 
"?additional=parameter").replaceAll("$1"));
     }
 
     @Test
     void shouldGlimAccountMatch() {
-        
Assertions.assertTrue(LoanCOBApiFilter.LOAN_GLIMACCOUNT_PATH_PATTERN.matcher("/v1/loans/glimAccount/12").matches());
+        
Assertions.assertTrue(LOAN_GLIMACCOUNT_PATH_PATTERN.matcher("/v1/loans/glimAccount/12").matches());
         Assertions.assertTrue(
-                
LoanCOBApiFilter.LOAN_GLIMACCOUNT_PATH_PATTERN.matcher("/v1/loans/glimAccount/12?additional=parameter").matches());
-        Assertions.assertEquals("12", 
LoanCOBApiFilter.LOAN_GLIMACCOUNT_PATH_PATTERN.matcher("/v1/loans/glimAccount/12").replaceAll("$1"));
+                
LOAN_GLIMACCOUNT_PATH_PATTERN.matcher("/v1/loans/glimAccount/12?additional=parameter").matches());
+        Assertions.assertEquals("12", 
LOAN_GLIMACCOUNT_PATH_PATTERN.matcher("/v1/loans/glimAccount/12").replaceAll("$1"));
         Assertions.assertEquals("12",
-                
LoanCOBApiFilter.LOAN_GLIMACCOUNT_PATH_PATTERN.matcher("/v1/loans/glimAccount/12?additional=parameter").replaceAll("$1"));
+                
LOAN_GLIMACCOUNT_PATH_PATTERN.matcher("/v1/loans/glimAccount/12?additional=parameter").replaceAll("$1"));
     }
 
     @Test
diff --git a/integration-tests/dependencies.gradle 
b/integration-tests/dependencies.gradle
index ee05c9657..d4b6f4db3 100644
--- a/integration-tests/dependencies.gradle
+++ b/integration-tests/dependencies.gradle
@@ -53,5 +53,5 @@ dependencies {
     testImplementation 'org.mapstruct:mapstruct'
     testAnnotationProcessor 'org.mapstruct:mapstruct-processor'
 
-    testImplementation 'com.github.tomakehurst:wiremock-jre8:2.35.0'
+    testImplementation 'com.github.tomakehurst:wiremock:3.0.0-beta-8'
 }
diff --git 
a/integration-tests/src/integrationTest/resources/bulkimport/importhandler/loan/Loan.xls
 
b/integration-tests/src/integrationTest/resources/bulkimport/importhandler/loan/Loan.xls
deleted file mode 100644
index a7c87b5bd..000000000
Binary files 
a/integration-tests/src/integrationTest/resources/bulkimport/importhandler/loan/Loan.xls
 and /dev/null differ
diff --git 
a/integration-tests/src/integrationTest/resources/bulkimport/importhandler/office/Office.xls
 
b/integration-tests/src/integrationTest/resources/bulkimport/importhandler/office/Office.xls
deleted file mode 100644
index 3a63e1221..000000000
Binary files 
a/integration-tests/src/integrationTest/resources/bulkimport/importhandler/office/Office.xls
 and /dev/null differ
diff --git 
a/integration-tests/src/integrationTest/resources/bulkimport/importhandler/savings/Savings.xls
 
b/integration-tests/src/integrationTest/resources/bulkimport/importhandler/savings/Savings.xls
deleted file mode 100644
index ec42864d5..000000000
Binary files 
a/integration-tests/src/integrationTest/resources/bulkimport/importhandler/savings/Savings.xls
 and /dev/null differ

Reply via email to