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

adamsaghy pushed a commit to branch develop
in repository https://gitbox.apache.org/repos/asf/fineract.git


The following commit(s) were added to refs/heads/develop by this push:
     new 92838e5ff FINERACT-1724 - Transaction handling fix [x] - Remove 
requires new transaction from NewCommandSourceHandler [x] - Rewrite batch 
processing [x] - Fix Idempotency transaction handling [x] - Fix Batch 
enclosingTransaction handling
92838e5ff is described below

commit 92838e5fff53e9618d36473c186c972482105a2d
Author: Janos Haber <[email protected]>
AuthorDate: Tue Mar 21 23:59:40 2023 +0100

    FINERACT-1724 - Transaction handling fix
    [x] - Remove requires new transaction from NewCommandSourceHandler
    [x] - Rewrite batch processing
    [x] - Fix Idempotency transaction handling
    [x] - Fix Batch enclosingTransaction handling
---
 .../batch/service/BatchApiServiceImpl.java         | 311 ++++++++++++++-------
 .../service/BatchExecutionException.java}          |  24 +-
 .../commands/handler/NewCommandSourceHandler.java  |   4 -
 .../commands/service/CommandSourceService.java     |  20 +-
 .../SynchronousCommandProcessingService.java       |  34 ++-
 .../core/domain/BatchRequestContextHolder.java     |  28 ++
 .../core/filters/BatchRequestPreprocessor.java}    |  14 +-
 .../core/filters/IdempotencyStoreFilter.java       |  12 +-
 .../jobs/filter/LoanCOBApiFilter.java              |  52 ++--
 .../AccountTransfersWritePlatformServiceImpl.java  |   5 +-
 .../portfolio/loanaccount/domain/Loan.java         |   2 +-
 .../domain/LoanAccountDomainServiceJpa.java        |  13 +-
 .../service/GuarantorDomainServiceImpl.java        |  13 +-
 ...nRescheduleRequestWritePlatformServiceImpl.java |  10 +-
 .../LoanChargeWritePlatformServiceImpl.java        |  21 +-
 .../LoanWritePlatformServiceJpaRepositoryImpl.java |  49 ++--
 16 files changed, 393 insertions(+), 219 deletions(-)

diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/batch/service/BatchApiServiceImpl.java
 
b/fineract-provider/src/main/java/org/apache/fineract/batch/service/BatchApiServiceImpl.java
index bbfb0d015..f6d5bcfd3 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/batch/service/BatchApiServiceImpl.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/batch/service/BatchApiServiceImpl.java
@@ -19,17 +19,21 @@
 package org.apache.fineract.batch.service;
 
 import com.google.gson.Gson;
+import io.github.resilience4j.core.functions.Either;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collections;
 import java.util.Comparator;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Optional;
+import java.util.function.Consumer;
+import java.util.function.Function;
+import java.util.function.Supplier;
 import java.util.stream.Collectors;
 import javax.persistence.EntityManager;
 import javax.persistence.PersistenceContext;
-import javax.ws.rs.core.Response;
 import javax.ws.rs.core.Response.Status;
 import javax.ws.rs.core.UriInfo;
 import lombok.RequiredArgsConstructor;
@@ -51,9 +55,14 @@ import 
org.apache.fineract.infrastructure.core.exception.IdempotentCommandProces
 import 
org.apache.fineract.infrastructure.core.exception.IdempotentCommandProcessUnderProcessingException;
 import org.apache.fineract.infrastructure.core.filters.BatchCallHandler;
 import org.apache.fineract.infrastructure.core.filters.BatchFilter;
+import 
org.apache.fineract.infrastructure.core.filters.BatchRequestPreprocessor;
+import org.jetbrains.annotations.NotNull;
 import org.springframework.dao.NonTransientDataAccessException;
 import org.springframework.stereotype.Service;
+import org.springframework.transaction.PlatformTransactionManager;
+import org.springframework.transaction.TransactionDefinition;
 import org.springframework.transaction.TransactionException;
+import org.springframework.transaction.TransactionExecution;
 import org.springframework.transaction.support.TransactionTemplate;
 
 /**
@@ -72,10 +81,12 @@ public class BatchApiServiceImpl implements BatchApiService 
{
 
     private final CommandStrategyProvider strategyProvider;
     private final ResolutionHelper resolutionHelper;
-    private final TransactionTemplate transactionTemplate;
+    private final PlatformTransactionManager transactionManager;
 
     private final List<BatchFilter> batchFilters;
 
+    private final List<BatchRequestPreprocessor> batchPreprocessors;
+
     @PersistenceContext
     private final EntityManager entityManager;
 
@@ -87,8 +98,8 @@ public class BatchApiServiceImpl implements BatchApiService {
      * @param uriInfo
      * @return {@code List<BatchResponse>}
      */
