From 21c0f684737c322af31629528544b877f837a1df Mon Sep 17 00:00:00 2001 From: Serhii Nosko Date: Mon, 25 Mar 2024 20:08:42 +0200 Subject: [PATCH] MODORDERS-1066. Item records are NOT deleted from Instance when Order is unopened and "Delete items" option is picked (#870) * MODORDERS-1066. Fix issue when items are not deleted during order reopen --- .../unopen/UnOpenCompositeOrderManager.java | 57 ++- .../UnOpenCompositeOrderManagerTest.java | 473 +++++++++++++----- 2 files changed, 379 insertions(+), 151 deletions(-) diff --git a/src/main/java/org/folio/service/orders/flows/update/unopen/UnOpenCompositeOrderManager.java b/src/main/java/org/folio/service/orders/flows/update/unopen/UnOpenCompositeOrderManager.java index 7a024fae8..127a35c86 100644 --- a/src/main/java/org/folio/service/orders/flows/update/unopen/UnOpenCompositeOrderManager.java +++ b/src/main/java/org/folio/service/orders/flows/update/unopen/UnOpenCompositeOrderManager.java @@ -78,12 +78,12 @@ public Future process(CompositePurchaseOrder compPO, CompositePurchaseOrde return updateAndGetOrderWithLines(compPO, requestContext) .map(aVoid -> encumbranceWorkflowStrategyFactory.getStrategy(OrderWorkflowType.OPEN_TO_PENDING)) .compose(strategy -> strategy.processEncumbrances(compPO, poFromStorage, requestContext)) + .compose(ok -> processInventory(compPO.getCompositePoLines(), deleteHoldings, requestContext)) .map(ok -> { PoLineCommonUtil.makePoLinesPending(compPO.getCompositePoLines()); return null; }) - .compose(ok -> updatePoLinesSummary(compPO.getCompositePoLines(), requestContext)) - .compose(ok -> processInventory(compPO.getCompositePoLines(), deleteHoldings, requestContext)); + .compose(ok -> updatePoLinesSummary(compPO.getCompositePoLines(), requestContext)); } @@ -169,10 +169,6 @@ private Future processInventoryOnlyWithHolding(CompositePoLine compPOL, Re } private Future processInventoryOnlyWithItems(CompositePoLine compPOL, RequestContext requestContext) { - if (!PoLineCommonUtil.isReceiptNotRequired(compPOL.getReceiptStatus())) { - logger.info("Skip deleting items and pieces for Un-Open order with id: {} because receipt can be required", compPOL.getId()); - return Future.succeededFuture(); - } return deleteExpectedPieces(compPOL, requestContext) .compose(deletedPieces -> inventoryManager.getItemsByPoLineIdsAndStatus(List.of(compPOL.getId()), ItemStatus.ON_ORDER.value(), requestContext) @@ -189,17 +185,18 @@ private Future processInventoryOnlyWithItems(CompositePoLine compPOL, Requ } private Future> deleteExpectedPieces(CompositePoLine compPOL, RequestContext requestContext) { - if (!PoLineCommonUtil.isReceiptNotRequired(compPOL.getReceiptStatus()) && Boolean.FALSE.equals(compPOL.getCheckinItems())) { - return pieceStorageService.getExpectedPiecesByLineId(compPOL.getId(), requestContext) - .compose(pieceCollection -> { - if (isNotEmpty(pieceCollection.getPieces())) { - return pieceStorageService.deletePiecesByIds(pieceCollection.getPieces().stream().map(Piece::getId).collect(toList()), requestContext) - .map(v -> pieceCollection.getPieces()); - } - return Future.succeededFuture(Collections.emptyList()); - }); + if (PoLineCommonUtil.isReceiptNotRequired(compPOL.getReceiptStatus()) || Boolean.TRUE.equals(compPOL.getCheckinItems())) { + logger.info("Receipt is not required or independent receiving flow is used, skipping deleting pieces, poLineId: {}", compPOL.getId()); + return Future.succeededFuture(Collections.emptyList()); } - return Future.succeededFuture(Collections.emptyList()); + return pieceStorageService.getExpectedPiecesByLineId(compPOL.getId(), requestContext) + .compose(pieceCollection -> { + if (isNotEmpty(pieceCollection.getPieces())) { + return pieceStorageService.deletePiecesByIds(pieceCollection.getPieces().stream().map(Piece::getId).collect(toList()), requestContext) + .map(v -> pieceCollection.getPieces()); + } + return Future.succeededFuture(Collections.emptyList()); + }); } private Future processInventoryHoldingWithItems(CompositePoLine compPOL, RequestContext requestContext) { @@ -221,7 +218,7 @@ private Future processInventoryHoldingWithItems(CompositePoLine compPOL, R .compose(pieceCollection -> { if (isNotEmpty(pieceCollection.getPieces())) { return inventoryManager.getItemRecordsByIds(itemIds, requestContext) - .map(items -> getItemsByStatus(items, ItemStatus.ON_ORDER.value())) + .map(items -> filterItemsByStatus(items, ItemStatus.ON_ORDER.value())) .compose(onOrderItemsP -> deletePiecesAndItems(onOrderItemsP, pieceCollection.getPieces(), requestContext)) .compose(deletedItems -> deleteHoldingsByItems(deletedItems, requestContext)) .map(deletedHoldingVsLocationIds -> { @@ -229,6 +226,8 @@ private Future processInventoryHoldingWithItems(CompositePoLine compPOL, R return null; }) .onSuccess(v -> logger.debug("Pieces, Items, Holdings deleted after UnOpen order")) + .onFailure(e -> logger.error("Pieces, Items, Holdings failed to be deleted after UnOpen order for order id: {}", + compPOL.getId(), e)) .mapEmpty(); } return Future.succeededFuture(); @@ -276,7 +275,7 @@ private Future>> deleteHoldingsByItems(List resultDeletedHoldingVsLocationIds.stream() - .filter(pair -> Objects.nonNull(pair.getKey())) + .filter(pair -> Objects.nonNull(pair) && Objects.nonNull(pair.getKey())) .collect(toList())) .map(resultDeletedHoldingVsLocationIds -> { if (logger.isDebugEnabled()) { @@ -345,7 +344,7 @@ private Future> deletePiecesAndItems(List onOrderIt }); } - private List getItemsByStatus(List items, String status) { + private List filterItemsByStatus(List items, String status) { return items.stream() .filter(item -> status.equalsIgnoreCase(item.getJsonObject(ITEM_STATUS).getString(ITEM_STATUS_NAME))) .collect(toList()); @@ -367,14 +366,18 @@ private Future updateAndGetOrderWithLines(CompositePurch public Future deletePieceWithItem(String pieceId, RequestContext requestContext) { PieceDeletionHolder holder = new PieceDeletionHolder().withDeleteHolding(true); return pieceStorageService.getPieceById(pieceId, requestContext) - .onSuccess(holder::setPieceToDelete) - .compose(aVoid -> purchaseOrderLineService.getOrderLineById(holder.getPieceToDelete().getPoLineId(), requestContext) - .compose(poLine -> purchaseOrderStorageService.getPurchaseOrderById(poLine.getPurchaseOrderId(), requestContext) - .map(purchaseOrder -> { - holder.withOrderInformation(purchaseOrder, poLine); - return null; - }))) - .compose(purchaseOrder -> protectionService.isOperationRestricted(holder.getOriginPurchaseOrder().getAcqUnitIds(), DELETE, requestContext)) + .map(piece-> { + holder.setPieceToDelete(piece); + return null; + }) + .compose(aVoid -> purchaseOrderLineService.getOrderLineById(holder.getPieceToDelete().getPoLineId(), requestContext)) + .compose(poLine -> purchaseOrderStorageService.getPurchaseOrderById(poLine.getPurchaseOrderId(), requestContext) + .map(purchaseOrder -> { + holder.withOrderInformation(purchaseOrder, poLine); + return null; + }) + ) + .compose(aVoid -> protectionService.isOperationRestricted(holder.getOriginPurchaseOrder().getAcqUnitIds(), DELETE, requestContext)) .compose(vVoid -> canDeletePieceWithItem(holder.getPieceToDelete(), requestContext)) .compose(aVoid -> pieceStorageService.deletePiece(pieceId, requestContext)) .compose(aVoid -> deletePieceConnectedItem(holder.getPieceToDelete(), requestContext)); diff --git a/src/test/java/org/folio/service/orders/flows/update/unopen/UnOpenCompositeOrderManagerTest.java b/src/test/java/org/folio/service/orders/flows/update/unopen/UnOpenCompositeOrderManagerTest.java index 64bc29231..063ec1c33 100644 --- a/src/test/java/org/folio/service/orders/flows/update/unopen/UnOpenCompositeOrderManagerTest.java +++ b/src/test/java/org/folio/service/orders/flows/update/unopen/UnOpenCompositeOrderManagerTest.java @@ -1,50 +1,60 @@ package org.folio.service.orders.flows.update.unopen; +import static io.vertx.core.Future.succeededFuture; import static org.folio.TestConfig.autowireDependencies; import static org.folio.TestConfig.clearServiceInteractions; import static org.folio.TestConfig.clearVertxContext; -import static org.folio.TestConfig.getFirstContextFromVertx; -import static org.folio.TestConfig.getVertx; import static org.folio.TestConfig.initSpringContext; import static org.folio.TestConfig.isVerticleNotDeployed; -import static org.folio.TestConfig.mockPort; -import static org.folio.TestConstants.X_OKAPI_TOKEN; -import static org.folio.TestConstants.X_OKAPI_USER_ID; -import static org.folio.rest.RestConstants.OKAPI_URL; +import static org.folio.TestUtils.getMockAsJson; import static org.folio.rest.impl.MockServer.BASE_MOCK_DATA_PATH; -import static org.folio.rest.impl.PurchaseOrdersApiTest.X_OKAPI_TENANT; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.Mockito.any; +import static org.mockito.Mockito.anyBoolean; +import static org.mockito.Mockito.anyList; +import static org.mockito.Mockito.anyString; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.eq; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; import static org.mockito.Mockito.reset; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoInteractions; -import java.util.HashMap; +import java.util.Collections; +import java.util.List; import java.util.Map; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeoutException; +import io.vertx.core.json.JsonObject; +import io.vertx.junit5.VertxExtension; import org.folio.ApiTestSuite; -import org.folio.rest.core.RestClient; +import org.folio.models.ItemStatus; +import org.folio.orders.utils.ProtectedOperationType; import org.folio.rest.core.models.RequestContext; -import org.folio.service.AcquisitionsUnitsService; +import org.folio.rest.jaxrs.model.CompositePoLine; +import org.folio.rest.jaxrs.model.CompositePurchaseOrder; +import org.folio.rest.jaxrs.model.Physical; +import org.folio.rest.jaxrs.model.Piece; +import org.folio.rest.jaxrs.model.PieceCollection; +import org.folio.rest.jaxrs.model.PoLine; +import org.folio.rest.jaxrs.model.PurchaseOrder; import org.folio.service.ProtectionService; -import org.folio.service.TagService; -import org.folio.service.configuration.ConfigurationEntriesService; -import org.folio.service.finance.expenceclass.ExpenseClassValidationService; -import org.folio.service.finance.transaction.EncumbranceService; import org.folio.service.finance.transaction.EncumbranceWorkflowStrategyFactory; import org.folio.service.finance.transaction.OpenToPendingEncumbranceStrategy; import org.folio.service.inventory.InventoryManager; -import org.folio.service.orders.CombinedOrderDataPopulateService; -import org.folio.service.orders.CompositeOrderDynamicDataPopulateService; -import org.folio.service.orders.OrderInvoiceRelationService; -import org.folio.service.orders.OrderLinesSummaryPopulateService; -import org.folio.service.orders.OrderReEncumberService; +import org.folio.service.orders.OrderWorkflowType; import org.folio.service.orders.PurchaseOrderLineService; -import org.folio.service.pieces.PieceService; -import org.folio.service.titles.TitlesService; +import org.folio.service.orders.PurchaseOrderStorageService; +import org.folio.service.pieces.PieceStorageService; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mock; import org.mockito.MockitoAnnotations; import org.springframework.beans.factory.annotation.Autowired; @@ -52,19 +62,32 @@ import io.vertx.core.Context; +@ExtendWith(VertxExtension.class) public class UnOpenCompositeOrderManagerTest { private static final String ORDER_ID = "1ab7ef6a-d1d4-4a4f-90a2-882aed18af20"; + private static final String PIECE_ID = "57cbee32-c6f8-4467-a4c7-97366792b0ce"; + private static final String ITEM_ID = "50bd38d8-2560-4084-b987-a939bb198865"; + private static final String HOLDING_ID = "41620fe3-9ab5-4a35-a8cf-6f3611ec50c8"; + private static final String EFFECTIVE_LOCATION_ID = "41620fe3-9ab5-4a35-a8cf-6f3611ec50c8"; public static final String ORDER_PATH = BASE_MOCK_DATA_PATH + "compositeOrders/" + ORDER_ID + ".json"; + @Autowired + private UnOpenCompositeOrderManager unOpenCompositeOrderManager; @Autowired private EncumbranceWorkflowStrategyFactory encumbranceWorkflowStrategyFactory; @Autowired private PurchaseOrderLineService purchaseOrderLineService; + @Autowired + private InventoryManager inventoryManager; + @Autowired + private PieceStorageService pieceStorageService; + @Autowired + private PurchaseOrderStorageService purchaseOrderStorageService; + @Autowired + private ProtectionService protectionService; @Mock private OpenToPendingEncumbranceStrategy openToPendingEncumbranceStrategy; @Mock - private RestClient restClient; - private Map okapiHeadersMock; private Context ctxMock; private RequestContext requestContext; @@ -92,129 +115,330 @@ public static void after() { void beforeEach() { MockitoAnnotations.openMocks(this); autowireDependencies(this); - ctxMock = getFirstContextFromVertx(getVertx()); - okapiHeadersMock = new HashMap<>(); - okapiHeadersMock.put(OKAPI_URL, "http://localhost:" + mockPort); - okapiHeadersMock.put(X_OKAPI_TOKEN.getName(), X_OKAPI_TOKEN.getValue()); - okapiHeadersMock.put(X_OKAPI_TENANT.getName(), X_OKAPI_TENANT.getValue()); - okapiHeadersMock.put(X_OKAPI_USER_ID.getName(), X_OKAPI_USER_ID.getValue()); - String okapiURL = okapiHeadersMock.getOrDefault(OKAPI_URL, ""); requestContext = new RequestContext(ctxMock, okapiHeadersMock); } @AfterEach void resetMocks() { clearServiceInteractions(); - reset(encumbranceWorkflowStrategyFactory); - } - -// @Test -// void testShouldSetEncumbrancesToPending() { -// //given -// PurchaseOrderLineHelper orderLineHelper = mock(PurchaseOrderLineHelper.class, CALLS_REAL_METHODS); -// UnOpenCompositeOrderManager serviceSpy = spy( -// new UnOpenCompositeOrderManager(piece, "en", orderLineHelper)); -// CompositePurchaseOrder order = getMockAsJson(ORDER_PATH).mapTo(CompositePurchaseOrder.class); -// -// doReturn(succeededFuture(order)).when(serviceSpy).updateAndGetOrderWithLines(any(CompositePurchaseOrder.class)); -// doReturn(openToPendingEncumbranceStrategy).when(encumbranceWorkflowStrategyFactory).getStrategy(eq(OPEN_TO_PENDING)); -// doReturn(succeededFuture(null)).when(openToPendingEncumbranceStrategy).processEncumbrances(eq(order), any()); -// doNothing().when(orderLineHelper).closeHttpClient(); -// doReturn(succeededFuture(null)).when(purchaseOrderLineService).updateOrderLine(any(), eq(requestContext)); -// //When -// serviceSpy.unOpenOrder(order, requestContext).result(); -// //Then -// -// verify(serviceSpy).unOpenOrderUpdatePoLinesSummary(any(), eq(requestContext)); -// } -// -// @Test -// void testShouldEndExceptionallyAndCloseConnection() { -// //given -// PurchaseOrderLineHelper orderLineHelper = mock(PurchaseOrderLineHelper.class, CALLS_REAL_METHODS); -// -// UnOpenCompositeOrderManager serviceSpy = spy(new PurchaseOrderHelper(httpClient, okapiHeadersMock, ctxMock, "en", orderLineHelper)); -// CompositePurchaseOrder order = getMockAsJson(ORDER_PATH).mapTo(CompositePurchaseOrder.class); -// -// doReturn(succeededFuture(order)).when(serviceSpy).updateAndGetOrderWithLines(any(CompositePurchaseOrder.class)); -// doReturn(openToPendingEncumbranceStrategy).when(encumbranceWorkflowStrategyFactory).getStrategy(any()); -// doReturn(succeededFuture(null)).when(openToPendingEncumbranceStrategy).processEncumbrances(any(), any()); -// -// doNothing().when(orderLineHelper).closeHttpClient(); -// //When -// Future act = serviceSpy.unOpenOrder(order, requestContext); -// //Then -// assertTrue(act.failed()); -// } + reset(encumbranceWorkflowStrategyFactory, pieceStorageService, inventoryManager); + } - /** - * Define unit test specific beans to override actual ones - */ - static class ContextConfiguration { + @Test + void testDeleteItemsAndPiecesForSynchronizedWorkflowWhenDeleteHoldingsFalse() { + //given + CompositePurchaseOrder order = getMockAsJson(ORDER_PATH).mapTo(CompositePurchaseOrder.class); + CompositePurchaseOrder orderFromStorage = getMockAsJson(ORDER_PATH).mapTo(CompositePurchaseOrder.class); + CompositePoLine poLine = getPoLine(order); + prepareInitialSetup(order, orderFromStorage, poLine); + //When + unOpenCompositeOrderManager.process(order, orderFromStorage, false, requestContext).result(); + //Then + makeBasicUnOpenWorkflowChecks(order, orderFromStorage); + // pieces should be deleted + verify(pieceStorageService).deletePiecesByIds(List.of(PIECE_ID), requestContext); + // items should be deleted + verify(inventoryManager).deleteItems(List.of(ITEM_ID), false, requestContext); + // holdings should not be deleted because deleteHodlings = false + verify(inventoryManager, never()).deleteHoldingById(anyString(), anyBoolean(), any()); + } - @Bean - public EncumbranceWorkflowStrategyFactory encumbranceWorkflowStrategyFactory() { - return mock(EncumbranceWorkflowStrategyFactory.class); - } + @Test + void testDeleteItemsAndPiecesAndHoldingsForSynchronizedWorkflowWhenDeleteHoldingsTrue() { + //given + CompositePurchaseOrder order = getMockAsJson(ORDER_PATH).mapTo(CompositePurchaseOrder.class); + CompositePurchaseOrder orderFromStorage = getMockAsJson(ORDER_PATH).mapTo(CompositePurchaseOrder.class); + CompositePoLine poLine = getPoLine(order); + prepareInitialSetup(order, orderFromStorage, poLine); + //When + unOpenCompositeOrderManager.process(order, orderFromStorage, true, requestContext).result(); + //Then + makeBasicUnOpenWorkflowChecks(order, orderFromStorage); + // piece should be deleted + verify(pieceStorageService).deletePiece(PIECE_ID, requestContext); + // item should be deleted + verify(inventoryManager).deleteItem(ITEM_ID, true, requestContext); + // holdings should be deleted because deleteHodlings = true + verify(inventoryManager).deleteHoldingById(HOLDING_ID, true, requestContext); + } - @Bean - public ConfigurationEntriesService configurationEntriesService() { - return mock(ConfigurationEntriesService.class); - } + @Test + void testDeleteItemsAndPiecesNotHoldingsThatHaveOnorderItemsForSynchronizedWorkflowWhenDeleteHoldingsTrue() { + //given + CompositePurchaseOrder order = getMockAsJson(ORDER_PATH).mapTo(CompositePurchaseOrder.class); + CompositePurchaseOrder orderFromStorage = getMockAsJson(ORDER_PATH).mapTo(CompositePurchaseOrder.class); + CompositePoLine poLine = getPoLine(order); + prepareInitialSetup(order, orderFromStorage, poLine); + doReturn(succeededFuture(List.of(getItem()))).when(inventoryManager).getItemsByHoldingId(HOLDING_ID, requestContext); + //When + unOpenCompositeOrderManager.process(order, orderFromStorage, true, requestContext).result(); + //Then + makeBasicUnOpenWorkflowChecks(order, orderFromStorage); + // piece should be deleted + verify(pieceStorageService).deletePiece(PIECE_ID, requestContext); + // item should be deleted + verify(inventoryManager).deleteItem(ITEM_ID, true, requestContext); + // holding should not be deleted, because have links to on-order items + verify(inventoryManager, never()).deleteHoldingById(anyString(), anyBoolean(), any()); + } - @Bean - public EncumbranceService encumbranceService() { - return mock(EncumbranceService.class); - } + @Test + void testDeletePiecesItemsAndHoldingsForSynchronizedWorkflowWhenDeleteHoldingsFalse() { + //given + CompositePurchaseOrder order = getMockAsJson(ORDER_PATH).mapTo(CompositePurchaseOrder.class); + CompositePurchaseOrder orderFromStorage = getMockAsJson(ORDER_PATH).mapTo(CompositePurchaseOrder.class); + CompositePoLine poLine = getPoLine(order); + prepareInitialSetup(order, orderFromStorage, poLine); + //When + unOpenCompositeOrderManager.process(order, orderFromStorage, false, requestContext).result(); + //Then + makeBasicUnOpenWorkflowChecks(order, orderFromStorage); + // piece should be deleted + verify(pieceStorageService).deletePiecesByIds(List.of(PIECE_ID), requestContext); + // item should be deleted + verify(inventoryManager).deleteItems(List.of(ITEM_ID), false, requestContext); + // holding should not be deleted when delete holdings = false + verify(inventoryManager, never()).deleteHoldingById(anyString(), anyBoolean(), any(RequestContext.class)); + } - @Bean - public OrderReEncumberService orderReEncumberService() { - return mock(OrderReEncumberService.class); - } + @Test + void testDoNotDoAnyActionsForPackageOrderLine() { + //given + CompositePurchaseOrder order = getMockAsJson(ORDER_PATH).mapTo(CompositePurchaseOrder.class); + CompositePurchaseOrder orderFromStorage = getMockAsJson(ORDER_PATH).mapTo(CompositePurchaseOrder.class); + CompositePoLine packagePoLine = getPoLine(order); + // make package order line + packagePoLine.setIsPackage(true); + prepareInitialSetup(order, orderFromStorage, packagePoLine); + //When + unOpenCompositeOrderManager.process(order, orderFromStorage, false, requestContext).result(); + //Then + makeBasicUnOpenWorkflowChecks(order, orderFromStorage); + verifyNoInteractions(inventoryManager); + } - @Bean - public ExpenseClassValidationService expenseClassValidationService() { - return mock(ExpenseClassValidationService.class); - } + @Test + void testDeletePiecesWhenCreateItemWasNoneAndSynchronizedFlow() { + //given + CompositePurchaseOrder order = getMockAsJson(ORDER_PATH).mapTo(CompositePurchaseOrder.class); + CompositePurchaseOrder orderFromStorage = getMockAsJson(ORDER_PATH).mapTo(CompositePurchaseOrder.class); + CompositePoLine poLine = getPoLine(order); + poLine.setReceiptStatus(CompositePoLine.ReceiptStatus.AWAITING_RECEIPT); + poLine.getPhysical().setCreateInventory(Physical.CreateInventory.NONE); + prepareInitialSetup(order, orderFromStorage, poLine); + //When + unOpenCompositeOrderManager.process(order, orderFromStorage, false, requestContext).result(); + //Then + makeBasicUnOpenWorkflowChecks(order, orderFromStorage); + // only pieces should be deleted because of Synchronized flow, items not because CreateInventory is None + verify(pieceStorageService).deletePiecesByIds(List.of(PIECE_ID), requestContext); + verifyNoInteractions(inventoryManager); + } - @Bean - public OrderInvoiceRelationService orderInvoiceRelationService() { - return mock(OrderInvoiceRelationService.class); - } + @Test + void testRemainPiecesWhenCreateItemWasNoneAndSynchronizedFlow() { + //given + CompositePurchaseOrder order = getMockAsJson(ORDER_PATH).mapTo(CompositePurchaseOrder.class); + CompositePurchaseOrder orderFromStorage = getMockAsJson(ORDER_PATH).mapTo(CompositePurchaseOrder.class); + CompositePoLine poLine = getPoLine(order); + // make Independent flow with Create Inventory is None and receipt not required status + poLine.setCheckinItems(true); + poLine.setReceiptStatus(CompositePoLine.ReceiptStatus.RECEIPT_NOT_REQUIRED); + poLine.setPaymentStatus(CompositePoLine.PaymentStatus.PAYMENT_NOT_REQUIRED); + poLine.getPhysical().setCreateInventory(Physical.CreateInventory.NONE); + prepareInitialSetup(order, orderFromStorage, poLine); + //When + unOpenCompositeOrderManager.process(order, orderFromStorage, false, requestContext).result(); + //Then + makeBasicUnOpenWorkflowChecksForReceiptNotRequired(order, orderFromStorage); + // pieces and inventory should not be deleted + verifyNoInteractions(pieceStorageService); + verifyNoInteractions(inventoryManager); + } - @Bean - TagService tagService() { - return mock(TagService.class); - } + @Test + void testDeleteHoldingsWhenOnlyHoldingWasCreatedAndDeleteHoldingsIsTrue() { + //given + CompositePurchaseOrder order = getMockAsJson(ORDER_PATH).mapTo(CompositePurchaseOrder.class); + CompositePurchaseOrder orderFromStorage = getMockAsJson(ORDER_PATH).mapTo(CompositePurchaseOrder.class); + CompositePoLine poLine = getPoLine(order); + poLine.getPhysical().setCreateInventory(Physical.CreateInventory.INSTANCE_HOLDING); + poLine.getLocations().forEach(location -> location.setHoldingId(HOLDING_ID)); + prepareInitialSetup(order, orderFromStorage, poLine); + //When + unOpenCompositeOrderManager.process(order, orderFromStorage, true, requestContext).result(); + //Then + makeBasicUnOpenWorkflowChecks(order, orderFromStorage); + verify(pieceStorageService).deletePiecesByIds(List.of(PIECE_ID), requestContext); + verify(inventoryManager).deleteHoldingById(HOLDING_ID, true, requestContext); + verify(inventoryManager, never()).deleteItem(anyString(), anyBoolean(), any(RequestContext.class)); + verify(inventoryManager, never()).deleteItems(anyList(), anyBoolean(), any(RequestContext.class)); + } - @Bean - public RestClient restClient() { - return mock(RestClient.class); - } + @Test + void testDeleteHoldingsForSynchronizedWorkflowWhenHoldingHasRelatedItems() { + //given + CompositePurchaseOrder order = getMockAsJson(ORDER_PATH).mapTo(CompositePurchaseOrder.class); + CompositePurchaseOrder orderFromStorage = getMockAsJson(ORDER_PATH).mapTo(CompositePurchaseOrder.class); + CompositePoLine poLine = getPoLine(order); + poLine.getPhysical().setCreateInventory(Physical.CreateInventory.INSTANCE_HOLDING); + poLine.getLocations().forEach(location -> location.setHoldingId(HOLDING_ID)); + prepareInitialSetup(order, orderFromStorage, poLine); + doReturn(succeededFuture(List.of(getItem()))).when(inventoryManager).getItemsByHoldingId(HOLDING_ID, requestContext); + //When + unOpenCompositeOrderManager.process(order, orderFromStorage, true, requestContext).result(); + //Then + makeBasicUnOpenWorkflowChecks(order, orderFromStorage); + verify(pieceStorageService).deletePiecesByIds(List.of(PIECE_ID), requestContext); + // holding is not deleted because it has associated items + verify(inventoryManager, never()).deleteHoldingById(HOLDING_ID, true, requestContext); + verify(inventoryManager, never()).deleteItem(anyString(), anyBoolean(), any(RequestContext.class)); + verify(inventoryManager, never()).deleteItems(anyList(), anyBoolean(), any(RequestContext.class)); + } + + @Test + void testDeleteHoldingForSynchronizedWorkflowWhenDeleteHoldingIsFalse() { + //given + CompositePurchaseOrder order = getMockAsJson(ORDER_PATH).mapTo(CompositePurchaseOrder.class); + CompositePurchaseOrder orderFromStorage = getMockAsJson(ORDER_PATH).mapTo(CompositePurchaseOrder.class); + CompositePoLine poLine = getPoLine(order); + poLine.getPhysical().setCreateInventory(Physical.CreateInventory.INSTANCE_HOLDING); + poLine.getLocations().forEach(location -> location.setHoldingId(HOLDING_ID)); + prepareInitialSetup(order, orderFromStorage, poLine); + //When + unOpenCompositeOrderManager.process(order, orderFromStorage, false, requestContext).result(); + //Then + makeBasicUnOpenWorkflowChecks(order, orderFromStorage); + verify(pieceStorageService).deletePiecesByIds(List.of(PIECE_ID), requestContext); + verify(inventoryManager, never()).deleteHoldingById(HOLDING_ID, true, requestContext); + verify(inventoryManager, never()).deleteItem(anyString(), anyBoolean(), any(RequestContext.class)); + verify(inventoryManager, never()).deleteItems(anyList(), anyBoolean(), any(RequestContext.class)); + } + + @Test + void testDeleteHoldingForIndependentWorkflowWhenDeleteHoldingIsTrue() { + //given + CompositePurchaseOrder order = getMockAsJson(ORDER_PATH).mapTo(CompositePurchaseOrder.class); + CompositePurchaseOrder orderFromStorage = getMockAsJson(ORDER_PATH).mapTo(CompositePurchaseOrder.class); + CompositePoLine poLine = getPoLine(order); + poLine.setCheckinItems(true); + poLine.getPhysical().setCreateInventory(Physical.CreateInventory.INSTANCE_HOLDING); + poLine.getLocations().forEach(location -> location.setHoldingId(HOLDING_ID)); + prepareInitialSetup(order, orderFromStorage, poLine); + //When + unOpenCompositeOrderManager.process(order, orderFromStorage, true, requestContext).result(); + //Then + makeBasicUnOpenWorkflowChecks(order, orderFromStorage); + verify(pieceStorageService, never()).deletePiecesByIds(List.of(PIECE_ID), requestContext); + verify(inventoryManager).deleteHoldingById(HOLDING_ID, true, requestContext); + verify(inventoryManager, never()).deleteItem(anyString(), anyBoolean(), any(RequestContext.class)); + verify(inventoryManager, never()).deleteItems(anyList(), anyBoolean(), any(RequestContext.class)); + } + + @Test + void testDeleteHoldingForIndependentWorkflowWhenDeleteHoldingIsFalse() { + //given + CompositePurchaseOrder order = getMockAsJson(ORDER_PATH).mapTo(CompositePurchaseOrder.class); + CompositePurchaseOrder orderFromStorage = getMockAsJson(ORDER_PATH).mapTo(CompositePurchaseOrder.class); + CompositePoLine poLine = getPoLine(order); + poLine.setCheckinItems(true); + poLine.getPhysical().setCreateInventory(Physical.CreateInventory.INSTANCE_HOLDING); + poLine.getLocations().forEach(location -> location.setHoldingId(HOLDING_ID)); + prepareInitialSetup(order, orderFromStorage, poLine); + //When + unOpenCompositeOrderManager.process(order, orderFromStorage, false, requestContext).result(); + //Then + makeBasicUnOpenWorkflowChecks(order, orderFromStorage); + verify(pieceStorageService, never()).deletePiecesByIds(List.of(PIECE_ID), requestContext); + verify(inventoryManager, never()).deleteHoldingById(HOLDING_ID, true, requestContext); + verify(inventoryManager, never()).deleteItem(anyString(), anyBoolean(), any(RequestContext.class)); + verify(inventoryManager, never()).deleteItems(anyList(), anyBoolean(), any(RequestContext.class)); + } + + private void prepareInitialSetup(CompositePurchaseOrder order, CompositePurchaseOrder orderFromStorage, CompositePoLine poLine) { + doReturn(openToPendingEncumbranceStrategy).when(encumbranceWorkflowStrategyFactory).getStrategy(eq(OrderWorkflowType.OPEN_TO_PENDING)); + doReturn(succeededFuture(null)).when(openToPendingEncumbranceStrategy).processEncumbrances(eq(order), eq(orderFromStorage), any()); + JsonObject item = getItem(); + List onOrderItems = List.of(item); + doReturn(succeededFuture(onOrderItems)).when(inventoryManager).getItemsByPoLineIdsAndStatus(List.of(poLine.getId()), ItemStatus.ON_ORDER.value(), requestContext); + doReturn(succeededFuture()).when(inventoryManager).deleteItems(List.of(ITEM_ID), false, requestContext); + doReturn(succeededFuture(onOrderItems)).when(inventoryManager).getItemRecordsByIds(List.of(ITEM_ID), requestContext); + Piece piece = new Piece().withId(PIECE_ID).withItemId(ITEM_ID).withPoLineId(poLine.getId()); + PieceCollection pieceCollection = new PieceCollection().withPieces(List.of(piece)); + doReturn(succeededFuture(pieceCollection)).when(pieceStorageService).getExpectedPiecesByLineId(poLine.getId(), requestContext); + doReturn(succeededFuture()).when(pieceStorageService).deletePiecesByIds(List.of(PIECE_ID), requestContext); + doReturn(succeededFuture(piece)).when(pieceStorageService).getPieceById(PIECE_ID, requestContext); + PoLine simpleLine = new PoLine().withId(poLine.getId()).withPurchaseOrderId(order.getId()).withIsPackage(poLine.getIsPackage()); + doReturn(succeededFuture(simpleLine)).when(purchaseOrderLineService).getOrderLineById(poLine.getId(), requestContext); + PurchaseOrder simpleOrder = new PurchaseOrder().withId(order.getId()); + doReturn(succeededFuture(simpleOrder)).when(purchaseOrderStorageService).getPurchaseOrderById(order.getId(), requestContext); + doReturn(succeededFuture()).when(protectionService).isOperationRestricted(anyList(), any(ProtectedOperationType.class), any()); + doReturn(succeededFuture(0)).when(inventoryManager).getNumberOfRequestsByItemId(ITEM_ID, requestContext); + doReturn(succeededFuture()).when(pieceStorageService).deletePiece(PIECE_ID, requestContext); + doReturn(succeededFuture()).when(inventoryManager).deleteItem(piece.getItemId(), true, requestContext); + doReturn(succeededFuture(Collections.emptyList())).when(inventoryManager).getItemsByHoldingId(HOLDING_ID, requestContext); + doReturn(succeededFuture()).when(inventoryManager).deleteHoldingById(HOLDING_ID, true, requestContext); + JsonObject holding = new JsonObject().put("id", HOLDING_ID).put("permanentLocationId", poLine.getLocations().get(0).getLocationId()); + doReturn(succeededFuture(List.of(holding))).when(inventoryManager).getHoldingsByIds(List.of(HOLDING_ID), requestContext); + } + + private JsonObject getItem() { + return new JsonObject() + .put("id", ITEM_ID) + .put("status", new JsonObject().put("name", ItemStatus.ON_ORDER.value())) + .put("holdingsRecordId", HOLDING_ID) + .put("effectiveLocation", new JsonObject().put("id", EFFECTIVE_LOCATION_ID)); + } + + private CompositePoLine getPoLine(CompositePurchaseOrder order) { + CompositePoLine poLine = order.getCompositePoLines().get(0); + poLine.setReceiptStatus(CompositePoLine.ReceiptStatus.AWAITING_RECEIPT); + poLine.setPaymentStatus(CompositePoLine.PaymentStatus.AWAITING_PAYMENT); + return poLine; + } + + private void makeBasicUnOpenWorkflowChecks(CompositePurchaseOrder order, CompositePurchaseOrder orderFromStorage) { + assertEquals(CompositePoLine.ReceiptStatus.PENDING, order.getCompositePoLines().get(0).getReceiptStatus()); + assertEquals(CompositePoLine.PaymentStatus.PENDING, order.getCompositePoLines().get(0).getPaymentStatus()); + verify(openToPendingEncumbranceStrategy).processEncumbrances(order, orderFromStorage, requestContext); + verify(purchaseOrderLineService).saveOrderLine(any(PoLine.class), eq(requestContext)); + } + + private void makeBasicUnOpenWorkflowChecksForReceiptNotRequired(CompositePurchaseOrder order, CompositePurchaseOrder orderFromStorage) { + assertEquals(CompositePoLine.ReceiptStatus.RECEIPT_NOT_REQUIRED, order.getCompositePoLines().get(0).getReceiptStatus()); + assertEquals(CompositePoLine.PaymentStatus.PAYMENT_NOT_REQUIRED, order.getCompositePoLines().get(0).getPaymentStatus()); + verify(openToPendingEncumbranceStrategy).processEncumbrances(order, orderFromStorage, requestContext); + verify(purchaseOrderLineService).saveOrderLine(any(PoLine.class), eq(requestContext)); + } + + /** + * Define unit test specific beans to override actual ones + */ + static class ContextConfiguration { @Bean - CompositeOrderDynamicDataPopulateService orderLinesSummaryPopulateService() { - return mock(OrderLinesSummaryPopulateService.class); + public PurchaseOrderLineService purchaseOrderLineService() { + return mock(PurchaseOrderLineService.class); } @Bean - CompositeOrderDynamicDataPopulateService combinedPopulateService() { - return mock(CombinedOrderDataPopulateService.class); + public EncumbranceWorkflowStrategyFactory encumbranceWorkflowStrategyFactory() { + return mock(EncumbranceWorkflowStrategyFactory.class); } @Bean - PurchaseOrderLineService purchaseOrderLineService() { - return mock(PurchaseOrderLineService.class); + public InventoryManager inventoryManager() { + return mock(InventoryManager.class); } @Bean - public TitlesService titlesService() { - return mock(TitlesService.class); + public PieceStorageService pieceStorageService() { + return mock(PieceStorageService.class); } @Bean - public AcquisitionsUnitsService acquisitionsUnitsService() { - return mock(AcquisitionsUnitsService.class); + public PurchaseOrderStorageService purchaseOrderStorageService() { + return mock(PurchaseOrderStorageService.class); } @Bean @@ -223,13 +447,14 @@ public ProtectionService protectionService() { } @Bean - public InventoryManager inventoryManager() { - return mock(InventoryManager.class); - } - - @Bean - public PieceService piecesService() { - return mock(PieceService.class); + UnOpenCompositeOrderManager unOpenCompositeOrderManager(PurchaseOrderLineService purchaseOrderLineService, + EncumbranceWorkflowStrategyFactory encumbranceWorkflowStrategyFactory, + InventoryManager inventoryManager, + PieceStorageService pieceStorageService, + PurchaseOrderStorageService purchaseOrderStorageService, + ProtectionService protectionService) { + return spy(new UnOpenCompositeOrderManager(purchaseOrderLineService, encumbranceWorkflowStrategyFactory, inventoryManager, + pieceStorageService, purchaseOrderStorageService, protectionService)); } } }