diff --git a/src/main/java/org/folio/service/orders/OrderRolloverService.java b/src/main/java/org/folio/service/orders/OrderRolloverService.java index 38d458c18..1321dd69c 100644 --- a/src/main/java/org/folio/service/orders/OrderRolloverService.java +++ b/src/main/java/org/folio/service/orders/OrderRolloverService.java @@ -9,8 +9,6 @@ import static org.folio.rest.RestConstants.MAX_IDS_FOR_GET_RQ_15; import static org.folio.rest.RestVerticle.OKAPI_HEADER_TENANT; import static org.folio.rest.acq.model.finance.LedgerFiscalYearRolloverError.ErrorType.ORDER_ROLLOVER; -import static org.folio.rest.jaxrs.model.PurchaseOrder.WorkflowStatus.CLOSED; -import static org.folio.rest.jaxrs.model.PurchaseOrder.WorkflowStatus.OPEN; import static org.folio.rest.jaxrs.model.RolloverStatus.ERROR; import static org.folio.rest.jaxrs.model.RolloverStatus.SUCCESS; @@ -154,8 +152,9 @@ private Future rolloverOrdersByFundIds(List ledgerFundIds, LedgerF for (var fundIds : fundIdChunks) { semaphore.acquire(() -> { // perform rollover for open orders and then for closed orders - var future = rolloverOrdersByFundIds(fundIds, ledgerFYRollover, systemCurrency, OPEN, requestContext) - .andThen(openOrdersResult -> rolloverOrdersByFundIds(fundIds, ledgerFYRollover, systemCurrency, CLOSED, requestContext) + var future = rolloverOrdersByFundIds(fundIds, ledgerFYRollover, systemCurrency, true, requestContext) + .andThen(openOrdersResult -> rolloverOrdersByFundIds(fundIds, ledgerFYRollover, systemCurrency, + false, requestContext) .andThen(closedOrdersResult -> throwExceptionIfOneOfFailed(openOrdersResult, closedOrdersResult))) .onComplete(asyncResult -> semaphore.release()); @@ -180,8 +179,8 @@ private void throwExceptionIfOneOfFailed(AsyncResult openOrdersResult, Asy } private Future rolloverOrdersByFundIds(List chunkFundIds, LedgerFiscalYearRollover ledgerFYRollover, - String systemCurrency, PurchaseOrder.WorkflowStatus workflowStatus, RequestContext requestContext) { - var query = buildOpenOrClosedOrderQueryByFundIdsAndTypes(chunkFundIds, workflowStatus, ledgerFYRollover); + String systemCurrency, boolean openOrders, RequestContext requestContext) { + var query = buildOpenOrClosedOrderQueryByFundIdsAndTypes(chunkFundIds, openOrders, ledgerFYRollover); var totalRecordsFuture = purchaseOrderLineService.getOrderLineCollection(query, 0, 0, requestContext) .map(PoLineCollection::getTotalRecords); var ctx = requestContext.getContext(); @@ -200,8 +199,8 @@ private Future rolloverOrdersByFundIds(List chunkFundIds, LedgerFi // can produce "thread blocked" warnings because of large number of data semaphore.acquire(() -> { var future = purchaseOrderLineService.getOrderLines(query, atomicChunkCounter.getAndIncrement() * POLINES_CHUNK_SIZE_200, POLINES_CHUNK_SIZE_200, requestContext) - .compose(poLines -> rolloverOrders(systemCurrency, poLines, ledgerFYRollover, workflowStatus, requestContext)) - .compose(modifiedPoLines -> saveOrderLines(modifiedPoLines, ledgerFYRollover, workflowStatus, requestContext)) + .compose(poLines -> rolloverOrders(systemCurrency, poLines, ledgerFYRollover, openOrders, requestContext)) + .compose(modifiedPoLines -> saveOrderLines(modifiedPoLines, ledgerFYRollover, openOrders, requestContext)) .onComplete(asyncResult -> semaphore.release()); futures.add(future); if (futures.size() == numberOfChunks) { @@ -215,25 +214,24 @@ private Future rolloverOrdersByFundIds(List chunkFundIds, LedgerFi } private Future saveOrderLines(List orderLines, LedgerFiscalYearRollover ledgerFYRollover, - PurchaseOrder.WorkflowStatus workflowStatus, RequestContext requestContext) { + boolean openOrders, RequestContext requestContext) { logger.info("saveOrderLines:: Saving POLs after rollover processing, size: {}", orderLines.size()); return purchaseOrderLineService.saveOrderLinesWithoutSearchLocationsUpdate(orderLines, requestContext) - .recover(t -> handlePoLineUpdateFailures(orderLines, ledgerFYRollover, workflowStatus, t, requestContext) + .recover(t -> handlePoLineUpdateFailures(orderLines, ledgerFYRollover, openOrders, t, requestContext) .map(v -> { throw new HttpException(400, ErrorCodes.PO_LINE_ROLLOVER_FAILED.toError()); })); } private Future handlePoLineUpdateFailures(List poLines, LedgerFiscalYearRollover ledgerFYRollover, - PurchaseOrder.WorkflowStatus workflowStatus, - Throwable t, RequestContext requestContext) { + boolean openOrders, Throwable t, RequestContext requestContext) { return GenericCompositeFuture.join(poLines.stream() - .map(poLine -> handlePoLineUpdateFailure(poLine, ledgerFYRollover, workflowStatus, t, requestContext)) + .map(poLine -> handlePoLineUpdateFailure(poLine, ledgerFYRollover, openOrders, t, requestContext)) .toList()) .mapEmpty(); } - private Future handlePoLineUpdateFailure(PoLine poLine, LedgerFiscalYearRollover ledgerFYRollover, PurchaseOrder.WorkflowStatus workflowStatus, + private Future handlePoLineUpdateFailure(PoLine poLine, LedgerFiscalYearRollover ledgerFYRollover, boolean openOrders, Throwable t, RequestContext requestContext) { logger.error("PO line {} update failed while making rollover", poLine.getId(), t); var failureDto = new FailedLedgerRolloverPoLineDto( @@ -244,13 +242,13 @@ private Future handlePoLineUpdateFailure(PoLine poLine, LedgerFiscalYearRo JsonObject.mapFrom(poLine).encode(), // requestBody t.getMessage(), // responseBody ((HttpException) t).getCode(), // statusCode - workflowStatus.value() // purchase order workflow status (Open / Closed) + openOrders ? "Open" : "Closed OR Pending" // purchase order workflow status (saved as text) ); return failedLedgerRolloverPoLineDao.saveFailedRolloverRecord(failureDto, requestContext.getHeaders().get(OKAPI_HEADER_TENANT)); } private Future> rolloverOrders(String systemCurrency, List poLines, LedgerFiscalYearRollover ledgerFYRollover, - PurchaseOrder.WorkflowStatus workflowStatus, RequestContext requestContext) { + boolean openOrders, RequestContext requestContext) { if (poLines.isEmpty()) { return Future.succeededFuture(emptyList()); } @@ -259,17 +257,17 @@ private Future> rolloverOrders(String systemCurrency, List .collect(toList()); return getEncumbrancesForRollover(poLineIds, ledgerFYRollover, requestContext) .compose(encumbrances -> { - if (OPEN.equals(workflowStatus)) { + if (openOrders) { var holders = buildPoLineEncumbrancesHolders(systemCurrency, poLines, encumbrances, requestContext); var modifiedPoLines = applyPoLinesRolloverChanges(holders); return Future.succeededFuture(modifiedPoLines); } else { - return removeEncumbrancesFromClosedPoLines(poLines, encumbrances, requestContext); + return removeEncumbrancesFromPoLines(poLines, encumbrances, requestContext); } }); } - private Future> removeEncumbrancesFromClosedPoLines(List poLines, List transactions, RequestContext requestContext) { + private Future> removeEncumbrancesFromPoLines(List poLines, List transactions, RequestContext requestContext) { return Future.succeededFuture() .compose(v -> { if (transactions.isEmpty()) { @@ -427,13 +425,16 @@ private String buildQuery(List orderIds, String queryTemplate, String de .collect(Collectors.joining(delimiter)); } - protected String buildOpenOrClosedOrderQueryByFundIdsAndTypes(List fundIds, PurchaseOrder.WorkflowStatus workflowStatus, LedgerFiscalYearRollover ledgerFYRollover) { + protected String buildOpenOrClosedOrderQueryByFundIdsAndTypes(List fundIds, boolean openOrders, + LedgerFiscalYearRollover ledgerFYRollover) { StringBuilder resultQuery = new StringBuilder(); if (!ledgerFYRollover.getEncumbrancesRollover().isEmpty()) { resultQuery.append("(").append(buildOrderTypesQuery(ledgerFYRollover)).append(")").append(AND); } - resultQuery.append("(purchaseOrder.workflowStatus==").append(workflowStatus).append(")").append(AND).append("(").append(buildFundIdQuery(fundIds)).append(")"); - if (workflowStatus == CLOSED) { + resultQuery.append("(").append(buildOrderStatusQuery(openOrders)).append(")") + .append(AND) + .append("(").append(buildFundIdQuery(fundIds)).append(")"); + if (!openOrders) { // MODORDERS-904 Avoid rollover re-processing of old already processed closed orders in previous fiscal years resultQuery.append(AND).append("(").append(PO_LINE_NON_EMPTY_ENCUMBRANCE_QUERY).append(")"); } @@ -463,4 +464,15 @@ private PurchaseOrder.OrderType convertToOrderType(EncumbranceRollover.OrderType } return PurchaseOrder.OrderType.ONGOING; } + + private String buildOrderStatusQuery(boolean openOrders) { + String statusQuery = "purchaseOrder.workflowStatus"; + if (openOrders) { + statusQuery += "=="; + } else { + statusQuery += "<>"; + } + statusQuery += "Open"; + return statusQuery; + } } diff --git a/src/test/java/org/folio/service/orders/OrderRolloverServiceTest.java b/src/test/java/org/folio/service/orders/OrderRolloverServiceTest.java index 51f1cc6ef..5b1c1bbec 100644 --- a/src/test/java/org/folio/service/orders/OrderRolloverServiceTest.java +++ b/src/test/java/org/folio/service/orders/OrderRolloverServiceTest.java @@ -45,7 +45,6 @@ import org.folio.rest.jaxrs.model.LedgerFiscalYearRollover; import org.folio.rest.jaxrs.model.PoLine; import org.folio.rest.jaxrs.model.PoLineCollection; -import org.folio.rest.jaxrs.model.PurchaseOrder; import org.folio.rest.jaxrs.model.RolloverStatus; import org.folio.service.caches.ConfigurationEntriesCache; import org.folio.service.exchange.ExchangeRateProviderResolver; @@ -54,6 +53,7 @@ import org.folio.service.finance.rollover.LedgerRolloverProgressService; import org.folio.service.finance.transaction.TransactionService; import org.folio.utils.CurrencyConversionMockHelper; +import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; @@ -99,12 +99,12 @@ public class OrderRolloverServiceTest { private ArgumentCaptor> argumentCaptor; private RequestContext requestContext; - private final String systemCurrency = "USD"; + private AutoCloseable mockitoMocks; @BeforeEach public void initMocks() { - MockitoAnnotations.openMocks(this); + mockitoMocks = MockitoAnnotations.openMocks(this); Map okapiHeadersMock = new HashMap<>(); okapiHeadersMock.put(OKAPI_URL, "http://localhost:" + mockPort); okapiHeadersMock.put(X_OKAPI_TOKEN.getName(), X_OKAPI_TOKEN.getValue()); @@ -113,6 +113,11 @@ public void initMocks() { requestContext = new RequestContext(Vertx.vertx().getOrCreateContext(), okapiHeadersMock); } + @AfterEach + void afterEach() throws Exception { + mockitoMocks.close(); + } + @Test @DisplayName("Should start preview rollover and check that start rollover was invoked") void shouldStartPreviewRolloverAndCheckThatStartRolloverWasInvoked(VertxTestContext vertxTestContext) { @@ -729,40 +734,40 @@ void shouldConvertTotalAmountUsingCorrectExchangeRateProviderMode(String fromCur private static Stream testBuildOpenOrClosedOrderQueryByFundIdsAndTypesArgs() { return Stream.of( Arguments.of("(purchaseOrder.orderType == One-Time) and (purchaseOrder.workflowStatus==Open) and (fundDistribution =/@fundId \"%s\") sortBy metadata.createdDate", - PurchaseOrder.WorkflowStatus.OPEN, + true, List.of(new EncumbranceRollover().withOrderType(EncumbranceRollover.OrderType.ONE_TIME).withBasedOn(EncumbranceRollover.BasedOn.INITIAL_AMOUNT).withIncreaseBy(0d))), - Arguments.of("(purchaseOrder.orderType == One-Time) and (purchaseOrder.workflowStatus==Closed) and (fundDistribution =/@fundId \"%s\") and (fundDistribution == \"*\\\"encumbrance\\\": \\\"*\") sortBy metadata.createdDate", - PurchaseOrder.WorkflowStatus.CLOSED, + Arguments.of("(purchaseOrder.orderType == One-Time) and (purchaseOrder.workflowStatus<>Open) and (fundDistribution =/@fundId \"%s\") and (fundDistribution == \"*\\\"encumbrance\\\": \\\"*\") sortBy metadata.createdDate", + false, List.of(new EncumbranceRollover().withOrderType(EncumbranceRollover.OrderType.ONE_TIME).withBasedOn(EncumbranceRollover.BasedOn.INITIAL_AMOUNT).withIncreaseBy(0d))) ); } @ParameterizedTest @MethodSource("testBuildOpenOrClosedOrderQueryByFundIdsAndTypesArgs") - void testBuildOpenOrClosedOrderQueryByFundIdsAndTypes(String expectedQueryTemplate, PurchaseOrder.WorkflowStatus workflowStatus, List encumbranceRollovers) { + void testBuildOpenOrClosedOrderQueryByFundIdsAndTypes(String expectedQueryTemplate, boolean openOrders, List encumbranceRollovers) { var fundId = UUID.randomUUID().toString(); var ledgerFiscalYearRollover = new LedgerFiscalYearRollover().withId(UUID.randomUUID().toString()).withEncumbrancesRollover(encumbranceRollovers); var expectedQuery = String.format(expectedQueryTemplate, fundId); - var actualQuery = orderRolloverService.buildOpenOrClosedOrderQueryByFundIdsAndTypes(List.of(fundId), workflowStatus, ledgerFiscalYearRollover); + var actualQuery = orderRolloverService.buildOpenOrClosedOrderQueryByFundIdsAndTypes(List.of(fundId), openOrders, ledgerFiscalYearRollover); Assertions.assertEquals(expectedQuery, actualQuery); } private static Stream testBuildOpenOrClosedOrderQueryByFundIdsAndTypesWithoutSettingsArgs() { return Stream.of( Arguments.of("(purchaseOrder.workflowStatus==Open) and (fundDistribution =/@fundId \"%s\") sortBy metadata.createdDate", - PurchaseOrder.WorkflowStatus.OPEN), - Arguments.of("(purchaseOrder.workflowStatus==Closed) and (fundDistribution =/@fundId \"%s\") and (fundDistribution == \"*\\\"encumbrance\\\": \\\"*\") sortBy metadata.createdDate", - PurchaseOrder.WorkflowStatus.CLOSED) + true), + Arguments.of("(purchaseOrder.workflowStatus<>Open) and (fundDistribution =/@fundId \"%s\") and (fundDistribution == \"*\\\"encumbrance\\\": \\\"*\") sortBy metadata.createdDate", + false) ); } @ParameterizedTest @MethodSource("testBuildOpenOrClosedOrderQueryByFundIdsAndTypesWithoutSettingsArgs") - void testBuildOpenOrClosedOrderQueryByFundIdsAndTypesWithoutSettings(String expectedQueryTemplate, PurchaseOrder.WorkflowStatus workflowStatus) { + void testBuildOpenOrClosedOrderQueryByFundIdsAndTypesWithoutSettings(String expectedQueryTemplate, boolean openOrders) { var fundId = UUID.randomUUID().toString(); var ledgerFiscalYearRollover = new LedgerFiscalYearRollover().withId(UUID.randomUUID().toString()).withEncumbrancesRollover(List.of()); var expectedQuery = String.format(expectedQueryTemplate, fundId); - var actualQuery = orderRolloverService.buildOpenOrClosedOrderQueryByFundIdsAndTypes(List.of(fundId), workflowStatus, ledgerFiscalYearRollover); + var actualQuery = orderRolloverService.buildOpenOrClosedOrderQueryByFundIdsAndTypes(List.of(fundId), openOrders, ledgerFiscalYearRollover); Assertions.assertEquals(expectedQuery, actualQuery); } }