-    private List<BatchResponse> handleBatchRequests(final List<BatchRequest> 
requestList, final UriInfo uriInfo,
-            boolean isEnclosingTransaction) {
+    private List<BatchResponse> handleBatchRequests(boolean 
enclosingTransaction, final List<BatchRequest> requestList,
+            final UriInfo uriInfo) {
 
         final List<BatchResponse> responseList = new 
ArrayList<>(requestList.size());
 
@@ -103,52 +114,150 @@ public class BatchApiServiceImpl implements 
BatchApiService {
         }
 
         for (BatchRequestNode rootNode : batchRequestNodes) {
-            final BatchRequest rootRequest = rootNode.getRequest();
-            final CommandStrategy commandStrategy = this.strategyProvider
-                    
.getCommandStrategy(CommandContext.resource(rootRequest.getRelativeUrl()).method(rootRequest.getMethod()).build());
-            log.debug("Batch request: method [{}], relative url [{}]", 
rootRequest.getMethod(), rootRequest.getRelativeUrl());
-            final BatchResponse rootResponse = 
safelyExecuteStrategy(commandStrategy, rootRequest, uriInfo, 
isEnclosingTransaction);
-            log.debug("Batch response: status code [{}], method [{}], relative 
url [{}]", rootResponse.getStatusCode(),
-                    rootRequest.getMethod(), rootRequest.getRelativeUrl());
-            responseList.add(rootResponse);
-            responseList.addAll(this.processChildRequests(rootNode, 
rootResponse, uriInfo, isEnclosingTransaction));
+            if (enclosingTransaction) {
+                this.callRequestRecursive(rootNode.getRequest(), rootNode, 
responseList, uriInfo);
+            } else {
+                responseList.addAll(callInTransaction(
+                        transactionTemplate -> 
transactionTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW),
+                        () -> {
+                            List<BatchResponse> localResponseList = new 
ArrayList<>();
+                            this.callRequestRecursive(rootNode.getRequest(), 
rootNode, localResponseList, uriInfo);
+                            return localResponseList;
+                        }));
+            }
         }
-
         Collections.sort(responseList, 
Comparator.comparing(BatchResponse::getRequestId));
-
         return responseList;
+    }
+
+    /**
+     * Executes the request and call child requests recursively.
+     *
+     * @param request
+     *            the current batch request
+     * @param requestNode
+     *            the batch request holder node
+     * @param responseList
+     *            the collected responses
+     * @return {@code BatchResponse}
+     */
+    private void callRequestRecursive(BatchRequest request, BatchRequestNode 
requestNode, List<BatchResponse> responseList,
+            UriInfo uriInfo) {
+        // 1. run current node
+        BatchResponse response = executeRequest(request, uriInfo);
+        responseList.add(response);
+        if (response.getStatusCode() == 200) {
+            requestNode.getChildRequests().forEach(childNode -> {
+                BatchRequest resolvedChildRequest;
+                try {
+                    resolvedChildRequest = 
this.resolutionHelper.resoluteRequest(childNode.getRequest(), response);
+                } catch (RuntimeException ex) {
+                    throw new BatchExecutionException(childNode.getRequest(), 
ex);
+                }
+                callRequestRecursive(resolvedChildRequest, childNode, 
responseList, uriInfo);
 
+            });
+        } else {
+            responseList.addAll(parentRequestFailedRecursive(request, 
requestNode));
+        }
+        // If the current request fails, then all the child requests are not 
executed. If we want to write out all the
+        // child requests, here is the place.
+    }
+
+    /**
+     * All requests recursively are set to status 409 if the parent request 
fails.
+     *
+     * @param request
+     *            the current request
+     * @param requestNode
+     *            the current request node
+     * @return {@code BatchResponse} list of the generated batch responses
+     */
+    private List<BatchResponse> parentRequestFailedRecursive(BatchRequest 
request, BatchRequestNode requestNode) {
+        List<BatchResponse> responseList = new ArrayList<>();
+        
BatchRequestContextHolder.getEnclosingTransaction().ifPresent(TransactionExecution::setRollbackOnly);
+        BatchResponse errorResponse = new BatchResponse();
+        errorResponse.setRequestId(request.getRequestId());
+        errorResponse.setStatusCode(Status.CONFLICT.getStatusCode());
+
+        // Some detail information about the error
+        final ErrorInfo conflictError = new 
ErrorInfo(Status.CONFLICT.getStatusCode(), 8001,
+                "Parent request with id " + request.getRequestId() + " was 
erroneous!");
+        errorResponse.setBody(conflictError.getMessage());
+        requestNode.getChildRequests().stream()
+                .flatMap(childNode -> 
parentRequestFailedRecursive(childNode.getRequest(), 
childNode).stream()).forEach(responseList::add);
+        return responseList;
     }
 
-    private BatchResponse safelyExecuteStrategy(CommandStrategy 
commandStrategy, BatchRequest request, UriInfo originalUriInfo,
-            boolean isEnclosingTransaction) {
+    /**
+     * Execute the request
+     *
+     * @param request
+     * @param uriInfo
+     * @return
+     */
+    private BatchResponse executeRequest(BatchRequest request, UriInfo 
uriInfo) {
+        final CommandStrategy commandStrategy = this.strategyProvider
+                
.getCommandStrategy(CommandContext.resource(request.getRelativeUrl()).method(request.getMethod()).build());
+        log.debug("Batch request: method [{}], relative url [{}]", 
request.getMethod(), request.getRelativeUrl());
+        Either<RuntimeException, BatchRequest> preprocessorResult = 
runPreprocessors(request);
+        if (preprocessorResult.isLeft()) {
+            throw new BatchExecutionException(request, 
preprocessorResult.getLeft());
+        } else {
+            request = preprocessorResult.get();
+        }
         try {
-            if (isEnclosingTransaction) {
-                entityManager.flush();
-            }
             BatchRequestContextHolder.setRequestAttributes(new 
HashMap<>(Optional.ofNullable(request.getHeaders())
                     .map(list -> 
list.stream().collect(Collectors.toMap(Header::getName, Header::getValue)))
                     .orElse(Collections.emptyMap())));
-
-            return new BatchCallHandler(this.batchFilters, 
commandStrategy::execute).serviceCall(request, originalUriInfo);
+            BatchCallHandler callHandler = new 
BatchCallHandler(this.batchFilters, commandStrategy::execute);
+            if 
(BatchRequestContextHolder.getEnclosingTransaction().isPresent()) {
+                if 
(BatchRequestContextHolder.getEnclosingTransaction().get().isRollbackOnly()) {
+                    return new BatchResponse();
+                }
+                entityManager.flush();
+            }
+            final BatchResponse rootResponse = 
callHandler.serviceCall(request, uriInfo);
+            log.debug("Batch response: status code [{}], method [{}], relative 
url [{}]", rootResponse.getStatusCode(), request.getMethod(),
+                    request.getRelativeUrl());
+            return rootResponse;
         } catch (AbstractIdempotentCommandException idempotentException) {
             return handleIdempotentRequests(idempotentException, request);
-        } catch (RuntimeException e) {
-            log.warn("Exception while executing batch strategy {}", 
commandStrategy.getClass().getSimpleName(), e);
-
-            ErrorInfo ex = ErrorHandler.handler(e);
-
-            final BatchResponse response = new BatchResponse();
-            response.setRequestId(request.getRequestId());
-            response.setHeaders(request.getHeaders());
-            response.setStatusCode(ex.getStatusCode());
-            response.setBody(ex.getMessage());
-            return response;
+        } catch (RuntimeException ex) {
+            throw new BatchExecutionException(request, ex);
         } finally {
             BatchRequestContextHolder.resetRequestAttributes();
         }
     }
 
+    private Either<RuntimeException, BatchRequest> 
runPreprocessors(BatchRequest request) {
+        return runPreprocessor(batchPreprocessors, request);
+    }
+
+    private Either<RuntimeException, BatchRequest> 
runPreprocessor(List<BatchRequestPreprocessor> remainingPreprocessor,
+            BatchRequest request) {
+        if (remainingPreprocessor.isEmpty()) {
+            return Either.right(request);
+        } else {
+            BatchRequestPreprocessor preprocessor = 
remainingPreprocessor.get(0);
+            Either<RuntimeException, BatchRequest> processingResult = 
preprocessor.preprocess(request);
+            if (processingResult.isLeft()) {
+                return processingResult;
+            } else {
+                return runPreprocessor(remainingPreprocessor.subList(1, 
remainingPreprocessor.size()), processingResult.get());
+            }
+        }
+    }
+
+    /**
+     * Return the idempotent response when idempotent exception raised
+     *
+     * @param idempotentException
+     *            the idempotent exception
+     * @param request
+     *            the called request
+     * @return
+     */
     private BatchResponse 
handleIdempotentRequests(AbstractIdempotentCommandException 
idempotentException, BatchRequest request) {
         BatchResponse response = new BatchResponse();
         response.setRequestId(request.getRequestId());
@@ -167,98 +276,96 @@ public class BatchApiServiceImpl implements 
BatchApiService {
         return response;
     }
 
-    private List<BatchResponse> processChildRequests(final BatchRequestNode 
rootRequest, BatchResponse rootResponse, UriInfo uriInfo,
-            boolean isEnclosingTransaction) {
-
-        final List<BatchResponse> childResponses = new ArrayList<>();
-        if (!rootRequest.getChildRequests().isEmpty()) {
-
-            for (BatchRequestNode childNode : rootRequest.getChildRequests()) {
-
-                BatchRequest childRequest = childNode.getRequest();
-                BatchResponse childResponse;
-
-                try {
-
-                    if (rootResponse.getStatusCode().equals(200)) {
-                        childRequest = 
this.resolutionHelper.resoluteRequest(childRequest, rootResponse);
-                        final CommandStrategy commandStrategy = 
this.strategyProvider.getCommandStrategy(
-                                
CommandContext.resource(childRequest.getRelativeUrl()).method(childRequest.getMethod()).build());
-
-                        childResponse = safelyExecuteStrategy(commandStrategy, 
childRequest, uriInfo, isEnclosingTransaction);
-                    } else {
-                        // Something went wrong with the parent request, create
-                        // a response with status code 409
-                        childResponse = new BatchResponse();
-                        
childResponse.setRequestId(childRequest.getRequestId());
-                        
childResponse.setStatusCode(Status.CONFLICT.getStatusCode());
+    /**
+     * Run each request root step in a separated transaction
+     *
+     * @param requestList
+     * @param uriInfo
+     * @return
+     */
+    @Override
+    public List<BatchResponse> 
handleBatchRequestsWithoutEnclosingTransaction(final List<BatchRequest> 
requestList, UriInfo uriInfo) {
+        BatchRequestContextHolder.setEnclosingTransaction(Optional.empty());
+        return handleBatchRequests(false, requestList, uriInfo);
+    }
 
-                        // Some detail information about the error
-                        final ErrorInfo conflictError = new 
ErrorInfo(Status.CONFLICT.getStatusCode(), 8001,
-                                "Parent request with id " + 
rootResponse.getRequestId() + " was erroneous!");
-                        childResponse.setBody(conflictError.getMessage());
-                    }
-                    childResponses.addAll(this.processChildRequests(childNode, 
childResponse, uriInfo, isEnclosingTransaction));
+    /**
+     * Run the batch request in transaction
+     *
+     * @param requestList
+     * @param uriInfo
+     * @return
+     */
+    @Override
+    public List<BatchResponse> 
handleBatchRequestsWithEnclosingTransaction(final List<BatchRequest> 
requestList, final UriInfo uriInfo) {
+        return callInTransaction(() -> handleBatchRequests(true, requestList, 
uriInfo));
+    }
 
-                } catch (Throwable ex) {
+    @NotNull
+    private List<BatchResponse> createErrorResponse(List<BatchResponse> 
responseList, int statusCode) {
+        BatchResponse errResponse = new BatchResponse();
 
-                    childResponse = new BatchResponse();
-                    childResponse.setRequestId(childRequest.getRequestId());
-                    
childResponse.setStatusCode(Response.Status.INTERNAL_SERVER_ERROR.getStatusCode());
-                    childResponse.setBody(ex.getMessage());
+        for (BatchResponse res : responseList) {
+            if (!res.getStatusCode().equals(200)) {
+                errResponse.setBody("Transaction is being rolled back. First 
erroneous request: \n" + new Gson().toJson(res));
+                errResponse.setRequestId(res.getRequestId());
+                if (statusCode == -1) {
+                    statusCode = res.getStatusCode();
                 }
-
-                childResponses.add(childResponse);
+                break;
             }
         }
-
-        return childResponses;
+        errResponse.setStatusCode(statusCode);
+        return Arrays.asList(errResponse);
     }
 
-    @Override
-    public List<BatchResponse> 
handleBatchRequestsWithoutEnclosingTransaction(final List<BatchRequest> 
requestList, UriInfo uriInfo) {
-
-        return handleBatchRequests(requestList, uriInfo, false);
+    private List<BatchResponse> 
callInTransaction(Supplier<List<BatchResponse>> request) {
+        return callInTransaction(Function.identity()::apply, request);
     }
 
-    @Override
-    public List<BatchResponse> 
handleBatchRequestsWithEnclosingTransaction(final List<BatchRequest> 
requestList, final UriInfo uriInfo) {
+    /**
+     * Helper method to run the command in transaction
+     *
+     * @param request
+     *            the enclosing supplier of the command
+     * @param transactionConfigurator
+     *            consumer to configure the transaction behavior and isolation
+     * @return
+     */
+    private List<BatchResponse> 
callInTransaction(Consumer<TransactionTemplate> transactionConfigurator,
+            Supplier<List<BatchResponse>> request) {
         List<BatchResponse> responseList = new ArrayList<>();
         try {
-            return this.transactionTemplate.execute(status -> {
+            TransactionTemplate transactionTemplate = new 
TransactionTemplate(transactionManager);
+            transactionConfigurator.accept(transactionTemplate);
+            return transactionTemplate.execute(status -> {
+                
BatchRequestContextHolder.setEnclosingTransaction(Optional.of(status));
                 try {
-                    responseList.addAll(handleBatchRequests(requestList, 
uriInfo, true));
+                    responseList.addAll(request.get());
+                    if (status.isRollbackOnly()) {
+                        return createErrorResponse(responseList, -1);
+                    }
                     return responseList;
+                } catch (BatchExecutionException ex) {
+                    status.setRollbackOnly();
+                    BatchResponse errResponse = new BatchResponse();
+                    
errResponse.setStatusCode(ex.getErrorInfo().getStatusCode());
+                    errResponse.setRequestId(ex.getRequest().getRequestId());
+                    errResponse.setBody(ex.getErrorInfo().getMessage());
+                    return Arrays.asList(errResponse);
                 } catch (RuntimeException ex) {
-
+                    status.setRollbackOnly();
                     ErrorInfo e = ErrorHandler.handler(ex);
                     BatchResponse errResponse = new BatchResponse();
                     errResponse.setStatusCode(e.getStatusCode());
                     errResponse.setBody(e.getMessage());
-
-                    List<BatchResponse> errResponseList = new ArrayList<>();
-                    errResponseList.add(errResponse);
-
-                    status.setRollbackOnly();
-                    return errResponseList;
+                    return Arrays.asList(errResponse);
                 }
             });
         } catch (TransactionException | NonTransientDataAccessException ex) {
             ErrorInfo e = ErrorHandler.handler(ex);
-            BatchResponse errResponse = new BatchResponse();
-            errResponse.setStatusCode(e.getStatusCode());
-
-            for (BatchResponse res : responseList) {
-                if (!res.getStatusCode().equals(200)) {
-                    errResponse.setBody("Transaction is being rolled back. 
First erroneous request: \n" + new Gson().toJson(res));
-                    break;
-                }
-            }
-
-            List<BatchResponse> errResponseList = new ArrayList<>();
-            errResponseList.add(errResponse);
-
-            return errResponseList;
+            return createErrorResponse(responseList, e.getStatusCode());
         }
     }
+
 }
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/commands/handler/NewCommandSourceHandler.java
 
b/fineract-provider/src/main/java/org/apache/fineract/batch/service/BatchExecutionException.java
similarity index 56%
copy from 
fineract-provider/src/main/java/org/apache/fineract/commands/handler/NewCommandSourceHandler.java
copy to 
fineract-provider/src/main/java/org/apache/fineract/batch/service/BatchExecutionException.java
index d07e04cd7..4b9ad606a 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/commands/handler/NewCommandSourceHandler.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/batch/service/BatchExecutionException.java
@@ -16,16 +16,22 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.fineract.commands.handler;
+package org.apache.fineract.batch.service;
 
-import org.apache.fineract.infrastructure.core.api.JsonCommand;
-import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
-import org.springframework.transaction.annotation.Isolation;
-import org.springframework.transaction.annotation.Propagation;
-import org.springframework.transaction.annotation.Transactional;
+import lombok.Getter;
+import org.apache.fineract.batch.domain.BatchRequest;
+import org.apache.fineract.batch.exception.ErrorHandler;
+import org.apache.fineract.batch.exception.ErrorInfo;
 
-public interface NewCommandSourceHandler {
+@Getter
+public class BatchExecutionException extends RuntimeException {
 
-    @Transactional(propagation = Propagation.REQUIRES_NEW, isolation = 
Isolation.REPEATABLE_READ)
-    CommandProcessingResult processCommand(JsonCommand command);
+    private final BatchRequest request;
+    private final ErrorInfo errorInfo;
+
+    public BatchExecutionException(BatchRequest request, RuntimeException ex) {
+        super("Error executing batch request: " + request, ex);
+        this.request = request;
+        this.errorInfo = ErrorHandler.handler(ex);
+    }
 }
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/commands/handler/NewCommandSourceHandler.java
 
b/fineract-provider/src/main/java/org/apache/fineract/commands/handler/NewCommandSourceHandler.java
index d07e04cd7..d2fd82ce1 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/commands/handler/NewCommandSourceHandler.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/commands/handler/NewCommandSourceHandler.java
@@ -20,12 +20,8 @@ package org.apache.fineract.commands.handler;
 
 import org.apache.fineract.infrastructure.core.api.JsonCommand;
 import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
-import org.springframework.transaction.annotation.Isolation;
-import org.springframework.transaction.annotation.Propagation;
-import org.springframework.transaction.annotation.Transactional;
 
 public interface NewCommandSourceHandler {
 
-    @Transactional(propagation = Propagation.REQUIRES_NEW, isolation = 
Isolation.REPEATABLE_READ)
     CommandProcessingResult processCommand(JsonCommand command);
 }
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/commands/service/CommandSourceService.java
 
b/fineract-provider/src/main/java/org/apache/fineract/commands/service/CommandSourceService.java
index 882e14041..09406ce1b 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/commands/service/CommandSourceService.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/commands/service/CommandSourceService.java
@@ -30,6 +30,7 @@ import org.apache.fineract.commands.domain.CommandWrapper;
 import org.apache.fineract.commands.exception.CommandNotFoundException;
 import org.apache.fineract.infrastructure.core.api.JsonCommand;
 import org.apache.fineract.useradministration.domain.AppUser;
+import org.jetbrains.annotations.NotNull;
 import org.springframework.stereotype.Component;
 import org.springframework.transaction.annotation.Isolation;
 import org.springframework.transaction.annotation.Propagation;
@@ -48,6 +49,15 @@ public class CommandSourceService {
 
     @Transactional(propagation = Propagation.REQUIRES_NEW, isolation = 
Isolation.REPEATABLE_READ)
     public CommandSource saveInitial(CommandWrapper wrapper, JsonCommand 
jsonCommand, AppUser maker, String idempotencyKey) {
+        return saveInitialInternal(wrapper, jsonCommand, maker, 
idempotencyKey);
+    }
+
+    public CommandSource saveInitialNoTransaction(CommandWrapper wrapper, 
JsonCommand jsonCommand, AppUser maker, String idempotencyKey) {
+        return saveInitialInternal(wrapper, jsonCommand, maker, 
idempotencyKey);
+    }
+
+    @NotNull
+    private CommandSource saveInitialInternal(CommandWrapper wrapper, 
JsonCommand jsonCommand, AppUser maker, String idempotencyKey) {
         CommandSource initialCommandSource = getInitialCommandSource(wrapper, 
jsonCommand, maker, idempotencyKey);
 
         if (initialCommandSource.getCommandJson() == null) {
@@ -57,7 +67,6 @@ public class CommandSourceService {
         return commandSourceRepository.saveAndFlush(initialCommandSource);
     }
 
-    @Transactional(propagation = Propagation.REQUIRES_NEW, isolation = 
Isolation.REPEATABLE_READ)
     public void saveFailed(CommandSource commandSource) {
         commandSource.setStatus(ERROR.getValue());
         commandSourceRepository.saveAndFlush(commandSource);
@@ -68,6 +77,10 @@ public class CommandSourceService {
         return commandSourceRepository.saveAndFlush(commandSource);
     }
 
+    public CommandSource saveResultNoTransaction(CommandSource commandSource) {
+        return commandSourceRepository.saveAndFlush(commandSource);
+    }
+
     public ErrorInfo generateErrorException(Throwable t) {
         if (t instanceof final RuntimeException e) {
             return ErrorHandler.handler(e);
@@ -82,6 +95,11 @@ public class CommandSourceService {
                 idempotencyKey);
     }
 
+    public CommandSource findCommandSourceNoTransaction(CommandWrapper 
wrapper, String idempotencyKey) {
+        return 
commandSourceRepository.findByActionNameAndEntityNameAndIdempotencyKey(wrapper.actionName(),
 wrapper.entityName(),
+                idempotencyKey);
+    }
+
     private CommandSource getInitialCommandSource(CommandWrapper wrapper, 
JsonCommand jsonCommand, AppUser maker, String idempotencyKey) {
         CommandSource commandSourceResult;
         if (jsonCommand.commandId() != null) {
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/commands/service/SynchronousCommandProcessingService.java
 
b/fineract-provider/src/main/java/org/apache/fineract/commands/service/SynchronousCommandProcessingService.java
index e95a702d1..899e3b84d 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/commands/service/SynchronousCommandProcessingService.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/commands/service/SynchronousCommandProcessingService.java
@@ -44,6 +44,7 @@ import 
org.apache.fineract.infrastructure.configuration.domain.ConfigurationDoma
 import org.apache.fineract.infrastructure.core.api.JsonCommand;
 import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
 import 
org.apache.fineract.infrastructure.core.data.CommandProcessingResultBuilder;
+import 
org.apache.fineract.infrastructure.core.domain.BatchRequestContextHolder;
 import 
org.apache.fineract.infrastructure.core.domain.FineractRequestContextHolder;
 import 
org.apache.fineract.infrastructure.core.exception.AbstractIdempotentCommandException;
 import 
org.apache.fineract.infrastructure.core.exception.IdempotentCommandProcessFailedException;
@@ -94,9 +95,13 @@ public class SynchronousCommandProcessingService implements 
CommandProcessingSer
         exceptionWhenTheRequestAlreadyProcessed(wrapper, idempotencyKey);
 
         // Store idempotency key to the request attribute
-
-        CommandSource savedCommandSource = 
commandSourceService.saveInitial(wrapper, command, 
context.authenticatedUser(wrapper),
-                idempotencyKey);
+        CommandSource savedCommandSource;
+        if (BatchRequestContextHolder.getEnclosingTransaction().isPresent()) {
+            savedCommandSource = 
commandSourceService.saveInitialNoTransaction(wrapper, command, 
context.authenticatedUser(wrapper),
+                    idempotencyKey);
+        } else {
+            savedCommandSource = commandSourceService.saveInitial(wrapper, 
command, context.authenticatedUser(wrapper), idempotencyKey);
+        }
         storeCommandToIdempotentFilter(savedCommandSource);
         setIdempotencyKeyStoreFlag(true);
 
@@ -104,12 +109,23 @@ public class SynchronousCommandProcessingService 
implements CommandProcessingSer
         try {
             result = findCommandHandler(wrapper).processCommand(command);
         } catch (Throwable t) { // NOSONAR
-            
commandSourceService.saveFailed(commandSourceService.findCommandSource(wrapper, 
idempotencyKey));
+            CommandSource source;
+            if 
(BatchRequestContextHolder.getEnclosingTransaction().isPresent()) {
+                source = 
commandSourceService.findCommandSourceNoTransaction(wrapper, idempotencyKey);
+            } else {
+                source = commandSourceService.findCommandSource(wrapper, 
idempotencyKey);
+            }
+            commandSourceService.saveFailed(source);
             publishHookErrorEvent(wrapper, command, t);
             throw t;
         }
+        CommandSource initialCommandSource;
+        if (BatchRequestContextHolder.getEnclosingTransaction().isPresent()) {
+            initialCommandSource = 
commandSourceService.findCommandSourceNoTransaction(wrapper, idempotencyKey);
+        } else {
+            initialCommandSource = 
commandSourceService.findCommandSource(wrapper, idempotencyKey);
+        }
 
-        CommandSource initialCommandSource = 
commandSourceService.findCommandSource(wrapper, idempotencyKey);
         
initialCommandSource.setResult(toApiJsonSerializer.serializeResult(result));
         initialCommandSource.updateResourceId(result.getResourceId());
         initialCommandSource.updateForAudit(result);
@@ -120,7 +136,11 @@ public class SynchronousCommandProcessingService 
implements CommandProcessingSer
         }
 
         
initialCommandSource.setStatus(CommandProcessingResultType.PROCESSED.getValue());
-        commandSourceService.saveResult(initialCommandSource);
+        if (BatchRequestContextHolder.getEnclosingTransaction().isPresent()) {
+            commandSourceService.saveResultNoTransaction(initialCommandSource);
+        } else {
+            commandSourceService.saveResult(initialCommandSource);
+        }
 
         if ((rollbackTransaction || result.isRollbackTransaction()) && 
!isApprovedByChecker) {
             /*
@@ -187,7 +207,7 @@ public class SynchronousCommandProcessingService implements 
CommandProcessingSer
         if (commandSourceResult.getIdempotencyKey() == null) {
             
commandSourceResult.setIdempotencyKey(idempotencyKeyGenerator.create());
         }
-        commandSourceResult = 
commandSourceService.saveResult(commandSourceResult);
+        commandSourceResult = 
commandSourceService.saveResultNoTransaction(commandSourceResult);
 
         return new 
CommandProcessingResultBuilder().withCommandId(commandSourceResult.getId())
                 .withEntityId(commandSourceResult.getResourceId()).build();
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/domain/BatchRequestContextHolder.java
 
b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/domain/BatchRequestContextHolder.java
index 9cdbbf36a..a807a70d9 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/domain/BatchRequestContextHolder.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/domain/BatchRequestContextHolder.java
@@ -19,7 +19,9 @@
 package org.apache.fineract.infrastructure.core.domain;
 
 import java.util.Map;
+import java.util.Optional;
 import org.springframework.core.NamedThreadLocal;
+import org.springframework.transaction.TransactionStatus;
 
 public final class BatchRequestContextHolder {
 
@@ -27,6 +29,14 @@ public final class BatchRequestContextHolder {
 
     private static final ThreadLocal<Map<String, Object>> batchAttributes = 
new NamedThreadLocal<>("BatchAttributesForProcessing");
 
+    private static final ThreadLocal<Optional<TransactionStatus>> 
enclosingTransaction = new NamedThreadLocal<>("EnclosingTransaction") {
+
+        @Override
+        protected Optional<TransactionStatus> initialValue() {
+            return Optional.empty();
+        }
+    };
+
     /**
      * True if the batch attributes are set
      *
@@ -36,6 +46,15 @@ public final class BatchRequestContextHolder {
         return batchAttributes.get() != null;
     }
 
+    /**
+     * True if the batch attributes are set and the enclosing transaction is 
set to true
+     *
+     * @return
+     */
+    public static Optional<TransactionStatus> getEnclosingTransaction() {
+        return enclosingTransaction.get();
+    }
+
     /**
      * Set the batch attributes for the current thread.
      *
@@ -61,4 +80,13 @@ public final class BatchRequestContextHolder {
     public static void resetRequestAttributes() {
         batchAttributes.remove();
     }
+
+    /**
+     * Set the enclosing transaction flag for the current thread.
+     *
+     * @param isEnclosingTransaction
+     */
+    public static void setEnclosingTransaction(Optional<TransactionStatus> 
enclosingTransaction) {
+        
BatchRequestContextHolder.enclosingTransaction.set(enclosingTransaction);
+    }
 }
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/commands/handler/NewCommandSourceHandler.java
 
b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/filters/BatchRequestPreprocessor.java
similarity index 58%
copy from 
fineract-provider/src/main/java/org/apache/fineract/commands/handler/NewCommandSourceHandler.java
copy to 
fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/filters/BatchRequestPreprocessor.java
index d07e04cd7..0addc3efe 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/commands/handler/NewCommandSourceHandler.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/filters/BatchRequestPreprocessor.java
@@ -16,16 +16,12 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.fineract.commands.handler;
+package org.apache.fineract.infrastructure.core.filters;
 
-import org.apache.fineract.infrastructure.core.api.JsonCommand;
-import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
-import org.springframework.transaction.annotation.Isolation;
-import org.springframework.transaction.annotation.Propagation;
-import org.springframework.transaction.annotation.Transactional;
+import io.github.resilience4j.core.functions.Either;
+import org.apache.fineract.batch.domain.BatchRequest;
 
-public interface NewCommandSourceHandler {
+public interface BatchRequestPreprocessor {
 
-    @Transactional(propagation = Propagation.REQUIRES_NEW, isolation = 
Isolation.REPEATABLE_READ)
-    CommandProcessingResult processCommand(JsonCommand command);
+    Either<RuntimeException, BatchRequest> preprocess(BatchRequest 
batchRequest);
 }
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 52d4d2f97..b2a6de590 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
@@ -70,7 +70,7 @@ public class IdempotencyStoreFilter extends 
OncePerRequestFilter implements Batc
         boolean isSuccessWithoutStored = isStoreIdempotencyKey(request) && 
commandId.isPresent() && isAllowedContentTypeResponse(response)
                 && wrapper.getValue() != null;
         if (isSuccessWithoutStored) {
-            storeCommandResult(response.getStatus(), 
Optional.ofNullable(wrapper.getValue())
+            storeCommandResult(false, response.getStatus(), 
Optional.ofNullable(wrapper.getValue())
                     
.map(ContentCachingResponseWrapper::getContentAsByteArray).map(s -> new 
String(s, StandardCharsets.UTF_8)).orElse(null),
                     commandId);
         }
@@ -79,11 +79,15 @@ public class IdempotencyStoreFilter extends 
OncePerRequestFilter implements Batc
         }
     }
 
-    private void storeCommandResult(int response, String body, Optional<Long> 
commandId) {
+    private void storeCommandResult(boolean batch, int response, String body, 
Optional<Long> commandId) {
         
commandSourceRepository.findById(commandId.get()).ifPresent(commandSource -> {
             commandSource.setResultStatusCode(response);
             commandSource.setResult(body);
-            commandSourceService.saveResult(commandSource);
+            if (batch) {
+                commandSourceService.saveResultNoTransaction(commandSource);
+            } else {
+                commandSourceService.saveResult(commandSource);
+            }
         });
     }
 
@@ -133,7 +137,7 @@ public class IdempotencyStoreFilter extends 
OncePerRequestFilter implements Batc
         Optional<Long> commandId = getCommandId(null);
         boolean isSuccessWithoutStored = isStoreIdempotencyKey(null) && 
commandId.isPresent();
         if (isSuccessWithoutStored) {
-            storeCommandResult(result.getStatusCode(), result.getBody(), 
commandId);
+            storeCommandResult(true, result.getStatusCode(), result.getBody(), 
commandId);
         }
         return result;
     }
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 8c8cb4eb1..02135d9f2 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
@@ -19,6 +19,7 @@
 package org.apache.fineract.infrastructure.jobs.filter;
 
 import com.google.common.collect.Lists;
+import io.github.resilience4j.core.functions.Either;
 import java.io.IOException;
 import java.math.BigDecimal;
 import java.util.ArrayList;
@@ -30,13 +31,11 @@ import javax.servlet.FilterChain;
 import javax.servlet.ServletException;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
-import javax.ws.rs.core.UriInfo;
 import lombok.Getter;
 import lombok.RequiredArgsConstructor;
 import org.apache.commons.collections.CollectionUtils;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.fineract.batch.domain.BatchRequest;
-import org.apache.fineract.batch.domain.BatchResponse;
 import org.apache.fineract.cob.data.LoanIdAndLastClosedBusinessDate;
 import org.apache.fineract.cob.service.InlineLoanCOBExecutorServiceImpl;
 import org.apache.fineract.cob.service.LoanAccountLockService;
@@ -44,24 +43,27 @@ 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.BatchFilter;
-import org.apache.fineract.infrastructure.core.filters.BatchFilterChain;
+import 
org.apache.fineract.infrastructure.core.filters.BatchRequestPreprocessor;
 import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil;
 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.stereotype.Component;
+import org.springframework.transaction.PlatformTransactionManager;
+import org.springframework.transaction.TransactionDefinition;
+import org.springframework.transaction.support.TransactionTemplate;
 import org.springframework.web.filter.OncePerRequestFilter;
 
 @Component
 @RequiredArgsConstructor
-public class LoanCOBApiFilter extends OncePerRequestFilter implements 
BatchFilter {
+public class LoanCOBApiFilter extends OncePerRequestFilter implements 
BatchRequestPreprocessor {
 
     private final GLIMAccountInfoRepository glimAccountInfoRepository;
     private final LoanAccountLockService loanAccountLockService;
@@ -82,6 +84,8 @@ public class LoanCOBApiFilter extends OncePerRequestFilter 
implements BatchFilte
             || LOAN_GLIMACCOUNT_PATH_PATTERN.matcher(s).find();
     private static final String JOB_NAME = "INLINE_LOAN_COB";
 
+    private final PlatformTransactionManager transactionManager;
+
     @RequiredArgsConstructor
     @Getter
     private static class LoanIdsHardLockedException extends RuntimeException {
@@ -107,15 +111,6 @@ public class LoanCOBApiFilter extends OncePerRequestFilter 
implements BatchFilte
             response.setStatus(statusCode);
             response.getWriter().write(message);
         }
-
-        public BatchResponse toBatchResponse(BatchRequest request) {
-            BatchResponse response = new BatchResponse();
-            response.setStatusCode(statusCode);
-            response.setBody(message);
-            response.setHeaders(request.getHeaders());
-            response.setRequestId(request.getRequestId());
-            return response;
-        }
     }
 
     @Override
@@ -251,28 +246,27 @@ public class LoanCOBApiFilter extends 
OncePerRequestFilter implements BatchFilte
     }
 
     @Override
-    public BatchResponse doFilter(BatchRequest batchRequest, UriInfo uriInfo, 
BatchFilterChain chain) {
-        if (!isOnApiList("/" + batchRequest.getRelativeUrl(), 
batchRequest.getMethod())) {
-            return chain.serviceCall(batchRequest, uriInfo);
-        } else {
+    public Either<RuntimeException, BatchRequest> preprocess(BatchRequest 
batchRequest) {
+        TransactionTemplate tr = new TransactionTemplate(transactionManager);
+        
tr.setPropagationBehavior(TransactionDefinition.PROPAGATION_NOT_SUPPORTED);
+        return tr.execute(status -> {
             try {
-                boolean bypassUser = isBypassUser();
-                if (bypassUser) {
-                    return chain.serviceCall(batchRequest, uriInfo);
-                } else {
-                    try {
+                if (isOnApiList("/" + batchRequest.getRelativeUrl(), 
batchRequest.getMethod())) {
+                    boolean bypassUser = isBypassUser();
+                    if (!bypassUser) {
                         List<Long> result = calculateRelevantLoanIds("/" + 
batchRequest.getRelativeUrl());
                         if (!result.isEmpty() && (isLoanSoftLocked(result) || 
isLoanBehind(result))) {
                             executeInlineCob(result);
                         }
-                        return chain.serviceCall(batchRequest, uriInfo);
-                    } catch (LoanIdsHardLockedException e) {
-                        return Reject.reject(e.getLoanIdFromRequest(), 
HttpStatus.SC_CONFLICT).toBatchResponse(batchRequest);
                     }
                 }
-            } catch (UnAuthenticatedUserException e) {
-                return Reject.reject(null, 
HttpStatus.SC_UNAUTHORIZED).toBatchResponse(batchRequest);
+            } 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/portfolio/account/service/AccountTransfersWritePlatformServiceImpl.java
 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/service/AccountTransfersWritePlatformServiceImpl.java
index ffd011ff0..644d6526d 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/service/AccountTransfersWritePlatformServiceImpl.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/service/AccountTransfersWritePlatformServiceImpl.java
@@ -158,7 +158,7 @@ public class AccountTransfersWritePlatformServiceImpl 
implements AccountTransfer
                     transactionDate, transactionAmount, paymentDetail, 
transactionBooleanValues, backdatedTxnsAllowedTill);
 
             final Long toLoanAccountId = 
command.longValueOfParameterNamed(toAccountIdParamName);
-            final Loan toLoanAccount = 
this.loanAccountAssembler.assembleFrom(toLoanAccountId);
+            Loan toLoanAccount = 
this.loanAccountAssembler.assembleFrom(toLoanAccountId);
 
             final Boolean isHolidayValidationDone = false;
             final HolidayDetailDTO holidayDetailDto = null;
@@ -169,7 +169,7 @@ public class AccountTransfersWritePlatformServiceImpl 
implements AccountTransfer
             final LoanTransaction loanRepaymentTransaction = 
this.loanAccountDomainService.makeRepayment(LoanTransactionType.REPAYMENT,
                     toLoanAccount, transactionDate, transactionAmount, 
paymentDetail, null, externalId, isRecoveryRepayment,
                     chargeRefundChargeType, isAccountTransfer, 
holidayDetailDto, isHolidayValidationDone);
-
+            toLoanAccount = loanRepaymentTransaction.getLoan();
             final AccountTransferDetails accountTransferDetails = 
this.accountTransferAssembler.assembleSavingsToLoanTransfer(command,
                     fromSavingsAccount, toLoanAccount, withdrawal, 
loanRepaymentTransaction);
             
this.accountTransferDetailRepository.saveAndFlush(accountTransferDetails);
@@ -340,6 +340,7 @@ public class AccountTransfersWritePlatformServiceImpl 
implements AccountTransfer
                         accountTransferDTO.getTransactionDate(), 
accountTransferDTO.getTransactionAmount(),
                         accountTransferDTO.getPaymentDetail(), null, 
externalId, isRecoveryRepayment, chargeRefundChargeType,
                         isAccountTransfer, holidayDetailDto, 
isHolidayValidationDone);
+                toLoanAccount = loanTransaction.getLoan();
             }
 
             accountTransferDetails = 
this.accountTransferAssembler.assembleSavingsToLoanTransfer(accountTransferDTO, 
fromSavingsAccount,
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java
 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java
index 6c335697c..e56edebdf 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java
@@ -2687,7 +2687,7 @@ public class Loan extends 
AbstractAuditableWithUTCDateTimeCustom {
                 for (final Map.Entry<Long, LoanTransaction> mapEntry : 
changedTransactionDetail.getNewTransactionMappings().entrySet()) {
                     mapEntry.getValue().updateLoan(this);
                 }
-
+                
this.loanTransactions.addAll(changedTransactionDetail.getNewTransactionMappings().values());
             }
             updateLoanSummaryDerivedFields();
         }
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanAccountDomainServiceJpa.java
 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanAccountDomainServiceJpa.java
index 883fc942c..6a0b36d43 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanAccountDomainServiceJpa.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanAccountDomainServiceJpa.java
@@ -216,7 +216,8 @@ public class LoanAccountDomainServiceJpa implements 
LoanAccountDomainService {
          * before the latest payment recorded against the loan)
          ***/
         if (changedTransactionDetail != null) {
-            for (Map.Entry<Long, LoanTransaction> mapEntry : 
changedTransactionDetail.getNewTransactionMappings().entrySet()) {
+            for (final Map.Entry<Long, LoanTransaction> mapEntry : 
changedTransactionDetail.getNewTransactionMappings().entrySet()) {
+                
saveLoanTransactionWithDataIntegrityViolationChecks(mapEntry.getValue());
                 updateLoanTransaction(mapEntry.getKey(), mapEntry.getValue());
             }
             // Trigger transaction replayed event
@@ -752,8 +753,8 @@ public class LoanAccountDomainServiceJpa implements 
LoanAccountDomainService {
     }
 
     @Override
-    public LoanTransaction foreCloseLoan(final Loan loan, final LocalDate 
foreClosureDate, final String noteText,
-            final ExternalId externalId, Map<String, Object> changes) {
+    public LoanTransaction foreCloseLoan(Loan loan, final LocalDate 
foreClosureDate, final String noteText, final ExternalId externalId,
+            Map<String, Object> changes) {
         if (loan.isChargedOff() && 
foreClosureDate.isBefore(loan.getChargedOffOnDate())) {
             throw new 
GeneralPlatformDomainRuleException("error.msg.transaction.date.cannot.be.earlier.than.charge.off.date",
 "Loan: "
                     + loan.getId()
@@ -836,14 +837,14 @@ public class LoanAccountDomainServiceJpa implements 
LoanAccountDomainService {
         changes.put("eventAmount", payPrincipal.getAmount().negate());
 
         if (changedTransactionDetail != null) {
-            for (Map.Entry<Long, LoanTransaction> mapEntry : 
changedTransactionDetail.getNewTransactionMappings().entrySet()) {
+            for (final Map.Entry<Long, LoanTransaction> mapEntry : 
changedTransactionDetail.getNewTransactionMappings().entrySet()) {
+                
saveLoanTransactionWithDataIntegrityViolationChecks(mapEntry.getValue());
                 updateLoanTransaction(mapEntry.getKey(), mapEntry.getValue());
             }
             // Trigger transaction replayed event
             
replayedTransactionBusinessEventService.raiseTransactionReplayedEvents(changedTransactionDetail);
         }
-
-        saveAndFlushLoanWithDataIntegrityViolationChecks(loan);
+        loan = saveAndFlushLoanWithDataIntegrityViolationChecks(loan);
 
         if (StringUtils.isNotBlank(noteText)) {
             changes.put("note", noteText);
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/guarantor/service/GuarantorDomainServiceImpl.java
 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/guarantor/service/GuarantorDomainServiceImpl.java
index dc988e93d..e531b8a07 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/guarantor/service/GuarantorDomainServiceImpl.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/guarantor/service/GuarantorDomainServiceImpl.java
@@ -52,6 +52,7 @@ import 
org.apache.fineract.portfolio.account.domain.AccountTransferDetails;
 import org.apache.fineract.portfolio.account.domain.AccountTransferType;
 import 
org.apache.fineract.portfolio.account.service.AccountTransfersWritePlatformService;
 import org.apache.fineract.portfolio.loanaccount.domain.Loan;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanRepository;
 import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
 import org.apache.fineract.portfolio.loanaccount.guarantor.GuarantorConstants;
 import org.apache.fineract.portfolio.loanaccount.guarantor.domain.Guarantor;
@@ -85,6 +86,7 @@ public class GuarantorDomainServiceImpl implements 
GuarantorDomainService {
     private final SavingsAccountAssembler savingsAccountAssembler;
     private final ConfigurationDomainService configurationDomainService;
     private final ExternalIdFactory externalIdFactory;
+    private final LoanRepository loanRepository;
 
     @PostConstruct
     public void addListeners() {
@@ -232,28 +234,27 @@ public class GuarantorDomainServiceImpl implements 
GuarantorDomainService {
         final AccountTransferDetails accountTransferDetails = null;
         final String noteText = null;
 
-        final SavingsAccount toSavingsAccount = null;
-
         Long loanId = loan.getId();
 
         for (Guarantor guarantor : existGuarantorList) {
             final List<GuarantorFundingDetails> fundingDetails = 
guarantor.getGuarantorFundDetails();
             for (GuarantorFundingDetails guarantorFundingDetails : 
fundingDetails) {
+                Loan freshLoan = loanRepository.findById(loanId).orElseThrow();
                 if (guarantorFundingDetails.getStatus().isActive()) {
                     final SavingsAccount fromSavingsAccount = 
guarantorFundingDetails.getLinkedSavingsAccount();
                     final Long fromAccountId = fromSavingsAccount.getId();
                     releaseLoanIds.put(loanId, 
guarantorFundingDetails.getId());
                     try {
                         BigDecimal remainingAmount = 
guarantorFundingDetails.getAmountRemaining();
-                        if 
(loan.getGuaranteeAmount().compareTo(loan.getPrincipal().getAmount()) > 0) {
-                            remainingAmount = 
remainingAmount.multiply(loan.getPrincipal().getAmount()).divide(loan.getGuaranteeAmount(),
-                                    MoneyHelper.getRoundingMode());
+                        if 
(freshLoan.getGuaranteeAmount().compareTo(freshLoan.getPrincipal().getAmount()) 
> 0) {
+                            remainingAmount = 
remainingAmount.multiply(freshLoan.getPrincipal().getAmount())
+                                    .divide(freshLoan.getGuaranteeAmount(), 
MoneyHelper.getRoundingMode());
                         }
                         ExternalId externalId = externalIdFactory.create();
                         AccountTransferDTO accountTransferDTO = new 
AccountTransferDTO(transactionDate, remainingAmount, fromAccountType,
                                 toAccountType, fromAccountId, toAccountId, 
description, locale, fmt, paymentDetail, fromTransferType,
                                 toTransferType, chargeId, 
loanInstallmentNumber, transferType, accountTransferDetails, noteText, 
externalId,
-                                loan, toSavingsAccount, fromSavingsAccount, 
isRegularTransaction, isExceptionForBalanceCheck);
+                                null, null, fromSavingsAccount, 
isRegularTransaction, isExceptionForBalanceCheck);
                         transferAmount(accountTransferDTO);
                     } finally {
                         releaseLoanIds.remove(loanId);
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/service/LoanRescheduleRequestWritePlatformServiceImpl.java
 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/service/LoanRescheduleRequestWritePlatformServiceImpl.java
index 13771d033..8368e603d 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/service/LoanRescheduleRequestWritePlatformServiceImpl.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/service/LoanRescheduleRequestWritePlatformServiceImpl.java
@@ -451,15 +451,21 @@ public class 
LoanRescheduleRequestWritePlatformServiceImpl implements LoanResche
             // update the status of the request
             loanRescheduleRequest.approve(appUser, approvedOnDate);
 
+            /***
+             * TODO Vishwas Batch save is giving me a 
HibernateOptimisticLockingFailureException, looping and saving for
+             * the time being, not a major issue for now as this loop is 
entered only in edge cases (when a adjustment
+             * is made before the latest payment recorded against the loan)
+             ***/
             if (changedTransactionDetail != null) {
                 for (final Map.Entry<Long, LoanTransaction> mapEntry : 
changedTransactionDetail.getNewTransactionMappings().entrySet()) {
-                    
this.accountTransfersWritePlatformService.updateLoanTransaction(mapEntry.getKey(),
 mapEntry.getValue());
+                    
loanAccountDomainService.saveLoanTransactionWithDataIntegrityViolationChecks(mapEntry.getValue());
+                    
accountTransfersWritePlatformService.updateLoanTransaction(mapEntry.getKey(), 
mapEntry.getValue());
                 }
                 // Trigger transaction replayed event
                 
replayedTransactionBusinessEventService.raiseTransactionReplayedEvents(changedTransactionDetail);
             }
-            // update the loan object
             loan = saveAndFlushLoanWithDataIntegrityViolationChecks(loan);
+            // update the loan object
             postJournalEntries(loan, existingTransactionIds, 
existingReversedTransactionIds);
             
loanAccrualTransactionBusinessEventService.raiseBusinessEventForAccrualTransactions(loan,
 existingTransactionIds);
 
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanChargeWritePlatformServiceImpl.java
 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanChargeWritePlatformServiceImpl.java
index 38d00e516..342ca5583 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanChargeWritePlatformServiceImpl.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanChargeWritePlatformServiceImpl.java
@@ -243,7 +243,7 @@ public class LoanChargeWritePlatformServiceImpl implements 
LoanChargeWritePlatfo
         if (loan.repaymentScheduleDetail().isInterestRecalculationEnabled()) {
             if (isAppliedOnBackDate && 
loan.isFeeCompoundingEnabledForInterestRecalculation()) {
 
-                runScheduleRecalculation(loan, recalculateFrom);
+                loan = runScheduleRecalculation(loan, recalculateFrom);
                 reprocessRequired = false;
             }
             this.loanWritePlatformService.updateOriginalSchedule(loan);
@@ -252,14 +252,15 @@ public class LoanChargeWritePlatformServiceImpl 
implements LoanChargeWritePlatfo
             ChangedTransactionDetail changedTransactionDetail = 
loan.reprocessTransactions();
             if (changedTransactionDetail != null) {
                 for (final Map.Entry<Long, LoanTransaction> mapEntry : 
changedTransactionDetail.getNewTransactionMappings().entrySet()) {
-                    
this.accountTransfersWritePlatformService.updateLoanTransaction(mapEntry.getKey(),
 mapEntry.getValue());
+                    
loanAccountDomainService.saveLoanTransactionWithDataIntegrityViolationChecks(mapEntry.getValue());
+                    
accountTransfersWritePlatformService.updateLoanTransaction(mapEntry.getKey(), 
mapEntry.getValue());
                 }
                 // Trigger transaction replayed event
                 
replayedTransactionBusinessEventService.raiseTransactionReplayedEvents(changedTransactionDetail);
             }
+            loan = 
loanAccountDomainService.saveAndFlushLoanWithDataIntegrityViolationChecks(loan);
 
         }
-        loan = 
loanAccountDomainService.saveAndFlushLoanWithDataIntegrityViolationChecks(loan);
 
         postJournalEntries(loan, existingTransactionIds, 
existingReversedTransactionIds);
 
@@ -760,7 +761,7 @@ public class LoanChargeWritePlatformServiceImpl implements 
LoanChargeWritePlatfo
 
             if 
(loan.repaymentScheduleDetail().isInterestRecalculationEnabled()) {
                 if (runInterestRecalculation && 
loan.isFeeCompoundingEnabledForInterestRecalculation()) {
-                    runScheduleRecalculation(loan, recalculateFrom);
+                    loan = runScheduleRecalculation(loan, recalculateFrom);
                     reprocessRequired = false;
                 }
                 this.loanWritePlatformService.updateOriginalSchedule(loan);
@@ -772,11 +773,13 @@ public class LoanChargeWritePlatformServiceImpl 
implements LoanChargeWritePlatfo
                 if (changedTransactionDetail != null) {
                     for (final Map.Entry<Long, LoanTransaction> mapEntry : 
changedTransactionDetail.getNewTransactionMappings()
                             .entrySet()) {
-                        
this.accountTransfersWritePlatformService.updateLoanTransaction(mapEntry.getKey(),
 mapEntry.getValue());
+                        
loanAccountDomainService.saveLoanTransactionWithDataIntegrityViolationChecks(mapEntry.getValue());
+                        
accountTransfersWritePlatformService.updateLoanTransaction(mapEntry.getKey(), 
mapEntry.getValue());
                     }
                     // Trigger transaction replayed event
                     
replayedTransactionBusinessEventService.raiseTransactionReplayedEvents(changedTransactionDetail);
                 }
+                loan = 
loanAccountDomainService.saveAndFlushLoanWithDataIntegrityViolationChecks(loan);
             }
 
             postJournalEntries(loan, existingTransactionIds, 
existingReversedTransactionIds);
@@ -1112,22 +1115,24 @@ public class LoanChargeWritePlatformServiceImpl 
implements LoanChargeWritePlatfo
         }
     }
 
-    public void runScheduleRecalculation(final Loan loan, final LocalDate 
recalculateFrom) {
+    public Loan runScheduleRecalculation(Loan loan, final LocalDate 
recalculateFrom) {
         if (loan.repaymentScheduleDetail().isInterestRecalculationEnabled()) {
             final List<Long> existingTransactionIds = 
loan.findExistingTransactionIds();
             ScheduleGeneratorDTO generatorDTO = 
this.loanUtilService.buildScheduleGeneratorDTO(loan, recalculateFrom);
             ChangedTransactionDetail changedTransactionDetail = loan
                     
.handleRegenerateRepaymentScheduleWithInterestRecalculation(generatorDTO);
-            this.loanRepositoryWrapper.save(loan);
             if (changedTransactionDetail != null) {
                 for (final Map.Entry<Long, LoanTransaction> mapEntry : 
changedTransactionDetail.getNewTransactionMappings().entrySet()) {
-                    
this.accountTransfersWritePlatformService.updateLoanTransaction(mapEntry.getKey(),
 mapEntry.getValue());
+                    
loanAccountDomainService.saveLoanTransactionWithDataIntegrityViolationChecks(mapEntry.getValue());
+                    
accountTransfersWritePlatformService.updateLoanTransaction(mapEntry.getKey(), 
mapEntry.getValue());
                 }
                 // Trigger transaction replayed event
                 
replayedTransactionBusinessEventService.raiseTransactionReplayedEvents(changedTransactionDetail);
             }
+            loan = 
loanAccountDomainService.saveAndFlushLoanWithDataIntegrityViolationChecks(loan);
             
loanAccrualTransactionBusinessEventService.raiseBusinessEventForAccrualTransactions(loan,
 existingTransactionIds);
         }
+        return loan;
     }
 
     private JsonCommand 
adaptLoanChargeRefundCommandForFurtherRepaymentProcessing(JsonCommand command, 
BigDecimal fullRefundAbleAmount) {
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java
 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java
index f0e389c26..97bd28814 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java
@@ -105,9 +105,7 @@ import 
org.apache.fineract.portfolio.account.domain.AccountAssociationsRepositor
 import 
org.apache.fineract.portfolio.account.domain.AccountTransferDetailRepository;
 import org.apache.fineract.portfolio.account.domain.AccountTransferDetails;
 import 
org.apache.fineract.portfolio.account.domain.AccountTransferRecurrenceType;
-import org.apache.fineract.portfolio.account.domain.AccountTransferRepository;
 import 
org.apache.fineract.portfolio.account.domain.AccountTransferStandingInstruction;
-import org.apache.fineract.portfolio.account.domain.AccountTransferTransaction;
 import org.apache.fineract.portfolio.account.domain.AccountTransferType;
 import 
org.apache.fineract.portfolio.account.domain.StandingInstructionPriority;
 import org.apache.fineract.portfolio.account.domain.StandingInstructionStatus;
@@ -223,7 +221,6 @@ public class LoanWritePlatformServiceJpaRepositoryImpl 
implements LoanWritePlatf
     private final AccountAssociationsReadPlatformService 
accountAssociationsReadPlatformService;
     private final LoanReadPlatformService loanReadPlatformService;
     private final FromJsonHelper fromApiJsonHelper;
-    private final AccountTransferRepository accountTransferRepository;
     private final CalendarRepository calendarRepository;
     private final LoanScheduleHistoryWritePlatformService 
loanScheduleHistoryWritePlatformService;
     private final LoanApplicationCommandFromApiJsonHelper 
loanApplicationCommandFromApiJsonHelper;
@@ -460,13 +457,12 @@ public class LoanWritePlatformServiceJpaRepositoryImpl 
implements LoanWritePlatf
         if (!changes.isEmpty()) {
             if (changedTransactionDetail != null) {
                 for (final Map.Entry<Long, LoanTransaction> mapEntry : 
changedTransactionDetail.getNewTransactionMappings().entrySet()) {
-                    this.loanTransactionRepository.save(mapEntry.getValue());
-                    
this.accountTransfersWritePlatformService.updateLoanTransaction(mapEntry.getKey(),
 mapEntry.getValue());
+                    
loanAccountDomainService.saveLoanTransactionWithDataIntegrityViolationChecks(mapEntry.getValue());
+                    
accountTransfersWritePlatformService.updateLoanTransaction(mapEntry.getKey(), 
mapEntry.getValue());
                 }
                 // Trigger transaction replayed event
                 
replayedTransactionBusinessEventService.raiseTransactionReplayedEvents(changedTransactionDetail);
             }
-
             loan = saveAndFlushLoanWithDataIntegrityViolationChecks(loan);
 
             final String noteText = 
command.stringValueOfParameterNamed("note");
@@ -745,8 +741,8 @@ public class LoanWritePlatformServiceJpaRepositoryImpl 
implements LoanWritePlatf
                 if (changedTransactionDetail != null) {
                     for (final Map.Entry<Long, LoanTransaction> mapEntry : 
changedTransactionDetail.getNewTransactionMappings()
                             .entrySet()) {
-                        
this.loanTransactionRepository.save(mapEntry.getValue());
-                        
this.accountTransfersWritePlatformService.updateLoanTransaction(mapEntry.getKey(),
 mapEntry.getValue());
+                        
loanAccountDomainService.saveLoanTransactionWithDataIntegrityViolationChecks(mapEntry.getValue());
+                        
accountTransfersWritePlatformService.updateLoanTransaction(mapEntry.getKey(), 
mapEntry.getValue());
                     }
                     // Trigger transaction replayed event
                     
replayedTransactionBusinessEventService.raiseTransactionReplayedEvents(changedTransactionDetail);
@@ -949,7 +945,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl 
implements LoanWritePlatf
         if (!txnExternalId.isEmpty()) {
             changes.put(LoanApiConstants.externalIdParameterName, 
txnExternalId);
         }
-        final Loan loan = this.loanAssembler.assembleFrom(loanId);
+        Loan loan = this.loanAssembler.assembleFrom(loanId);
         final PaymentDetail paymentDetail = 
this.paymentDetailWritePlatformService.createAndPersistPaymentDetail(command, 
changes);
         final Boolean isHolidayValidationDone = false;
         final HolidayDetailDTO holidayDetailDto = null;
@@ -958,7 +954,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl 
implements LoanWritePlatf
         LoanTransaction loanTransaction = 
this.loanAccountDomainService.makeRepayment(repaymentTransactionType, loan, 
transactionDate,
                 transactionAmount, paymentDetail, noteText, txnExternalId, 
isRecoveryRepayment, chargeRefundChargeType, isAccountTransfer,
                 holidayDetailDto, isHolidayValidationDone);
-
+        loan = loanTransaction.getLoan();
         // Update loan transaction on repayment.
         if (AccountType.fromInt(loan.getLoanType()).isIndividualAccount()) {
             Set<LoanCollateralManagement> loanCollateralManagements = 
loan.getLoanCollateralManagements();
@@ -1156,7 +1152,8 @@ public class LoanWritePlatformServiceJpaRepositoryImpl 
implements LoanWritePlatf
          ***/
         if (changedTransactionDetail != null) {
             for (final Map.Entry<Long, LoanTransaction> mapEntry : 
changedTransactionDetail.getNewTransactionMappings().entrySet()) {
-                
this.accountTransfersWritePlatformService.updateLoanTransaction(mapEntry.getKey(),
 mapEntry.getValue());
+                
loanAccountDomainService.saveLoanTransactionWithDataIntegrityViolationChecks(mapEntry.getValue());
+                
accountTransfersWritePlatformService.updateLoanTransaction(mapEntry.getKey(), 
mapEntry.getValue());
             }
             // Trigger transaction replayed event
             
replayedTransactionBusinessEventService.raiseTransactionReplayedEvents(changedTransactionDetail);
@@ -1391,7 +1388,8 @@ public class LoanWritePlatformServiceJpaRepositoryImpl 
implements LoanWritePlatf
 
         if (changedTransactionDetail != null) {
             for (final Map.Entry<Long, LoanTransaction> mapEntry : 
changedTransactionDetail.getNewTransactionMappings().entrySet()) {
-                
this.accountTransfersWritePlatformService.updateLoanTransaction(mapEntry.getKey(),
 mapEntry.getValue());
+                
loanAccountDomainService.saveLoanTransactionWithDataIntegrityViolationChecks(mapEntry.getValue());
+                
accountTransfersWritePlatformService.updateLoanTransaction(mapEntry.getKey(), 
mapEntry.getValue());
             }
             // Trigger transaction replayed event
             
replayedTransactionBusinessEventService.raiseTransactionReplayedEvents(changedTransactionDetail);
@@ -2171,7 +2169,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl 
implements LoanWritePlatf
     @Override
     public CommandProcessingResult undoWriteOff(Long loanId) {
 
-        final Loan loan = this.loanAssembler.assembleFrom(loanId);
+        Loan loan = this.loanAssembler.assembleFrom(loanId);
         checkClientOrGroupActive(loan);
         final List<Long> existingTransactionIds = new ArrayList<>();
         final List<Long> existingReversedTransactionIds = new ArrayList<>();
@@ -2193,13 +2191,13 @@ public class LoanWritePlatformServiceJpaRepositoryImpl 
implements LoanWritePlatf
                 existingReversedTransactionIds, scheduleGeneratorDTO);
         if (changedTransactionDetail != null) {
             for (final Map.Entry<Long, LoanTransaction> mapEntry : 
changedTransactionDetail.getNewTransactionMappings().entrySet()) {
-                this.loanTransactionRepository.save(mapEntry.getValue());
-                
this.accountTransfersWritePlatformService.updateLoanTransaction(mapEntry.getKey(),
 mapEntry.getValue());
+                
loanAccountDomainService.saveLoanTransactionWithDataIntegrityViolationChecks(mapEntry.getValue());
+                
accountTransfersWritePlatformService.updateLoanTransaction(mapEntry.getKey(), 
mapEntry.getValue());
             }
             // Trigger transaction replayed event
             
replayedTransactionBusinessEventService.raiseTransactionReplayedEvents(changedTransactionDetail);
         }
-        saveLoanWithDataIntegrityViolationChecks(loan);
+        loan = saveAndFlushLoanWithDataIntegrityViolationChecks(loan);
 
         postJournalEntries(loan, existingTransactionIds, 
existingReversedTransactionIds);
         
loanAccrualTransactionBusinessEventService.raiseBusinessEventForAccrualTransactions(loan,
 existingTransactionIds);
@@ -2340,9 +2338,9 @@ public class LoanWritePlatformServiceJpaRepositoryImpl 
implements LoanWritePlatf
         }
 
         if (command.entityId() != null && changedTransactionDetail != null) {
-            for (Map.Entry<Long, LoanTransaction> mapEntry : 
changedTransactionDetail.getNewTransactionMappings().entrySet()) {
-                this.loanTransactionRepository.save(mapEntry.getValue());
-                updateLoanTransaction(mapEntry.getKey(), mapEntry.getValue());
+            for (final Map.Entry<Long, LoanTransaction> mapEntry : 
changedTransactionDetail.getNewTransactionMappings().entrySet()) {
+                
loanAccountDomainService.saveLoanTransactionWithDataIntegrityViolationChecks(mapEntry.getValue());
+                
accountTransfersWritePlatformService.updateLoanTransaction(mapEntry.getKey(), 
mapEntry.getValue());
             }
             // Trigger transaction replayed event
             
replayedTransactionBusinessEventService.raiseTransactionReplayedEvents(changedTransactionDetail);
@@ -2400,12 +2398,13 @@ public class LoanWritePlatformServiceJpaRepositoryImpl 
implements LoanWritePlatf
 
         if (changedTransactionDetail != null) {
             for (final Map.Entry<Long, LoanTransaction> mapEntry : 
changedTransactionDetail.getNewTransactionMappings().entrySet()) {
-                
this.accountTransfersWritePlatformService.updateLoanTransaction(mapEntry.getKey(),
 mapEntry.getValue());
+                
loanAccountDomainService.saveLoanTransactionWithDataIntegrityViolationChecks(mapEntry.getValue());
+                
accountTransfersWritePlatformService.updateLoanTransaction(mapEntry.getKey(), 
mapEntry.getValue());
             }
             // Trigger transaction replayed event
             
replayedTransactionBusinessEventService.raiseTransactionReplayedEvents(changedTransactionDetail);
         }
-        saveLoanWithDataIntegrityViolationChecks(loan);
+        loan = saveAndFlushLoanWithDataIntegrityViolationChecks(loan);
         postJournalEntries(loan, existingTransactionIds, 
existingReversedTransactionIds);
         
loanAccrualTransactionBusinessEventService.raiseBusinessEventForAccrualTransactions(loan,
 existingTransactionIds);
         loanAccountDomainService.recalculateAccruals(loan);
@@ -2441,14 +2440,6 @@ public class LoanWritePlatformServiceJpaRepositoryImpl 
implements LoanWritePlatf
         }
     }
 
-    private void updateLoanTransaction(final Long loanTransactionId, final 
LoanTransaction newLoanTransaction) {
-        final AccountTransferTransaction transferTransaction = 
this.accountTransferRepository.findByToLoanTransactionId(loanTransactionId);
-        if (transferTransaction != null) {
-            transferTransaction.updateToLoanTransaction(newLoanTransaction);
-            this.accountTransferRepository.save(transferTransaction);
-        }
-    }
-
     private void createLoanScheduleArchive(final Loan loan, final 
ScheduleGeneratorDTO scheduleGeneratorDTO) {
         createAndSaveLoanScheduleArchive(loan, scheduleGeneratorDTO);
 

Reply via email to