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
This commit is contained in:
+214
-107
@@ -19,17 +19,21 @@
|
|||||||
package org.apache.fineract.batch.service;
|
package org.apache.fineract.batch.service;
|
||||||
|
|
||||||
import com.google.gson.Gson;
|
import com.google.gson.Gson;
|
||||||
|
import io.github.resilience4j.core.functions.Either;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.Comparator;
|
import java.util.Comparator;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Optional;
|
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 java.util.stream.Collectors;
|
||||||
import javax.persistence.EntityManager;
|
import javax.persistence.EntityManager;
|
||||||
import javax.persistence.PersistenceContext;
|
import javax.persistence.PersistenceContext;
|
||||||
import javax.ws.rs.core.Response;
|
|
||||||
import javax.ws.rs.core.Response.Status;
|
import javax.ws.rs.core.Response.Status;
|
||||||
import javax.ws.rs.core.UriInfo;
|
import javax.ws.rs.core.UriInfo;
|
||||||
import lombok.RequiredArgsConstructor;
|
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.exception.IdempotentCommandProcessUnderProcessingException;
|
||||||
import org.apache.fineract.infrastructure.core.filters.BatchCallHandler;
|
import org.apache.fineract.infrastructure.core.filters.BatchCallHandler;
|
||||||
import org.apache.fineract.infrastructure.core.filters.BatchFilter;
|
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.dao.NonTransientDataAccessException;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.transaction.PlatformTransactionManager;
|
||||||
|
import org.springframework.transaction.TransactionDefinition;
|
||||||
import org.springframework.transaction.TransactionException;
|
import org.springframework.transaction.TransactionException;
|
||||||
|
import org.springframework.transaction.TransactionExecution;
|
||||||
import org.springframework.transaction.support.TransactionTemplate;
|
import org.springframework.transaction.support.TransactionTemplate;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -72,10 +81,12 @@ public class BatchApiServiceImpl implements BatchApiService {
|
|||||||
|
|
||||||
private final CommandStrategyProvider strategyProvider;
|
private final CommandStrategyProvider strategyProvider;
|
||||||
private final ResolutionHelper resolutionHelper;
|
private final ResolutionHelper resolutionHelper;
|
||||||
private final TransactionTemplate transactionTemplate;
|
private final PlatformTransactionManager transactionManager;
|
||||||
|
|
||||||
private final List<BatchFilter> batchFilters;
|
private final List<BatchFilter> batchFilters;
|
||||||
|
|
||||||
|
private final List<BatchRequestPreprocessor> batchPreprocessors;
|
||||||
|
|
||||||
@PersistenceContext
|
@PersistenceContext
|
||||||
private final EntityManager entityManager;
|
private final EntityManager entityManager;
|
||||||
|
|
||||||
@@ -87,8 +98,8 @@ public class BatchApiServiceImpl implements BatchApiService {
|
|||||||
* @param uriInfo
|
* @param uriInfo
|
||||||
* @return {@code List<BatchResponse>}
|
* @return {@code List<BatchResponse>}
|
||||||
*/
|
*/
|
||||||
private List<BatchResponse> handleBatchRequests(final List<BatchRequest> requestList, final UriInfo uriInfo,
|
private List<BatchResponse> handleBatchRequests(boolean enclosingTransaction, final List<BatchRequest> requestList,
|
||||||
boolean isEnclosingTransaction) {
|
final UriInfo uriInfo) {
|
||||||
|
|
||||||
final List<BatchResponse> responseList = new ArrayList<>(requestList.size());
|
final List<BatchResponse> responseList = new ArrayList<>(requestList.size());
|
||||||
|
|
||||||
@@ -103,52 +114,150 @@ public class BatchApiServiceImpl implements BatchApiService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for (BatchRequestNode rootNode : batchRequestNodes) {
|
for (BatchRequestNode rootNode : batchRequestNodes) {
|
||||||
final BatchRequest rootRequest = rootNode.getRequest();
|
if (enclosingTransaction) {
|
||||||
final CommandStrategy commandStrategy = this.strategyProvider
|
this.callRequestRecursive(rootNode.getRequest(), rootNode, responseList, uriInfo);
|
||||||
.getCommandStrategy(CommandContext.resource(rootRequest.getRelativeUrl()).method(rootRequest.getMethod()).build());
|
} else {
|
||||||
log.debug("Batch request: method [{}], relative url [{}]", rootRequest.getMethod(), rootRequest.getRelativeUrl());
|
responseList.addAll(callInTransaction(
|
||||||
final BatchResponse rootResponse = safelyExecuteStrategy(commandStrategy, rootRequest, uriInfo, isEnclosingTransaction);
|
transactionTemplate -> transactionTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW),
|
||||||
log.debug("Batch response: status code [{}], method [{}], relative url [{}]", rootResponse.getStatusCode(),
|
() -> {
|
||||||
rootRequest.getMethod(), rootRequest.getRelativeUrl());
|
List<BatchResponse> localResponseList = new ArrayList<>();
|
||||||
responseList.add(rootResponse);
|
this.callRequestRecursive(rootNode.getRequest(), rootNode, localResponseList, uriInfo);
|
||||||
responseList.addAll(this.processChildRequests(rootNode, rootResponse, uriInfo, isEnclosingTransaction));
|
return localResponseList;
|
||||||
|
}));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Collections.sort(responseList, Comparator.comparing(BatchResponse::getRequestId));
|
Collections.sort(responseList, Comparator.comparing(BatchResponse::getRequestId));
|
||||||
|
|
||||||
return responseList;
|
return responseList;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private BatchResponse safelyExecuteStrategy(CommandStrategy commandStrategy, BatchRequest request, UriInfo originalUriInfo,
|
/**
|
||||||
boolean isEnclosingTransaction) {
|
* 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 {
|
try {
|
||||||
if (isEnclosingTransaction) {
|
|
||||||
entityManager.flush();
|
|
||||||
}
|
|
||||||
BatchRequestContextHolder.setRequestAttributes(new HashMap<>(Optional.ofNullable(request.getHeaders())
|
BatchRequestContextHolder.setRequestAttributes(new HashMap<>(Optional.ofNullable(request.getHeaders())
|
||||||
.map(list -> list.stream().collect(Collectors.toMap(Header::getName, Header::getValue)))
|
.map(list -> list.stream().collect(Collectors.toMap(Header::getName, Header::getValue)))
|
||||||
.orElse(Collections.emptyMap())));
|
.orElse(Collections.emptyMap())));
|
||||||
|
BatchCallHandler callHandler = new BatchCallHandler(this.batchFilters, commandStrategy::execute);
|
||||||
return new BatchCallHandler(this.batchFilters, commandStrategy::execute).serviceCall(request, originalUriInfo);
|
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) {
|
} catch (AbstractIdempotentCommandException idempotentException) {
|
||||||
return handleIdempotentRequests(idempotentException, request);
|
return handleIdempotentRequests(idempotentException, request);
|
||||||
} catch (RuntimeException e) {
|
} catch (RuntimeException ex) {
|
||||||
log.warn("Exception while executing batch strategy {}", commandStrategy.getClass().getSimpleName(), e);
|
throw new BatchExecutionException(request, ex);
|
||||||
|
|
||||||
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;
|
|
||||||
} finally {
|
} finally {
|
||||||
BatchRequestContextHolder.resetRequestAttributes();
|
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) {
|
private BatchResponse handleIdempotentRequests(AbstractIdempotentCommandException idempotentException, BatchRequest request) {
|
||||||
BatchResponse response = new BatchResponse();
|
BatchResponse response = new BatchResponse();
|
||||||
response.setRequestId(request.getRequestId());
|
response.setRequestId(request.getRequestId());
|
||||||
@@ -167,98 +276,96 @@ public class BatchApiServiceImpl implements BatchApiService {
|
|||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<BatchResponse> processChildRequests(final BatchRequestNode rootRequest, BatchResponse rootResponse, UriInfo uriInfo,
|
/**
|
||||||
boolean isEnclosingTransaction) {
|
* Run each request root step in a separated transaction
|
||||||
|
*
|
||||||
final List<BatchResponse> childResponses = new ArrayList<>();
|
* @param requestList
|
||||||
if (!rootRequest.getChildRequests().isEmpty()) {
|
* @param uriInfo
|
||||||
|
* @return
|
||||||
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());
|
|
||||||
|
|
||||||
// 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));
|
|
||||||
|
|
||||||
} catch (Throwable ex) {
|
|
||||||
|
|
||||||
childResponse = new BatchResponse();
|
|
||||||
childResponse.setRequestId(childRequest.getRequestId());
|
|
||||||
childResponse.setStatusCode(Response.Status.INTERNAL_SERVER_ERROR.getStatusCode());
|
|
||||||
childResponse.setBody(ex.getMessage());
|
|
||||||
}
|
|
||||||
|
|
||||||
childResponses.add(childResponse);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return childResponses;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<BatchResponse> handleBatchRequestsWithoutEnclosingTransaction(final List<BatchRequest> requestList, UriInfo uriInfo) {
|
public List<BatchResponse> handleBatchRequestsWithoutEnclosingTransaction(final List<BatchRequest> requestList, UriInfo uriInfo) {
|
||||||
|
BatchRequestContextHolder.setEnclosingTransaction(Optional.empty());
|
||||||
return handleBatchRequests(requestList, uriInfo, false);
|
return handleBatchRequests(false, requestList, uriInfo);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Run the batch request in transaction
|
||||||
|
*
|
||||||
|
* @param requestList
|
||||||
|
* @param uriInfo
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public List<BatchResponse> handleBatchRequestsWithEnclosingTransaction(final List<BatchRequest> requestList, final UriInfo uriInfo) {
|
public List<BatchResponse> handleBatchRequestsWithEnclosingTransaction(final List<BatchRequest> requestList, final UriInfo uriInfo) {
|
||||||
|
return callInTransaction(() -> handleBatchRequests(true, requestList, uriInfo));
|
||||||
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
private List<BatchResponse> createErrorResponse(List<BatchResponse> responseList, int statusCode) {
|
||||||
|
BatchResponse errResponse = new BatchResponse();
|
||||||
|
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
errResponse.setStatusCode(statusCode);
|
||||||
|
return Arrays.asList(errResponse);
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<BatchResponse> callInTransaction(Supplier<List<BatchResponse>> request) {
|
||||||
|
return callInTransaction(Function.identity()::apply, request);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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<>();
|
List<BatchResponse> responseList = new ArrayList<>();
|
||||||
try {
|
try {
|
||||||
return this.transactionTemplate.execute(status -> {
|
TransactionTemplate transactionTemplate = new TransactionTemplate(transactionManager);
|
||||||
|
transactionConfigurator.accept(transactionTemplate);
|
||||||
|
return transactionTemplate.execute(status -> {
|
||||||
|
BatchRequestContextHolder.setEnclosingTransaction(Optional.of(status));
|
||||||
try {
|
try {
|
||||||
responseList.addAll(handleBatchRequests(requestList, uriInfo, true));
|
responseList.addAll(request.get());
|
||||||
|
if (status.isRollbackOnly()) {
|
||||||
|
return createErrorResponse(responseList, -1);
|
||||||
|
}
|
||||||
return responseList;
|
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) {
|
} catch (RuntimeException ex) {
|
||||||
|
status.setRollbackOnly();
|
||||||
ErrorInfo e = ErrorHandler.handler(ex);
|
ErrorInfo e = ErrorHandler.handler(ex);
|
||||||
BatchResponse errResponse = new BatchResponse();
|
BatchResponse errResponse = new BatchResponse();
|
||||||
errResponse.setStatusCode(e.getStatusCode());
|
errResponse.setStatusCode(e.getStatusCode());
|
||||||
errResponse.setBody(e.getMessage());
|
errResponse.setBody(e.getMessage());
|
||||||
|
return Arrays.asList(errResponse);
|
||||||
List<BatchResponse> errResponseList = new ArrayList<>();
|
|
||||||
errResponseList.add(errResponse);
|
|
||||||
|
|
||||||
status.setRollbackOnly();
|
|
||||||
return errResponseList;
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} catch (TransactionException | NonTransientDataAccessException ex) {
|
} catch (TransactionException | NonTransientDataAccessException ex) {
|
||||||
ErrorInfo e = ErrorHandler.handler(ex);
|
ErrorInfo e = ErrorHandler.handler(ex);
|
||||||
BatchResponse errResponse = new BatchResponse();
|
return createErrorResponse(responseList, e.getStatusCode());
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
+37
@@ -0,0 +1,37 @@
|
|||||||
|
/**
|
||||||
|
* 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.batch.service;
|
||||||
|
|
||||||
|
import lombok.Getter;
|
||||||
|
import org.apache.fineract.batch.domain.BatchRequest;
|
||||||
|
import org.apache.fineract.batch.exception.ErrorHandler;
|
||||||
|
import org.apache.fineract.batch.exception.ErrorInfo;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
public class BatchExecutionException extends RuntimeException {
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
-4
@@ -20,12 +20,8 @@ package org.apache.fineract.commands.handler;
|
|||||||
|
|
||||||
import org.apache.fineract.infrastructure.core.api.JsonCommand;
|
import org.apache.fineract.infrastructure.core.api.JsonCommand;
|
||||||
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
|
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 {
|
public interface NewCommandSourceHandler {
|
||||||
|
|
||||||
@Transactional(propagation = Propagation.REQUIRES_NEW, isolation = Isolation.REPEATABLE_READ)
|
|
||||||
CommandProcessingResult processCommand(JsonCommand command);
|
CommandProcessingResult processCommand(JsonCommand command);
|
||||||
}
|
}
|
||||||
|
|||||||
+19
-1
@@ -30,6 +30,7 @@ import org.apache.fineract.commands.domain.CommandWrapper;
|
|||||||
import org.apache.fineract.commands.exception.CommandNotFoundException;
|
import org.apache.fineract.commands.exception.CommandNotFoundException;
|
||||||
import org.apache.fineract.infrastructure.core.api.JsonCommand;
|
import org.apache.fineract.infrastructure.core.api.JsonCommand;
|
||||||
import org.apache.fineract.useradministration.domain.AppUser;
|
import org.apache.fineract.useradministration.domain.AppUser;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
import org.springframework.transaction.annotation.Isolation;
|
import org.springframework.transaction.annotation.Isolation;
|
||||||
import org.springframework.transaction.annotation.Propagation;
|
import org.springframework.transaction.annotation.Propagation;
|
||||||
@@ -48,6 +49,15 @@ public class CommandSourceService {
|
|||||||
|
|
||||||
@Transactional(propagation = Propagation.REQUIRES_NEW, isolation = Isolation.REPEATABLE_READ)
|
@Transactional(propagation = Propagation.REQUIRES_NEW, isolation = Isolation.REPEATABLE_READ)
|
||||||
public CommandSource saveInitial(CommandWrapper wrapper, JsonCommand jsonCommand, AppUser maker, String idempotencyKey) {
|
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);
|
CommandSource initialCommandSource = getInitialCommandSource(wrapper, jsonCommand, maker, idempotencyKey);
|
||||||
|
|
||||||
if (initialCommandSource.getCommandJson() == null) {
|
if (initialCommandSource.getCommandJson() == null) {
|
||||||
@@ -57,7 +67,6 @@ public class CommandSourceService {
|
|||||||
return commandSourceRepository.saveAndFlush(initialCommandSource);
|
return commandSourceRepository.saveAndFlush(initialCommandSource);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Transactional(propagation = Propagation.REQUIRES_NEW, isolation = Isolation.REPEATABLE_READ)
|
|
||||||
public void saveFailed(CommandSource commandSource) {
|
public void saveFailed(CommandSource commandSource) {
|
||||||
commandSource.setStatus(ERROR.getValue());
|
commandSource.setStatus(ERROR.getValue());
|
||||||
commandSourceRepository.saveAndFlush(commandSource);
|
commandSourceRepository.saveAndFlush(commandSource);
|
||||||
@@ -68,6 +77,10 @@ public class CommandSourceService {
|
|||||||
return commandSourceRepository.saveAndFlush(commandSource);
|
return commandSourceRepository.saveAndFlush(commandSource);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public CommandSource saveResultNoTransaction(CommandSource commandSource) {
|
||||||
|
return commandSourceRepository.saveAndFlush(commandSource);
|
||||||
|
}
|
||||||
|
|
||||||
public ErrorInfo generateErrorException(Throwable t) {
|
public ErrorInfo generateErrorException(Throwable t) {
|
||||||
if (t instanceof final RuntimeException e) {
|
if (t instanceof final RuntimeException e) {
|
||||||
return ErrorHandler.handler(e);
|
return ErrorHandler.handler(e);
|
||||||
@@ -82,6 +95,11 @@ public class CommandSourceService {
|
|||||||
idempotencyKey);
|
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) {
|
private CommandSource getInitialCommandSource(CommandWrapper wrapper, JsonCommand jsonCommand, AppUser maker, String idempotencyKey) {
|
||||||
CommandSource commandSourceResult;
|
CommandSource commandSourceResult;
|
||||||
if (jsonCommand.commandId() != null) {
|
if (jsonCommand.commandId() != null) {
|
||||||
|
|||||||
+27
-7
@@ -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.api.JsonCommand;
|
||||||
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
|
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
|
||||||
import org.apache.fineract.infrastructure.core.data.CommandProcessingResultBuilder;
|
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.domain.FineractRequestContextHolder;
|
||||||
import org.apache.fineract.infrastructure.core.exception.AbstractIdempotentCommandException;
|
import org.apache.fineract.infrastructure.core.exception.AbstractIdempotentCommandException;
|
||||||
import org.apache.fineract.infrastructure.core.exception.IdempotentCommandProcessFailedException;
|
import org.apache.fineract.infrastructure.core.exception.IdempotentCommandProcessFailedException;
|
||||||
@@ -94,9 +95,13 @@ public class SynchronousCommandProcessingService implements CommandProcessingSer
|
|||||||
exceptionWhenTheRequestAlreadyProcessed(wrapper, idempotencyKey);
|
exceptionWhenTheRequestAlreadyProcessed(wrapper, idempotencyKey);
|
||||||
|
|
||||||
// Store idempotency key to the request attribute
|
// Store idempotency key to the request attribute
|
||||||
|
CommandSource savedCommandSource;
|
||||||
CommandSource savedCommandSource = commandSourceService.saveInitial(wrapper, command, context.authenticatedUser(wrapper),
|
if (BatchRequestContextHolder.getEnclosingTransaction().isPresent()) {
|
||||||
idempotencyKey);
|
savedCommandSource = commandSourceService.saveInitialNoTransaction(wrapper, command, context.authenticatedUser(wrapper),
|
||||||
|
idempotencyKey);
|
||||||
|
} else {
|
||||||
|
savedCommandSource = commandSourceService.saveInitial(wrapper, command, context.authenticatedUser(wrapper), idempotencyKey);
|
||||||
|
}
|
||||||
storeCommandToIdempotentFilter(savedCommandSource);
|
storeCommandToIdempotentFilter(savedCommandSource);
|
||||||
setIdempotencyKeyStoreFlag(true);
|
setIdempotencyKeyStoreFlag(true);
|
||||||
|
|
||||||
@@ -104,12 +109,23 @@ public class SynchronousCommandProcessingService implements CommandProcessingSer
|
|||||||
try {
|
try {
|
||||||
result = findCommandHandler(wrapper).processCommand(command);
|
result = findCommandHandler(wrapper).processCommand(command);
|
||||||
} catch (Throwable t) { // NOSONAR
|
} 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);
|
publishHookErrorEvent(wrapper, command, t);
|
||||||
throw 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.setResult(toApiJsonSerializer.serializeResult(result));
|
||||||
initialCommandSource.updateResourceId(result.getResourceId());
|
initialCommandSource.updateResourceId(result.getResourceId());
|
||||||
initialCommandSource.updateForAudit(result);
|
initialCommandSource.updateForAudit(result);
|
||||||
@@ -120,7 +136,11 @@ public class SynchronousCommandProcessingService implements CommandProcessingSer
|
|||||||
}
|
}
|
||||||
|
|
||||||
initialCommandSource.setStatus(CommandProcessingResultType.PROCESSED.getValue());
|
initialCommandSource.setStatus(CommandProcessingResultType.PROCESSED.getValue());
|
||||||
commandSourceService.saveResult(initialCommandSource);
|
if (BatchRequestContextHolder.getEnclosingTransaction().isPresent()) {
|
||||||
|
commandSourceService.saveResultNoTransaction(initialCommandSource);
|
||||||
|
} else {
|
||||||
|
commandSourceService.saveResult(initialCommandSource);
|
||||||
|
}
|
||||||
|
|
||||||
if ((rollbackTransaction || result.isRollbackTransaction()) && !isApprovedByChecker) {
|
if ((rollbackTransaction || result.isRollbackTransaction()) && !isApprovedByChecker) {
|
||||||
/*
|
/*
|
||||||
@@ -187,7 +207,7 @@ public class SynchronousCommandProcessingService implements CommandProcessingSer
|
|||||||
if (commandSourceResult.getIdempotencyKey() == null) {
|
if (commandSourceResult.getIdempotencyKey() == null) {
|
||||||
commandSourceResult.setIdempotencyKey(idempotencyKeyGenerator.create());
|
commandSourceResult.setIdempotencyKey(idempotencyKeyGenerator.create());
|
||||||
}
|
}
|
||||||
commandSourceResult = commandSourceService.saveResult(commandSourceResult);
|
commandSourceResult = commandSourceService.saveResultNoTransaction(commandSourceResult);
|
||||||
|
|
||||||
return new CommandProcessingResultBuilder().withCommandId(commandSourceResult.getId())
|
return new CommandProcessingResultBuilder().withCommandId(commandSourceResult.getId())
|
||||||
.withEntityId(commandSourceResult.getResourceId()).build();
|
.withEntityId(commandSourceResult.getResourceId()).build();
|
||||||
|
|||||||
+28
@@ -19,7 +19,9 @@
|
|||||||
package org.apache.fineract.infrastructure.core.domain;
|
package org.apache.fineract.infrastructure.core.domain;
|
||||||
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Optional;
|
||||||
import org.springframework.core.NamedThreadLocal;
|
import org.springframework.core.NamedThreadLocal;
|
||||||
|
import org.springframework.transaction.TransactionStatus;
|
||||||
|
|
||||||
public final class BatchRequestContextHolder {
|
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<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
|
* True if the batch attributes are set
|
||||||
*
|
*
|
||||||
@@ -36,6 +46,15 @@ public final class BatchRequestContextHolder {
|
|||||||
return batchAttributes.get() != null;
|
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.
|
* Set the batch attributes for the current thread.
|
||||||
*
|
*
|
||||||
@@ -61,4 +80,13 @@ public final class BatchRequestContextHolder {
|
|||||||
public static void resetRequestAttributes() {
|
public static void resetRequestAttributes() {
|
||||||
batchAttributes.remove();
|
batchAttributes.remove();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the enclosing transaction flag for the current thread.
|
||||||
|
*
|
||||||
|
* @param isEnclosingTransaction
|
||||||
|
*/
|
||||||
|
public static void setEnclosingTransaction(Optional<TransactionStatus> enclosingTransaction) {
|
||||||
|
BatchRequestContextHolder.enclosingTransaction.set(enclosingTransaction);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+27
@@ -0,0 +1,27 @@
|
|||||||
|
/**
|
||||||
|
* 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 io.github.resilience4j.core.functions.Either;
|
||||||
|
import org.apache.fineract.batch.domain.BatchRequest;
|
||||||
|
|
||||||
|
public interface BatchRequestPreprocessor {
|
||||||
|
|
||||||
|
Either<RuntimeException, BatchRequest> preprocess(BatchRequest batchRequest);
|
||||||
|
}
|
||||||
+8
-4
@@ -70,7 +70,7 @@ public class IdempotencyStoreFilter extends OncePerRequestFilter implements Batc
|
|||||||
boolean isSuccessWithoutStored = isStoreIdempotencyKey(request) && commandId.isPresent() && isAllowedContentTypeResponse(response)
|
boolean isSuccessWithoutStored = isStoreIdempotencyKey(request) && commandId.isPresent() && isAllowedContentTypeResponse(response)
|
||||||
&& wrapper.getValue() != null;
|
&& wrapper.getValue() != null;
|
||||||
if (isSuccessWithoutStored) {
|
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),
|
.map(ContentCachingResponseWrapper::getContentAsByteArray).map(s -> new String(s, StandardCharsets.UTF_8)).orElse(null),
|
||||||
commandId);
|
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 -> {
|
commandSourceRepository.findById(commandId.get()).ifPresent(commandSource -> {
|
||||||
commandSource.setResultStatusCode(response);
|
commandSource.setResultStatusCode(response);
|
||||||
commandSource.setResult(body);
|
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);
|
Optional<Long> commandId = getCommandId(null);
|
||||||
boolean isSuccessWithoutStored = isStoreIdempotencyKey(null) && commandId.isPresent();
|
boolean isSuccessWithoutStored = isStoreIdempotencyKey(null) && commandId.isPresent();
|
||||||
if (isSuccessWithoutStored) {
|
if (isSuccessWithoutStored) {
|
||||||
storeCommandResult(result.getStatusCode(), result.getBody(), commandId);
|
storeCommandResult(true, result.getStatusCode(), result.getBody(), commandId);
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|||||||
+23
-29
@@ -19,6 +19,7 @@
|
|||||||
package org.apache.fineract.infrastructure.jobs.filter;
|
package org.apache.fineract.infrastructure.jobs.filter;
|
||||||
|
|
||||||
import com.google.common.collect.Lists;
|
import com.google.common.collect.Lists;
|
||||||
|
import io.github.resilience4j.core.functions.Either;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
@@ -30,13 +31,11 @@ import javax.servlet.FilterChain;
|
|||||||
import javax.servlet.ServletException;
|
import javax.servlet.ServletException;
|
||||||
import javax.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletRequest;
|
||||||
import javax.servlet.http.HttpServletResponse;
|
import javax.servlet.http.HttpServletResponse;
|
||||||
import javax.ws.rs.core.UriInfo;
|
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import org.apache.commons.collections.CollectionUtils;
|
import org.apache.commons.collections.CollectionUtils;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import org.apache.fineract.batch.domain.BatchRequest;
|
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.data.LoanIdAndLastClosedBusinessDate;
|
||||||
import org.apache.fineract.cob.service.InlineLoanCOBExecutorServiceImpl;
|
import org.apache.fineract.cob.service.InlineLoanCOBExecutorServiceImpl;
|
||||||
import org.apache.fineract.cob.service.LoanAccountLockService;
|
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.config.FineractProperties;
|
||||||
import org.apache.fineract.infrastructure.core.data.ApiGlobalErrorResponse;
|
import org.apache.fineract.infrastructure.core.data.ApiGlobalErrorResponse;
|
||||||
import org.apache.fineract.infrastructure.core.domain.ExternalId;
|
import org.apache.fineract.infrastructure.core.domain.ExternalId;
|
||||||
import org.apache.fineract.infrastructure.core.filters.BatchFilter;
|
import org.apache.fineract.infrastructure.core.filters.BatchRequestPreprocessor;
|
||||||
import org.apache.fineract.infrastructure.core.filters.BatchFilterChain;
|
|
||||||
import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil;
|
import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil;
|
||||||
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
|
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
|
||||||
import org.apache.fineract.portfolio.loanaccount.domain.GLIMAccountInfoRepository;
|
import org.apache.fineract.portfolio.loanaccount.domain.GLIMAccountInfoRepository;
|
||||||
import org.apache.fineract.portfolio.loanaccount.domain.GroupLoanIndividualMonitoringAccount;
|
import org.apache.fineract.portfolio.loanaccount.domain.GroupLoanIndividualMonitoringAccount;
|
||||||
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
|
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
|
||||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepository;
|
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.portfolio.loanaccount.rescheduleloan.domain.LoanRescheduleRequestRepository;
|
||||||
import org.apache.fineract.useradministration.exception.UnAuthenticatedUserException;
|
import org.apache.fineract.useradministration.exception.UnAuthenticatedUserException;
|
||||||
import org.apache.http.HttpStatus;
|
import org.apache.http.HttpStatus;
|
||||||
import org.springframework.http.HttpMethod;
|
import org.springframework.http.HttpMethod;
|
||||||
import org.springframework.stereotype.Component;
|
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;
|
import org.springframework.web.filter.OncePerRequestFilter;
|
||||||
|
|
||||||
@Component
|
@Component
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
public class LoanCOBApiFilter extends OncePerRequestFilter implements BatchFilter {
|
public class LoanCOBApiFilter extends OncePerRequestFilter implements BatchRequestPreprocessor {
|
||||||
|
|
||||||
private final GLIMAccountInfoRepository glimAccountInfoRepository;
|
private final GLIMAccountInfoRepository glimAccountInfoRepository;
|
||||||
private final LoanAccountLockService loanAccountLockService;
|
private final LoanAccountLockService loanAccountLockService;
|
||||||
@@ -82,6 +84,8 @@ public class LoanCOBApiFilter extends OncePerRequestFilter implements BatchFilte
|
|||||||
|| LOAN_GLIMACCOUNT_PATH_PATTERN.matcher(s).find();
|
|| LOAN_GLIMACCOUNT_PATH_PATTERN.matcher(s).find();
|
||||||
private static final String JOB_NAME = "INLINE_LOAN_COB";
|
private static final String JOB_NAME = "INLINE_LOAN_COB";
|
||||||
|
|
||||||
|
private final PlatformTransactionManager transactionManager;
|
||||||
|
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
@Getter
|
@Getter
|
||||||
private static class LoanIdsHardLockedException extends RuntimeException {
|
private static class LoanIdsHardLockedException extends RuntimeException {
|
||||||
@@ -107,15 +111,6 @@ public class LoanCOBApiFilter extends OncePerRequestFilter implements BatchFilte
|
|||||||
response.setStatus(statusCode);
|
response.setStatus(statusCode);
|
||||||
response.getWriter().write(message);
|
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
|
@Override
|
||||||
@@ -251,28 +246,27 @@ public class LoanCOBApiFilter extends OncePerRequestFilter implements BatchFilte
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public BatchResponse doFilter(BatchRequest batchRequest, UriInfo uriInfo, BatchFilterChain chain) {
|
public Either<RuntimeException, BatchRequest> preprocess(BatchRequest batchRequest) {
|
||||||
if (!isOnApiList("/" + batchRequest.getRelativeUrl(), batchRequest.getMethod())) {
|
TransactionTemplate tr = new TransactionTemplate(transactionManager);
|
||||||
return chain.serviceCall(batchRequest, uriInfo);
|
tr.setPropagationBehavior(TransactionDefinition.PROPAGATION_NOT_SUPPORTED);
|
||||||
} else {
|
return tr.execute(status -> {
|
||||||
try {
|
try {
|
||||||
boolean bypassUser = isBypassUser();
|
if (isOnApiList("/" + batchRequest.getRelativeUrl(), batchRequest.getMethod())) {
|
||||||
if (bypassUser) {
|
boolean bypassUser = isBypassUser();
|
||||||
return chain.serviceCall(batchRequest, uriInfo);
|
if (!bypassUser) {
|
||||||
} else {
|
|
||||||
try {
|
|
||||||
List<Long> result = calculateRelevantLoanIds("/" + batchRequest.getRelativeUrl());
|
List<Long> result = calculateRelevantLoanIds("/" + batchRequest.getRelativeUrl());
|
||||||
if (!result.isEmpty() && (isLoanSoftLocked(result) || isLoanBehind(result))) {
|
if (!result.isEmpty() && (isLoanSoftLocked(result) || isLoanBehind(result))) {
|
||||||
executeInlineCob(result);
|
executeInlineCob(result);
|
||||||
}
|
}
|
||||||
return chain.serviceCall(batchRequest, uriInfo);
|
|
||||||
} catch (LoanIdsHardLockedException e) {
|
|
||||||
return Reject.reject(e.getLoanIdFromRequest(), HttpStatus.SC_CONFLICT).toBatchResponse(batchRequest);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (UnAuthenticatedUserException e) {
|
} catch (LoanNotFoundException e) {
|
||||||
return Reject.reject(null, HttpStatus.SC_UNAUTHORIZED).toBatchResponse(batchRequest);
|
return Either.right(batchRequest);
|
||||||
|
} catch (RuntimeException e) {
|
||||||
|
return Either.left(e);
|
||||||
}
|
}
|
||||||
}
|
return Either.right(batchRequest);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
+3
-2
@@ -158,7 +158,7 @@ public class AccountTransfersWritePlatformServiceImpl implements AccountTransfer
|
|||||||
transactionDate, transactionAmount, paymentDetail, transactionBooleanValues, backdatedTxnsAllowedTill);
|
transactionDate, transactionAmount, paymentDetail, transactionBooleanValues, backdatedTxnsAllowedTill);
|
||||||
|
|
||||||
final Long toLoanAccountId = command.longValueOfParameterNamed(toAccountIdParamName);
|
final Long toLoanAccountId = command.longValueOfParameterNamed(toAccountIdParamName);
|
||||||
final Loan toLoanAccount = this.loanAccountAssembler.assembleFrom(toLoanAccountId);
|
Loan toLoanAccount = this.loanAccountAssembler.assembleFrom(toLoanAccountId);
|
||||||
|
|
||||||
final Boolean isHolidayValidationDone = false;
|
final Boolean isHolidayValidationDone = false;
|
||||||
final HolidayDetailDTO holidayDetailDto = null;
|
final HolidayDetailDTO holidayDetailDto = null;
|
||||||
@@ -169,7 +169,7 @@ public class AccountTransfersWritePlatformServiceImpl implements AccountTransfer
|
|||||||
final LoanTransaction loanRepaymentTransaction = this.loanAccountDomainService.makeRepayment(LoanTransactionType.REPAYMENT,
|
final LoanTransaction loanRepaymentTransaction = this.loanAccountDomainService.makeRepayment(LoanTransactionType.REPAYMENT,
|
||||||
toLoanAccount, transactionDate, transactionAmount, paymentDetail, null, externalId, isRecoveryRepayment,
|
toLoanAccount, transactionDate, transactionAmount, paymentDetail, null, externalId, isRecoveryRepayment,
|
||||||
chargeRefundChargeType, isAccountTransfer, holidayDetailDto, isHolidayValidationDone);
|
chargeRefundChargeType, isAccountTransfer, holidayDetailDto, isHolidayValidationDone);
|
||||||
|
toLoanAccount = loanRepaymentTransaction.getLoan();
|
||||||
final AccountTransferDetails accountTransferDetails = this.accountTransferAssembler.assembleSavingsToLoanTransfer(command,
|
final AccountTransferDetails accountTransferDetails = this.accountTransferAssembler.assembleSavingsToLoanTransfer(command,
|
||||||
fromSavingsAccount, toLoanAccount, withdrawal, loanRepaymentTransaction);
|
fromSavingsAccount, toLoanAccount, withdrawal, loanRepaymentTransaction);
|
||||||
this.accountTransferDetailRepository.saveAndFlush(accountTransferDetails);
|
this.accountTransferDetailRepository.saveAndFlush(accountTransferDetails);
|
||||||
@@ -340,6 +340,7 @@ public class AccountTransfersWritePlatformServiceImpl implements AccountTransfer
|
|||||||
accountTransferDTO.getTransactionDate(), accountTransferDTO.getTransactionAmount(),
|
accountTransferDTO.getTransactionDate(), accountTransferDTO.getTransactionAmount(),
|
||||||
accountTransferDTO.getPaymentDetail(), null, externalId, isRecoveryRepayment, chargeRefundChargeType,
|
accountTransferDTO.getPaymentDetail(), null, externalId, isRecoveryRepayment, chargeRefundChargeType,
|
||||||
isAccountTransfer, holidayDetailDto, isHolidayValidationDone);
|
isAccountTransfer, holidayDetailDto, isHolidayValidationDone);
|
||||||
|
toLoanAccount = loanTransaction.getLoan();
|
||||||
}
|
}
|
||||||
|
|
||||||
accountTransferDetails = this.accountTransferAssembler.assembleSavingsToLoanTransfer(accountTransferDTO, fromSavingsAccount,
|
accountTransferDetails = this.accountTransferAssembler.assembleSavingsToLoanTransfer(accountTransferDTO, fromSavingsAccount,
|
||||||
|
|||||||
+1
-1
@@ -2687,7 +2687,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom {
|
|||||||
for (final Map.Entry<Long, LoanTransaction> mapEntry : changedTransactionDetail.getNewTransactionMappings().entrySet()) {
|
for (final Map.Entry<Long, LoanTransaction> mapEntry : changedTransactionDetail.getNewTransactionMappings().entrySet()) {
|
||||||
mapEntry.getValue().updateLoan(this);
|
mapEntry.getValue().updateLoan(this);
|
||||||
}
|
}
|
||||||
|
this.loanTransactions.addAll(changedTransactionDetail.getNewTransactionMappings().values());
|
||||||
}
|
}
|
||||||
updateLoanSummaryDerivedFields();
|
updateLoanSummaryDerivedFields();
|
||||||
}
|
}
|
||||||
|
|||||||
+7
-6
@@ -216,7 +216,8 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
|
|||||||
* before the latest payment recorded against the loan)
|
* before the latest payment recorded against the loan)
|
||||||
***/
|
***/
|
||||||
if (changedTransactionDetail != null) {
|
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());
|
updateLoanTransaction(mapEntry.getKey(), mapEntry.getValue());
|
||||||
}
|
}
|
||||||
// Trigger transaction replayed event
|
// Trigger transaction replayed event
|
||||||
@@ -752,8 +753,8 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public LoanTransaction foreCloseLoan(final Loan loan, final LocalDate foreClosureDate, final String noteText,
|
public LoanTransaction foreCloseLoan(Loan loan, final LocalDate foreClosureDate, final String noteText, final ExternalId externalId,
|
||||||
final ExternalId externalId, Map<String, Object> changes) {
|
Map<String, Object> changes) {
|
||||||
if (loan.isChargedOff() && foreClosureDate.isBefore(loan.getChargedOffOnDate())) {
|
if (loan.isChargedOff() && foreClosureDate.isBefore(loan.getChargedOffOnDate())) {
|
||||||
throw new GeneralPlatformDomainRuleException("error.msg.transaction.date.cannot.be.earlier.than.charge.off.date", "Loan: "
|
throw new GeneralPlatformDomainRuleException("error.msg.transaction.date.cannot.be.earlier.than.charge.off.date", "Loan: "
|
||||||
+ loan.getId()
|
+ loan.getId()
|
||||||
@@ -836,14 +837,14 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
|
|||||||
changes.put("eventAmount", payPrincipal.getAmount().negate());
|
changes.put("eventAmount", payPrincipal.getAmount().negate());
|
||||||
|
|
||||||
if (changedTransactionDetail != null) {
|
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());
|
updateLoanTransaction(mapEntry.getKey(), mapEntry.getValue());
|
||||||
}
|
}
|
||||||
// Trigger transaction replayed event
|
// Trigger transaction replayed event
|
||||||
replayedTransactionBusinessEventService.raiseTransactionReplayedEvents(changedTransactionDetail);
|
replayedTransactionBusinessEventService.raiseTransactionReplayedEvents(changedTransactionDetail);
|
||||||
}
|
}
|
||||||
|
loan = saveAndFlushLoanWithDataIntegrityViolationChecks(loan);
|
||||||
saveAndFlushLoanWithDataIntegrityViolationChecks(loan);
|
|
||||||
|
|
||||||
if (StringUtils.isNotBlank(noteText)) {
|
if (StringUtils.isNotBlank(noteText)) {
|
||||||
changes.put("note", noteText);
|
changes.put("note", noteText);
|
||||||
|
|||||||
+7
-6
@@ -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.domain.AccountTransferType;
|
||||||
import org.apache.fineract.portfolio.account.service.AccountTransfersWritePlatformService;
|
import org.apache.fineract.portfolio.account.service.AccountTransfersWritePlatformService;
|
||||||
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
|
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.domain.LoanTransaction;
|
||||||
import org.apache.fineract.portfolio.loanaccount.guarantor.GuarantorConstants;
|
import org.apache.fineract.portfolio.loanaccount.guarantor.GuarantorConstants;
|
||||||
import org.apache.fineract.portfolio.loanaccount.guarantor.domain.Guarantor;
|
import org.apache.fineract.portfolio.loanaccount.guarantor.domain.Guarantor;
|
||||||
@@ -85,6 +86,7 @@ public class GuarantorDomainServiceImpl implements GuarantorDomainService {
|
|||||||
private final SavingsAccountAssembler savingsAccountAssembler;
|
private final SavingsAccountAssembler savingsAccountAssembler;
|
||||||
private final ConfigurationDomainService configurationDomainService;
|
private final ConfigurationDomainService configurationDomainService;
|
||||||
private final ExternalIdFactory externalIdFactory;
|
private final ExternalIdFactory externalIdFactory;
|
||||||
|
private final LoanRepository loanRepository;
|
||||||
|
|
||||||
@PostConstruct
|
@PostConstruct
|
||||||
public void addListeners() {
|
public void addListeners() {
|
||||||
@@ -232,28 +234,27 @@ public class GuarantorDomainServiceImpl implements GuarantorDomainService {
|
|||||||
final AccountTransferDetails accountTransferDetails = null;
|
final AccountTransferDetails accountTransferDetails = null;
|
||||||
final String noteText = null;
|
final String noteText = null;
|
||||||
|
|
||||||
final SavingsAccount toSavingsAccount = null;
|
|
||||||
|
|
||||||
Long loanId = loan.getId();
|
Long loanId = loan.getId();
|
||||||
|
|
||||||
for (Guarantor guarantor : existGuarantorList) {
|
for (Guarantor guarantor : existGuarantorList) {
|
||||||
final List<GuarantorFundingDetails> fundingDetails = guarantor.getGuarantorFundDetails();
|
final List<GuarantorFundingDetails> fundingDetails = guarantor.getGuarantorFundDetails();
|
||||||
for (GuarantorFundingDetails guarantorFundingDetails : fundingDetails) {
|
for (GuarantorFundingDetails guarantorFundingDetails : fundingDetails) {
|
||||||
|
Loan freshLoan = loanRepository.findById(loanId).orElseThrow();
|
||||||
if (guarantorFundingDetails.getStatus().isActive()) {
|
if (guarantorFundingDetails.getStatus().isActive()) {
|
||||||
final SavingsAccount fromSavingsAccount = guarantorFundingDetails.getLinkedSavingsAccount();
|
final SavingsAccount fromSavingsAccount = guarantorFundingDetails.getLinkedSavingsAccount();
|
||||||
final Long fromAccountId = fromSavingsAccount.getId();
|
final Long fromAccountId = fromSavingsAccount.getId();
|
||||||
releaseLoanIds.put(loanId, guarantorFundingDetails.getId());
|
releaseLoanIds.put(loanId, guarantorFundingDetails.getId());
|
||||||
try {
|
try {
|
||||||
BigDecimal remainingAmount = guarantorFundingDetails.getAmountRemaining();
|
BigDecimal remainingAmount = guarantorFundingDetails.getAmountRemaining();
|
||||||
if (loan.getGuaranteeAmount().compareTo(loan.getPrincipal().getAmount()) > 0) {
|
if (freshLoan.getGuaranteeAmount().compareTo(freshLoan.getPrincipal().getAmount()) > 0) {
|
||||||
remainingAmount = remainingAmount.multiply(loan.getPrincipal().getAmount()).divide(loan.getGuaranteeAmount(),
|
remainingAmount = remainingAmount.multiply(freshLoan.getPrincipal().getAmount())
|
||||||
MoneyHelper.getRoundingMode());
|
.divide(freshLoan.getGuaranteeAmount(), MoneyHelper.getRoundingMode());
|
||||||
}
|
}
|
||||||
ExternalId externalId = externalIdFactory.create();
|
ExternalId externalId = externalIdFactory.create();
|
||||||
AccountTransferDTO accountTransferDTO = new AccountTransferDTO(transactionDate, remainingAmount, fromAccountType,
|
AccountTransferDTO accountTransferDTO = new AccountTransferDTO(transactionDate, remainingAmount, fromAccountType,
|
||||||
toAccountType, fromAccountId, toAccountId, description, locale, fmt, paymentDetail, fromTransferType,
|
toAccountType, fromAccountId, toAccountId, description, locale, fmt, paymentDetail, fromTransferType,
|
||||||
toTransferType, chargeId, loanInstallmentNumber, transferType, accountTransferDetails, noteText, externalId,
|
toTransferType, chargeId, loanInstallmentNumber, transferType, accountTransferDetails, noteText, externalId,
|
||||||
loan, toSavingsAccount, fromSavingsAccount, isRegularTransaction, isExceptionForBalanceCheck);
|
null, null, fromSavingsAccount, isRegularTransaction, isExceptionForBalanceCheck);
|
||||||
transferAmount(accountTransferDTO);
|
transferAmount(accountTransferDTO);
|
||||||
} finally {
|
} finally {
|
||||||
releaseLoanIds.remove(loanId);
|
releaseLoanIds.remove(loanId);
|
||||||
|
|||||||
+8
-2
@@ -451,15 +451,21 @@ public class LoanRescheduleRequestWritePlatformServiceImpl implements LoanResche
|
|||||||
// update the status of the request
|
// update the status of the request
|
||||||
loanRescheduleRequest.approve(appUser, approvedOnDate);
|
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) {
|
if (changedTransactionDetail != null) {
|
||||||
for (final Map.Entry<Long, LoanTransaction> mapEntry : changedTransactionDetail.getNewTransactionMappings().entrySet()) {
|
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
|
// Trigger transaction replayed event
|
||||||
replayedTransactionBusinessEventService.raiseTransactionReplayedEvents(changedTransactionDetail);
|
replayedTransactionBusinessEventService.raiseTransactionReplayedEvents(changedTransactionDetail);
|
||||||
}
|
}
|
||||||
// update the loan object
|
|
||||||
loan = saveAndFlushLoanWithDataIntegrityViolationChecks(loan);
|
loan = saveAndFlushLoanWithDataIntegrityViolationChecks(loan);
|
||||||
|
// update the loan object
|
||||||
postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds);
|
postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds);
|
||||||
loanAccrualTransactionBusinessEventService.raiseBusinessEventForAccrualTransactions(loan, existingTransactionIds);
|
loanAccrualTransactionBusinessEventService.raiseBusinessEventForAccrualTransactions(loan, existingTransactionIds);
|
||||||
|
|
||||||
|
|||||||
+13
-8
@@ -243,7 +243,7 @@ public class LoanChargeWritePlatformServiceImpl implements LoanChargeWritePlatfo
|
|||||||
if (loan.repaymentScheduleDetail().isInterestRecalculationEnabled()) {
|
if (loan.repaymentScheduleDetail().isInterestRecalculationEnabled()) {
|
||||||
if (isAppliedOnBackDate && loan.isFeeCompoundingEnabledForInterestRecalculation()) {
|
if (isAppliedOnBackDate && loan.isFeeCompoundingEnabledForInterestRecalculation()) {
|
||||||
|
|
||||||
runScheduleRecalculation(loan, recalculateFrom);
|
loan = runScheduleRecalculation(loan, recalculateFrom);
|
||||||
reprocessRequired = false;
|
reprocessRequired = false;
|
||||||
}
|
}
|
||||||
this.loanWritePlatformService.updateOriginalSchedule(loan);
|
this.loanWritePlatformService.updateOriginalSchedule(loan);
|
||||||
@@ -252,14 +252,15 @@ public class LoanChargeWritePlatformServiceImpl implements LoanChargeWritePlatfo
|
|||||||
ChangedTransactionDetail changedTransactionDetail = loan.reprocessTransactions();
|
ChangedTransactionDetail changedTransactionDetail = loan.reprocessTransactions();
|
||||||
if (changedTransactionDetail != null) {
|
if (changedTransactionDetail != null) {
|
||||||
for (final Map.Entry<Long, LoanTransaction> mapEntry : changedTransactionDetail.getNewTransactionMappings().entrySet()) {
|
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
|
// Trigger transaction replayed event
|
||||||
replayedTransactionBusinessEventService.raiseTransactionReplayedEvents(changedTransactionDetail);
|
replayedTransactionBusinessEventService.raiseTransactionReplayedEvents(changedTransactionDetail);
|
||||||
}
|
}
|
||||||
|
loan = loanAccountDomainService.saveAndFlushLoanWithDataIntegrityViolationChecks(loan);
|
||||||
|
|
||||||
}
|
}
|
||||||
loan = loanAccountDomainService.saveAndFlushLoanWithDataIntegrityViolationChecks(loan);
|
|
||||||
|
|
||||||
postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds);
|
postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds);
|
||||||
|
|
||||||
@@ -760,7 +761,7 @@ public class LoanChargeWritePlatformServiceImpl implements LoanChargeWritePlatfo
|
|||||||
|
|
||||||
if (loan.repaymentScheduleDetail().isInterestRecalculationEnabled()) {
|
if (loan.repaymentScheduleDetail().isInterestRecalculationEnabled()) {
|
||||||
if (runInterestRecalculation && loan.isFeeCompoundingEnabledForInterestRecalculation()) {
|
if (runInterestRecalculation && loan.isFeeCompoundingEnabledForInterestRecalculation()) {
|
||||||
runScheduleRecalculation(loan, recalculateFrom);
|
loan = runScheduleRecalculation(loan, recalculateFrom);
|
||||||
reprocessRequired = false;
|
reprocessRequired = false;
|
||||||
}
|
}
|
||||||
this.loanWritePlatformService.updateOriginalSchedule(loan);
|
this.loanWritePlatformService.updateOriginalSchedule(loan);
|
||||||
@@ -772,11 +773,13 @@ public class LoanChargeWritePlatformServiceImpl implements LoanChargeWritePlatfo
|
|||||||
if (changedTransactionDetail != null) {
|
if (changedTransactionDetail != null) {
|
||||||
for (final Map.Entry<Long, LoanTransaction> mapEntry : changedTransactionDetail.getNewTransactionMappings()
|
for (final Map.Entry<Long, LoanTransaction> mapEntry : changedTransactionDetail.getNewTransactionMappings()
|
||||||
.entrySet()) {
|
.entrySet()) {
|
||||||
this.accountTransfersWritePlatformService.updateLoanTransaction(mapEntry.getKey(), mapEntry.getValue());
|
loanAccountDomainService.saveLoanTransactionWithDataIntegrityViolationChecks(mapEntry.getValue());
|
||||||
|
accountTransfersWritePlatformService.updateLoanTransaction(mapEntry.getKey(), mapEntry.getValue());
|
||||||
}
|
}
|
||||||
// Trigger transaction replayed event
|
// Trigger transaction replayed event
|
||||||
replayedTransactionBusinessEventService.raiseTransactionReplayedEvents(changedTransactionDetail);
|
replayedTransactionBusinessEventService.raiseTransactionReplayedEvents(changedTransactionDetail);
|
||||||
}
|
}
|
||||||
|
loan = loanAccountDomainService.saveAndFlushLoanWithDataIntegrityViolationChecks(loan);
|
||||||
}
|
}
|
||||||
|
|
||||||
postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds);
|
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()) {
|
if (loan.repaymentScheduleDetail().isInterestRecalculationEnabled()) {
|
||||||
final List<Long> existingTransactionIds = loan.findExistingTransactionIds();
|
final List<Long> existingTransactionIds = loan.findExistingTransactionIds();
|
||||||
ScheduleGeneratorDTO generatorDTO = this.loanUtilService.buildScheduleGeneratorDTO(loan, recalculateFrom);
|
ScheduleGeneratorDTO generatorDTO = this.loanUtilService.buildScheduleGeneratorDTO(loan, recalculateFrom);
|
||||||
ChangedTransactionDetail changedTransactionDetail = loan
|
ChangedTransactionDetail changedTransactionDetail = loan
|
||||||
.handleRegenerateRepaymentScheduleWithInterestRecalculation(generatorDTO);
|
.handleRegenerateRepaymentScheduleWithInterestRecalculation(generatorDTO);
|
||||||
this.loanRepositoryWrapper.save(loan);
|
|
||||||
if (changedTransactionDetail != null) {
|
if (changedTransactionDetail != null) {
|
||||||
for (final Map.Entry<Long, LoanTransaction> mapEntry : changedTransactionDetail.getNewTransactionMappings().entrySet()) {
|
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
|
// Trigger transaction replayed event
|
||||||
replayedTransactionBusinessEventService.raiseTransactionReplayedEvents(changedTransactionDetail);
|
replayedTransactionBusinessEventService.raiseTransactionReplayedEvents(changedTransactionDetail);
|
||||||
}
|
}
|
||||||
|
loan = loanAccountDomainService.saveAndFlushLoanWithDataIntegrityViolationChecks(loan);
|
||||||
loanAccrualTransactionBusinessEventService.raiseBusinessEventForAccrualTransactions(loan, existingTransactionIds);
|
loanAccrualTransactionBusinessEventService.raiseBusinessEventForAccrualTransactions(loan, existingTransactionIds);
|
||||||
}
|
}
|
||||||
|
return loan;
|
||||||
}
|
}
|
||||||
|
|
||||||
private JsonCommand adaptLoanChargeRefundCommandForFurtherRepaymentProcessing(JsonCommand command, BigDecimal fullRefundAbleAmount) {
|
private JsonCommand adaptLoanChargeRefundCommandForFurtherRepaymentProcessing(JsonCommand command, BigDecimal fullRefundAbleAmount) {
|
||||||
|
|||||||
+20
-29
@@ -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.AccountTransferDetailRepository;
|
||||||
import org.apache.fineract.portfolio.account.domain.AccountTransferDetails;
|
import org.apache.fineract.portfolio.account.domain.AccountTransferDetails;
|
||||||
import org.apache.fineract.portfolio.account.domain.AccountTransferRecurrenceType;
|
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.AccountTransferStandingInstruction;
|
||||||
import org.apache.fineract.portfolio.account.domain.AccountTransferTransaction;
|
|
||||||
import org.apache.fineract.portfolio.account.domain.AccountTransferType;
|
import org.apache.fineract.portfolio.account.domain.AccountTransferType;
|
||||||
import org.apache.fineract.portfolio.account.domain.StandingInstructionPriority;
|
import org.apache.fineract.portfolio.account.domain.StandingInstructionPriority;
|
||||||
import org.apache.fineract.portfolio.account.domain.StandingInstructionStatus;
|
import org.apache.fineract.portfolio.account.domain.StandingInstructionStatus;
|
||||||
@@ -223,7 +221,6 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
|||||||
private final AccountAssociationsReadPlatformService accountAssociationsReadPlatformService;
|
private final AccountAssociationsReadPlatformService accountAssociationsReadPlatformService;
|
||||||
private final LoanReadPlatformService loanReadPlatformService;
|
private final LoanReadPlatformService loanReadPlatformService;
|
||||||
private final FromJsonHelper fromApiJsonHelper;
|
private final FromJsonHelper fromApiJsonHelper;
|
||||||
private final AccountTransferRepository accountTransferRepository;
|
|
||||||
private final CalendarRepository calendarRepository;
|
private final CalendarRepository calendarRepository;
|
||||||
private final LoanScheduleHistoryWritePlatformService loanScheduleHistoryWritePlatformService;
|
private final LoanScheduleHistoryWritePlatformService loanScheduleHistoryWritePlatformService;
|
||||||
private final LoanApplicationCommandFromApiJsonHelper loanApplicationCommandFromApiJsonHelper;
|
private final LoanApplicationCommandFromApiJsonHelper loanApplicationCommandFromApiJsonHelper;
|
||||||
@@ -460,13 +457,12 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
|||||||
if (!changes.isEmpty()) {
|
if (!changes.isEmpty()) {
|
||||||
if (changedTransactionDetail != null) {
|
if (changedTransactionDetail != null) {
|
||||||
for (final Map.Entry<Long, LoanTransaction> mapEntry : changedTransactionDetail.getNewTransactionMappings().entrySet()) {
|
for (final Map.Entry<Long, LoanTransaction> mapEntry : changedTransactionDetail.getNewTransactionMappings().entrySet()) {
|
||||||
this.loanTransactionRepository.save(mapEntry.getValue());
|
loanAccountDomainService.saveLoanTransactionWithDataIntegrityViolationChecks(mapEntry.getValue());
|
||||||
this.accountTransfersWritePlatformService.updateLoanTransaction(mapEntry.getKey(), mapEntry.getValue());
|
accountTransfersWritePlatformService.updateLoanTransaction(mapEntry.getKey(), mapEntry.getValue());
|
||||||
}
|
}
|
||||||
// Trigger transaction replayed event
|
// Trigger transaction replayed event
|
||||||
replayedTransactionBusinessEventService.raiseTransactionReplayedEvents(changedTransactionDetail);
|
replayedTransactionBusinessEventService.raiseTransactionReplayedEvents(changedTransactionDetail);
|
||||||
}
|
}
|
||||||
|
|
||||||
loan = saveAndFlushLoanWithDataIntegrityViolationChecks(loan);
|
loan = saveAndFlushLoanWithDataIntegrityViolationChecks(loan);
|
||||||
|
|
||||||
final String noteText = command.stringValueOfParameterNamed("note");
|
final String noteText = command.stringValueOfParameterNamed("note");
|
||||||
@@ -745,8 +741,8 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
|||||||
if (changedTransactionDetail != null) {
|
if (changedTransactionDetail != null) {
|
||||||
for (final Map.Entry<Long, LoanTransaction> mapEntry : changedTransactionDetail.getNewTransactionMappings()
|
for (final Map.Entry<Long, LoanTransaction> mapEntry : changedTransactionDetail.getNewTransactionMappings()
|
||||||
.entrySet()) {
|
.entrySet()) {
|
||||||
this.loanTransactionRepository.save(mapEntry.getValue());
|
loanAccountDomainService.saveLoanTransactionWithDataIntegrityViolationChecks(mapEntry.getValue());
|
||||||
this.accountTransfersWritePlatformService.updateLoanTransaction(mapEntry.getKey(), mapEntry.getValue());
|
accountTransfersWritePlatformService.updateLoanTransaction(mapEntry.getKey(), mapEntry.getValue());
|
||||||
}
|
}
|
||||||
// Trigger transaction replayed event
|
// Trigger transaction replayed event
|
||||||
replayedTransactionBusinessEventService.raiseTransactionReplayedEvents(changedTransactionDetail);
|
replayedTransactionBusinessEventService.raiseTransactionReplayedEvents(changedTransactionDetail);
|
||||||
@@ -949,7 +945,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
|||||||
if (!txnExternalId.isEmpty()) {
|
if (!txnExternalId.isEmpty()) {
|
||||||
changes.put(LoanApiConstants.externalIdParameterName, txnExternalId);
|
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 PaymentDetail paymentDetail = this.paymentDetailWritePlatformService.createAndPersistPaymentDetail(command, changes);
|
||||||
final Boolean isHolidayValidationDone = false;
|
final Boolean isHolidayValidationDone = false;
|
||||||
final HolidayDetailDTO holidayDetailDto = null;
|
final HolidayDetailDTO holidayDetailDto = null;
|
||||||
@@ -958,7 +954,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
|||||||
LoanTransaction loanTransaction = this.loanAccountDomainService.makeRepayment(repaymentTransactionType, loan, transactionDate,
|
LoanTransaction loanTransaction = this.loanAccountDomainService.makeRepayment(repaymentTransactionType, loan, transactionDate,
|
||||||
transactionAmount, paymentDetail, noteText, txnExternalId, isRecoveryRepayment, chargeRefundChargeType, isAccountTransfer,
|
transactionAmount, paymentDetail, noteText, txnExternalId, isRecoveryRepayment, chargeRefundChargeType, isAccountTransfer,
|
||||||
holidayDetailDto, isHolidayValidationDone);
|
holidayDetailDto, isHolidayValidationDone);
|
||||||
|
loan = loanTransaction.getLoan();
|
||||||
// Update loan transaction on repayment.
|
// Update loan transaction on repayment.
|
||||||
if (AccountType.fromInt(loan.getLoanType()).isIndividualAccount()) {
|
if (AccountType.fromInt(loan.getLoanType()).isIndividualAccount()) {
|
||||||
Set<LoanCollateralManagement> loanCollateralManagements = loan.getLoanCollateralManagements();
|
Set<LoanCollateralManagement> loanCollateralManagements = loan.getLoanCollateralManagements();
|
||||||
@@ -1156,7 +1152,8 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
|||||||
***/
|
***/
|
||||||
if (changedTransactionDetail != null) {
|
if (changedTransactionDetail != null) {
|
||||||
for (final Map.Entry<Long, LoanTransaction> mapEntry : changedTransactionDetail.getNewTransactionMappings().entrySet()) {
|
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
|
// Trigger transaction replayed event
|
||||||
replayedTransactionBusinessEventService.raiseTransactionReplayedEvents(changedTransactionDetail);
|
replayedTransactionBusinessEventService.raiseTransactionReplayedEvents(changedTransactionDetail);
|
||||||
@@ -1391,7 +1388,8 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
|||||||
|
|
||||||
if (changedTransactionDetail != null) {
|
if (changedTransactionDetail != null) {
|
||||||
for (final Map.Entry<Long, LoanTransaction> mapEntry : changedTransactionDetail.getNewTransactionMappings().entrySet()) {
|
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
|
// Trigger transaction replayed event
|
||||||
replayedTransactionBusinessEventService.raiseTransactionReplayedEvents(changedTransactionDetail);
|
replayedTransactionBusinessEventService.raiseTransactionReplayedEvents(changedTransactionDetail);
|
||||||
@@ -2171,7 +2169,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
|||||||
@Override
|
@Override
|
||||||
public CommandProcessingResult undoWriteOff(Long loanId) {
|
public CommandProcessingResult undoWriteOff(Long loanId) {
|
||||||
|
|
||||||
final Loan loan = this.loanAssembler.assembleFrom(loanId);
|
Loan loan = this.loanAssembler.assembleFrom(loanId);
|
||||||
checkClientOrGroupActive(loan);
|
checkClientOrGroupActive(loan);
|
||||||
final List<Long> existingTransactionIds = new ArrayList<>();
|
final List<Long> existingTransactionIds = new ArrayList<>();
|
||||||
final List<Long> existingReversedTransactionIds = new ArrayList<>();
|
final List<Long> existingReversedTransactionIds = new ArrayList<>();
|
||||||
@@ -2193,13 +2191,13 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
|||||||
existingReversedTransactionIds, scheduleGeneratorDTO);
|
existingReversedTransactionIds, scheduleGeneratorDTO);
|
||||||
if (changedTransactionDetail != null) {
|
if (changedTransactionDetail != null) {
|
||||||
for (final Map.Entry<Long, LoanTransaction> mapEntry : changedTransactionDetail.getNewTransactionMappings().entrySet()) {
|
for (final Map.Entry<Long, LoanTransaction> mapEntry : changedTransactionDetail.getNewTransactionMappings().entrySet()) {
|
||||||
this.loanTransactionRepository.save(mapEntry.getValue());
|
loanAccountDomainService.saveLoanTransactionWithDataIntegrityViolationChecks(mapEntry.getValue());
|
||||||
this.accountTransfersWritePlatformService.updateLoanTransaction(mapEntry.getKey(), mapEntry.getValue());
|
accountTransfersWritePlatformService.updateLoanTransaction(mapEntry.getKey(), mapEntry.getValue());
|
||||||
}
|
}
|
||||||
// Trigger transaction replayed event
|
// Trigger transaction replayed event
|
||||||
replayedTransactionBusinessEventService.raiseTransactionReplayedEvents(changedTransactionDetail);
|
replayedTransactionBusinessEventService.raiseTransactionReplayedEvents(changedTransactionDetail);
|
||||||
}
|
}
|
||||||
saveLoanWithDataIntegrityViolationChecks(loan);
|
loan = saveAndFlushLoanWithDataIntegrityViolationChecks(loan);
|
||||||
|
|
||||||
postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds);
|
postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds);
|
||||||
loanAccrualTransactionBusinessEventService.raiseBusinessEventForAccrualTransactions(loan, existingTransactionIds);
|
loanAccrualTransactionBusinessEventService.raiseBusinessEventForAccrualTransactions(loan, existingTransactionIds);
|
||||||
@@ -2340,9 +2338,9 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (command.entityId() != null && changedTransactionDetail != null) {
|
if (command.entityId() != null && changedTransactionDetail != null) {
|
||||||
for (Map.Entry<Long, LoanTransaction> mapEntry : changedTransactionDetail.getNewTransactionMappings().entrySet()) {
|
for (final Map.Entry<Long, LoanTransaction> mapEntry : changedTransactionDetail.getNewTransactionMappings().entrySet()) {
|
||||||
this.loanTransactionRepository.save(mapEntry.getValue());
|
loanAccountDomainService.saveLoanTransactionWithDataIntegrityViolationChecks(mapEntry.getValue());
|
||||||
updateLoanTransaction(mapEntry.getKey(), mapEntry.getValue());
|
accountTransfersWritePlatformService.updateLoanTransaction(mapEntry.getKey(), mapEntry.getValue());
|
||||||
}
|
}
|
||||||
// Trigger transaction replayed event
|
// Trigger transaction replayed event
|
||||||
replayedTransactionBusinessEventService.raiseTransactionReplayedEvents(changedTransactionDetail);
|
replayedTransactionBusinessEventService.raiseTransactionReplayedEvents(changedTransactionDetail);
|
||||||
@@ -2400,12 +2398,13 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
|||||||
|
|
||||||
if (changedTransactionDetail != null) {
|
if (changedTransactionDetail != null) {
|
||||||
for (final Map.Entry<Long, LoanTransaction> mapEntry : changedTransactionDetail.getNewTransactionMappings().entrySet()) {
|
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
|
// Trigger transaction replayed event
|
||||||
replayedTransactionBusinessEventService.raiseTransactionReplayedEvents(changedTransactionDetail);
|
replayedTransactionBusinessEventService.raiseTransactionReplayedEvents(changedTransactionDetail);
|
||||||
}
|
}
|
||||||
saveLoanWithDataIntegrityViolationChecks(loan);
|
loan = saveAndFlushLoanWithDataIntegrityViolationChecks(loan);
|
||||||
postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds);
|
postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds);
|
||||||
loanAccrualTransactionBusinessEventService.raiseBusinessEventForAccrualTransactions(loan, existingTransactionIds);
|
loanAccrualTransactionBusinessEventService.raiseBusinessEventForAccrualTransactions(loan, existingTransactionIds);
|
||||||
loanAccountDomainService.recalculateAccruals(loan);
|
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) {
|
private void createLoanScheduleArchive(final Loan loan, final ScheduleGeneratorDTO scheduleGeneratorDTO) {
|
||||||
createAndSaveLoanScheduleArchive(loan, scheduleGeneratorDTO);
|
createAndSaveLoanScheduleArchive(loan, scheduleGeneratorDTO);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user