diff --git a/ramls/acq-models b/ramls/acq-models index a176d4e5d..fa765ca39 160000 --- a/ramls/acq-models +++ b/ramls/acq-models @@ -1 +1 @@ -Subproject commit a176d4e5d4744e9ac9e7957b30da7f07b24a531a +Subproject commit fa765ca3997a1bd20df30242143a3a7ed7a08dd4 diff --git a/src/main/java/org/folio/helper/BindHelper.java b/src/main/java/org/folio/helper/BindHelper.java index e69bc0b25..de40cf1fe 100644 --- a/src/main/java/org/folio/helper/BindHelper.java +++ b/src/main/java/org/folio/helper/BindHelper.java @@ -72,8 +72,8 @@ public Future bindPieces(BindPiecesCollection bindPiecesCollec } private Future processBindPieces(BindPiecesCollection bindPiecesCollection, RequestContext requestContext) { - // 1. Get piece records from storage - return retrievePieceRecords(requestContext) + // 1. Get valid piece records from storage + return getValidPieces(requestContext) // 2. Generate holder object to include necessary data .map(piecesGroupedByPoLine -> generateHolder(piecesGroupedByPoLine, bindPiecesCollection)) // 3. Check if there are any open requests for items @@ -98,13 +98,25 @@ private BindPiecesHolder generateHolder(Map> piecesGroupedBy .withPiecesGroupedByPoLine(piecesGroupedByPoLine); } + private Future>> getValidPieces(RequestContext requestContext) { + return retrievePieceRecords(requestContext) + .map(piecesGroupedByPoLine -> { + var areAllPiecesReceived = extractAllPieces(piecesGroupedByPoLine) + .allMatch(piece -> RECEIVED_STATUSES.contains(piece.getReceivingStatus())); + if (areAllPiecesReceived) { + return piecesGroupedByPoLine; + } + throw new HttpException(RestConstants.VALIDATION_ERROR, ErrorCodes.PIECES_MUST_HAVE_RECEIVED_STATUS); + }); + } + private Future checkRequestsForPieceItems(BindPiecesHolder holder, RequestContext requestContext) { var tenantToItem = mapTenantIdsToItemIds(holder.getPiecesGroupedByPoLine(), requestContext); return GenericCompositeFuture.all( tenantToItem.entrySet().stream() .map(entry -> { var locationContext = createContextWithNewTenantId(requestContext, entry.getKey()); - return inventoryItemRequestService.getItemsWithActiveRequests(entry.getValue(), locationContext) + return inventoryItemRequestService.getItemIdsWithActiveRequests(entry.getValue(), locationContext) .compose(items -> validateItemsForRequestTransfer(tenantToItem.keySet(), items, holder.getBindPiecesCollection())); }) .toList()) @@ -168,12 +180,12 @@ private Future createItemForPieces(BindPiecesHolder holder, Re logger.debug("createItemForPiece:: Trying to get poLine by id '{}'", poLineId); return purchaseOrderLineService.getOrderLineById(poLineId, requestContext) .map(PoLineCommonUtil::convertToCompositePoLine) - .compose(compPOL -> createInventoryObjects(compPOL, bindPiecesCollection.getBindItem(), requestContext)) + .compose(compPOL -> createInventoryObjects(compPOL, bindPiecesCollection.getInstanceId(), bindPiecesCollection.getBindItem(), requestContext)) .map(newItemId -> { // Move requests if requestsAction is TRANSFER, otherwise do nothing if (TRANSFER.equals(bindPiecesCollection.getRequestsAction())) { var itemIds = holder.getPieces().map(Piece::getItemId).toList(); - inventoryItemRequestService.transferItemsRequests(itemIds, newItemId, requestContext); + inventoryItemRequestService.transferItemRequests(itemIds, newItemId, requestContext); } // Set new item ids for pieces and holder holder.getPieces().forEach(piece -> piece.setItemId(newItemId)); @@ -181,10 +193,13 @@ private Future createItemForPieces(BindPiecesHolder holder, Re }); } - private Future createInventoryObjects(CompositePoLine compPOL, BindItem bindItem, RequestContext requestContext) { + private Future createInventoryObjects(CompositePoLine compPOL, String instanceId, BindItem bindItem, RequestContext requestContext) { + if (!Boolean.TRUE.equals(compPOL.getIsPackage())) { + instanceId = compPOL.getInstanceId(); + } var locationContext = createContextWithNewTenantId(requestContext, bindItem.getTenantId()); - return handleInstance(compPOL.getInstanceId(), bindItem.getTenantId(), locationContext, requestContext) - .compose(instanceId -> handleHolding(bindItem, instanceId, locationContext)) + return handleInstance(instanceId, bindItem.getTenantId(), locationContext, requestContext) + .compose(instId -> handleHolding(bindItem, instId, locationContext)) .compose(holdingId -> inventoryItemManager.createBindItem(compPOL, holdingId, bindItem, locationContext)); } @@ -236,7 +251,8 @@ private BindPiecesResult prepareResponseBody(BindPiecesHolder holder) { @Override protected boolean isRevertToOnOrder(Piece piece) { - return false; + // Set to true for piece validation while fetching + return true; } @Override diff --git a/src/main/java/org/folio/orders/utils/CommonFields.java b/src/main/java/org/folio/orders/utils/CommonFields.java new file mode 100644 index 000000000..e76b7b222 --- /dev/null +++ b/src/main/java/org/folio/orders/utils/CommonFields.java @@ -0,0 +1,19 @@ +package org.folio.orders.utils; + +public enum CommonFields { + + ID("id"), + METADATA("metadata"), + CREATED_DATE("createdDate"); + + private final String value; + + CommonFields(String value) { + this.value = value; + } + + public String getValue() { + return value; + } + +} diff --git a/src/main/java/org/folio/orders/utils/HelperUtils.java b/src/main/java/org/folio/orders/utils/HelperUtils.java index fe294461d..e56fc4013 100644 --- a/src/main/java/org/folio/orders/utils/HelperUtils.java +++ b/src/main/java/org/folio/orders/utils/HelperUtils.java @@ -542,6 +542,11 @@ public static String extractId(JsonObject json) { return json.getString(ID); } + public static String extractCreatedDate(JsonObject json) { + return json.getJsonObject(CommonFields.METADATA.getValue()) + .getString(CommonFields.CREATED_DATE.getValue()); + } + public static CompositePurchaseOrder convertToCompositePurchaseOrder(PurchaseOrder purchaseOrder, List poLineList) { List compositePoLines = new ArrayList<>(poLineList.size()); if (CollectionUtils.isNotEmpty(poLineList)) { diff --git a/src/main/java/org/folio/rest/core/exceptions/ErrorCodes.java b/src/main/java/org/folio/rest/core/exceptions/ErrorCodes.java index 59a01ca48..bff976338 100644 --- a/src/main/java/org/folio/rest/core/exceptions/ErrorCodes.java +++ b/src/main/java/org/folio/rest/core/exceptions/ErrorCodes.java @@ -85,6 +85,7 @@ public enum ErrorCodes { REQUEST_FOUND("thereAreRequestsOnItem", "There are requests on item"), REQUESTS_ACTION_REQUIRED("requestsActionRequired", "There are circulation requests on items and requestsAction should be populated"), PIECES_HAVE_DIFFERENT_RECEIVING_TENANT_IDS("piecesHaveDifferentReceivingTenantIds", "All pieces do not have the same receivingTenantId"), + PIECES_MUST_HAVE_RECEIVED_STATUS("piecesMustHaveReceivedStatus", "All pieces must have received status in order to be bound"), BUDGET_EXPENSE_CLASS_NOT_FOUND("budgetExpenseClassNotFound", "Budget expense class not found"), INACTIVE_EXPENSE_CLASS("inactiveExpenseClass", "Expense class is Inactive"), HOLDINGS_BY_INSTANCE_AND_LOCATION_NOT_FOUND("holdingsByInstanceAndLocationNotFoundError", "Holdings by instance id %s and location id %s not found"), diff --git a/src/main/java/org/folio/service/CirculationRequestsRetriever.java b/src/main/java/org/folio/service/CirculationRequestsRetriever.java index a61a18a93..36152812b 100644 --- a/src/main/java/org/folio/service/CirculationRequestsRetriever.java +++ b/src/main/java/org/folio/service/CirculationRequestsRetriever.java @@ -19,8 +19,8 @@ import static org.folio.service.inventory.InventoryUtils.REQUESTS; import static org.folio.service.inventory.util.RequestFields.COLLECTION_RECORDS; import static org.folio.service.inventory.util.RequestFields.COLLECTION_TOTAL; -import static org.folio.service.inventory.util.RequestFields.ID_KEY; -import static org.folio.service.inventory.util.RequestFields.ITEM_ID_KEY; +import static org.folio.service.inventory.util.RequestFields.REQUESTER_ID; +import static org.folio.service.inventory.util.RequestFields.ITEM_ID; public class CirculationRequestsRetriever { @@ -41,23 +41,21 @@ public Future getNumberOfRequestsByItemId(String itemId, RequestContext } public Future> getNumbersOfRequestsByItemIds(List itemIds, RequestContext requestContext) { - return getRequestsByIds(itemIds, requestContext) + return getRequestsByItemIds(itemIds, requestContext) .map(jsonList -> jsonList.stream() - .collect(Collectors.groupingBy(json -> json.getString(ITEM_ID_KEY.getValue()), Collectors.counting())) + .collect(Collectors.groupingBy(json -> json.getString(ITEM_ID.getValue()), Collectors.counting())) ); } - public Future> getRequestIdsByItemIds(List itemIds, RequestContext requestContext) { - return getRequestsByIds(itemIds, requestContext) + public Future>> getRequesterIdsToRequestsByItemIds(List itemIds, RequestContext requestContext) { + return getRequestsByItemIds(itemIds, requestContext) .map(jsonList -> jsonList.stream() - .map(json -> json.getString(ID_KEY.getValue())) - .toList() - ); + .collect(Collectors.groupingBy(json -> json.getString(REQUESTER_ID.getValue())))); } - private Future> getRequestsByIds(List itemIds, RequestContext requestContext) { + private Future> getRequestsByItemIds(List itemIds, RequestContext requestContext) { var futures = StreamEx.ofSubLists(itemIds, MAX_IDS_FOR_GET_RQ_15) - .map(ids -> String.format("(%s and status=\"%s*\")", convertIdsToCqlQuery(ids, ITEM_ID_KEY.getValue()), OUTSTANDING_REQUEST_STATUS_PREFIX)) + .map(ids -> String.format("(%s and status=\"%s*\")", convertIdsToCqlQuery(ids, ITEM_ID.getValue()), OUTSTANDING_REQUEST_STATUS_PREFIX)) .map(query -> new RequestEntry(INVENTORY_LOOKUP_ENDPOINTS.get(REQUESTS)) .withQuery(query).withOffset(0).withLimit(Integer.MAX_VALUE)) .map(entry -> restClient.getAsJsonObject(entry, requestContext)) diff --git a/src/main/java/org/folio/service/inventory/InventoryItemManager.java b/src/main/java/org/folio/service/inventory/InventoryItemManager.java index c26de8a94..e7478a38d 100644 --- a/src/main/java/org/folio/service/inventory/InventoryItemManager.java +++ b/src/main/java/org/folio/service/inventory/InventoryItemManager.java @@ -135,7 +135,7 @@ public Future> updateItemRecords(List itemRecords, Requ } public Future updateItemWithPieceFields(Piece piece, RequestContext requestContext) { - if (piece.getItemId() == null || piece.getPoLineId() == null) { + if (piece.getItemId() == null || piece.getPoLineId() == null || piece.getIsBound()) { return Future.succeededFuture(); } String itemId = piece.getItemId(); diff --git a/src/main/java/org/folio/service/inventory/InventoryItemRequestService.java b/src/main/java/org/folio/service/inventory/InventoryItemRequestService.java index 275b63862..687ea5050 100644 --- a/src/main/java/org/folio/service/inventory/InventoryItemRequestService.java +++ b/src/main/java/org/folio/service/inventory/InventoryItemRequestService.java @@ -10,13 +10,19 @@ import org.folio.rest.core.models.RequestEntry; import org.folio.service.CirculationRequestsRetriever; +import java.time.Instant; +import java.util.ArrayList; +import java.util.Collection; import java.util.List; import java.util.Map; import java.util.function.Function; +import static org.folio.orders.utils.HelperUtils.extractCreatedDate; +import static org.folio.orders.utils.HelperUtils.extractId; import static org.folio.service.inventory.InventoryUtils.INVENTORY_LOOKUP_ENDPOINTS; import static org.folio.service.inventory.InventoryUtils.REQUESTS; -import static org.folio.service.inventory.util.RequestFields.DESTINATION_KEY; +import static org.folio.service.inventory.util.RequestFields.DESTINATION_ITEM_ID; +import static org.folio.service.inventory.util.RequestFields.STATUS; public class InventoryItemRequestService { @@ -24,6 +30,7 @@ public class InventoryItemRequestService { private static final String REQUEST_ENDPOINT = INVENTORY_LOOKUP_ENDPOINTS.get(REQUESTS) + "/%s"; private static final String REQUEST_MOVE_ENDPOINT = REQUEST_ENDPOINT + "/move"; + private static final String REQUEST_CANCEL_STATUS = "Closed - Cancelled"; private final CirculationRequestsRetriever circulationRequestsRetriever; private final RestClient restClient; @@ -33,7 +40,7 @@ public InventoryItemRequestService(RestClient restClient, CirculationRequestsRet this.circulationRequestsRetriever = circulationRequestsRetriever; } - public Future> getItemsWithActiveRequests(List itemIds, RequestContext requestContext) { + public Future> getItemIdsWithActiveRequests(List itemIds, RequestContext requestContext) { if (logger.isDebugEnabled()) { logger.debug("getItemsWithActiveRequests:: Filtering itemIds with active requests: {}", itemIds); } @@ -44,35 +51,63 @@ public Future> getItemsWithActiveRequests(List itemIds, Req .toList()); } - public Future cancelItemsRequests(List itemIds, RequestContext requestContext) { - return handleItemsRequests(itemIds, requestContext, reqId -> cancelRequest(reqId, requestContext)); + public Future cancelItemRequests(List itemIds, RequestContext requestContext) { + return circulationRequestsRetriever.getRequesterIdsToRequestsByItemIds(itemIds, requestContext) + .compose(requesterToRequestsMap -> { + var requestsToCancel = requesterToRequestsMap.values().stream() + .flatMap(Collection::stream) + .toList(); + return handleItemRequests(requestsToCancel, request -> cancelRequest(request, requestContext)); + }); } - public Future transferItemsRequests(List originItemIds, String destinationItemId, RequestContext requestContext) { - return handleItemsRequests(originItemIds, requestContext, reqId -> transferRequest(reqId, destinationItemId, requestContext)); + public Future transferItemRequests(List originItemIds, String destinationItemId, RequestContext requestContext) { + return circulationRequestsRetriever.getRequesterIdsToRequestsByItemIds(originItemIds, requestContext) + .compose(requesterToRequestsMap -> { + var requestsToCancel = new ArrayList(); + var requestsToTransfer = new ArrayList(); + for (var requests : requesterToRequestsMap.values()) { + requestsToCancel.addAll(requests); + requests.stream().min(this::compareRequests).ifPresent(request -> { + requestsToTransfer.add(request); + requestsToCancel.remove(request); + }); + } + + return handleItemRequests(requestsToTransfer, request -> transferRequest(request, destinationItemId, requestContext)) + .compose(v -> handleItemRequests(requestsToCancel, request -> cancelRequest(request, requestContext))); + }); } - private Future handleItemsRequests(List itemId, RequestContext requestContext, Function> handler) { - return circulationRequestsRetriever.getRequestIdsByItemIds(itemId, requestContext) - .compose(reqIds -> { - var futures = reqIds.stream().map(handler).toList(); - return GenericCompositeFuture.all(futures); - }) + private Future handleItemRequests(List requests, Function> handler) { + return GenericCompositeFuture.all( + requests.stream() + .map(handler) + .toList()) .mapEmpty(); } - private Future cancelRequest(String reqId, RequestContext requestContext) { - logger.info("cancelRequest:: Cancelling Request with id='{}'", reqId); + private Future cancelRequest(JsonObject request, RequestContext requestContext) { + String reqId = extractId(request); + request.put(STATUS.getValue(), REQUEST_CANCEL_STATUS); RequestEntry requestEntry = new RequestEntry(String.format(REQUEST_ENDPOINT, reqId)); - return restClient.delete(requestEntry, requestContext); + logger.info("cancelRequest:: Cancelling Request with id='{}'", reqId); + return restClient.put(requestEntry, request, requestContext); + } + + private Future transferRequest(JsonObject request, String itemId, RequestContext requestContext) { + String reqId = extractId(request); + JsonObject jsonObject = JsonObject.of(DESTINATION_ITEM_ID.getValue(), itemId); + RequestEntry requestEntry = new RequestEntry(String.format(REQUEST_MOVE_ENDPOINT, reqId)); + logger.info("transferRequest:: Moving Request with id='{}' to item with id='{}'", reqId, itemId); + return restClient.postJsonObject(requestEntry, jsonObject, requestContext) + .mapEmpty(); } - private Future transferRequest(String reqId, String itemId, RequestContext requestContext) { - logger.info("transferRequest:: Moving Request with id='{}' to item with id='{}'", reqId, itemId); - JsonObject jsonObject = JsonObject.of(DESTINATION_KEY.getValue(), itemId); - RequestEntry requestEntry = new RequestEntry(String.format(REQUEST_MOVE_ENDPOINT, reqId)); - return restClient.postJsonObject(requestEntry, jsonObject, requestContext) - .mapEmpty(); + private int compareRequests(JsonObject r1, JsonObject r2) { + Instant createdDate1 = Instant.parse(extractCreatedDate(r1)); + Instant createdDate2 = Instant.parse(extractCreatedDate(r2)); + return createdDate1.compareTo(createdDate2); } } diff --git a/src/main/java/org/folio/service/inventory/util/RequestFields.java b/src/main/java/org/folio/service/inventory/util/RequestFields.java index 46f01e492..090ec0205 100644 --- a/src/main/java/org/folio/service/inventory/util/RequestFields.java +++ b/src/main/java/org/folio/service/inventory/util/RequestFields.java @@ -2,13 +2,14 @@ public enum RequestFields { - ID_KEY("id"), - ITEM_ID_KEY("itemId"), - DESTINATION_KEY("destinationItemId"), + ITEM_ID("itemId"), + REQUESTER_ID("itemId"), + STATUS("status"), + DESTINATION_ITEM_ID("destinationItemId"), COLLECTION_RECORDS("requests"), COLLECTION_TOTAL("totalRecords"); - private String value; + private final String value; RequestFields(String value) { this.value = value; diff --git a/src/main/java/org/folio/service/pieces/flows/update/PieceUpdateFlowInventoryManager.java b/src/main/java/org/folio/service/pieces/flows/update/PieceUpdateFlowInventoryManager.java index 8310263a0..c2857a8c6 100644 --- a/src/main/java/org/folio/service/pieces/flows/update/PieceUpdateFlowInventoryManager.java +++ b/src/main/java/org/folio/service/pieces/flows/update/PieceUpdateFlowInventoryManager.java @@ -107,7 +107,7 @@ private Future handleHolding(PieceUpdateHolder holder, RequestContext private Future handleItem(PieceUpdateHolder holder, RequestContext requestContext) { CompositePoLine poLineToSave = holder.getPoLineToSave(); Piece pieceToUpdate = holder.getPieceToUpdate(); - if (!DefaultPieceFlowsValidator.isCreateItemForPiecePossible(pieceToUpdate, poLineToSave)) { + if (!DefaultPieceFlowsValidator.isCreateItemForPiecePossible(pieceToUpdate, poLineToSave) || pieceToUpdate.getIsBound()) { return Future.succeededFuture(); } return inventoryItemManager.getItemRecordById(pieceToUpdate.getItemId(), true, requestContext) diff --git a/src/test/java/org/folio/rest/impl/CheckinReceivingApiTest.java b/src/test/java/org/folio/rest/impl/CheckinReceivingApiTest.java index 35bd4369e..a9c8248b3 100644 --- a/src/test/java/org/folio/rest/impl/CheckinReceivingApiTest.java +++ b/src/test/java/org/folio/rest/impl/CheckinReceivingApiTest.java @@ -87,6 +87,7 @@ import static org.folio.rest.core.exceptions.ErrorCodes.LOC_NOT_PROVIDED; import static org.folio.rest.core.exceptions.ErrorCodes.MULTIPLE_NONPACKAGE_TITLES; import static org.folio.rest.core.exceptions.ErrorCodes.PIECES_HAVE_DIFFERENT_RECEIVING_TENANT_IDS; +import static org.folio.rest.core.exceptions.ErrorCodes.PIECES_MUST_HAVE_RECEIVED_STATUS; import static org.folio.rest.core.exceptions.ErrorCodes.PIECE_ALREADY_RECEIVED; import static org.folio.rest.core.exceptions.ErrorCodes.PIECE_NOT_FOUND; import static org.folio.rest.core.exceptions.ErrorCodes.PIECE_NOT_RETRIEVED; @@ -1245,6 +1246,40 @@ void testBindPiecesToTitleWithTransferRequestsAction() { assertThat(pieceList.size(), is(1)); } + @Test + void testBindExpectedPieces() { + logger.info("=== Test POST Bind to Title with Expected Piece "); + + var order = getMinimalContentCompositePurchaseOrder() + .withWorkflowStatus(CompositePurchaseOrder.WorkflowStatus.OPEN); + var poLine = getMinimalContentCompositePoLine(order.getId()) + .withLocations(List.of(new Location().withHoldingId(UUID.randomUUID().toString()) + .withQuantityPhysical(1).withQuantity(1))); + var bindingPiece = getMinimalContentPiece(poLine.getId()) + .withItemId("522a501a-56b5-48d9-b28a-3a8f02482d98") // Present in mockdata/itemRequests/itemRequests.json + .withReceivingStatus(Piece.ReceivingStatus.EXPECTED) + .withFormat(org.folio.rest.jaxrs.model.Piece.Format.ELECTRONIC); + var bindPiecesCollection = new BindPiecesCollection() + .withPoLineId(poLine.getId()) + .withBindItem(getMinimalContentBindItem() + .withLocationId(null) + .withHoldingId(UUID.randomUUID().toString())) + .withBindPieceIds(List.of(bindingPiece.getId())) + .withRequestsAction(BindPiecesCollection.RequestsAction.TRANSFER); + + addMockEntry(PURCHASE_ORDER_STORAGE, order); + addMockEntry(PO_LINES_STORAGE, poLine); + addMockEntry(PIECES_STORAGE, bindingPiece); + addMockEntry(TITLES, getTitle(poLine)); + + var errors = verifyPostResponse(ORDERS_BIND_ENDPOINT, JsonObject.mapFrom(bindPiecesCollection).encode(), + prepareHeaders(EXIST_CONFIG_X_OKAPI_TENANT_LIMIT_10), APPLICATION_JSON, VALIDATION_ERROR) + .as(Errors.class) + .getErrors(); + + assertThat(errors.get(0).getMessage(), equalTo(PIECES_MUST_HAVE_RECEIVED_STATUS.getDescription())); + } + @Test void testPostReceivingWithErrorSearchingForPiece() { logger.info("=== Test POST Receiving - Receive resources with error searching for piece"); diff --git a/src/test/java/org/folio/service/CirculationRequestsRetrieverTest.java b/src/test/java/org/folio/service/CirculationRequestsRetrieverTest.java index eb8583f36..8343ae430 100644 --- a/src/test/java/org/folio/service/CirculationRequestsRetrieverTest.java +++ b/src/test/java/org/folio/service/CirculationRequestsRetrieverTest.java @@ -19,6 +19,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; +import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -153,11 +154,13 @@ void getRequestsByItemIdsTest(VertxTestContext vertxTestContext) { doReturn(Future.succeededFuture(mockData)).when(restClient).getAsJsonObject(any(RequestEntry.class), eq(requestContext)); - Future> future = circulationRequestsRetriever.getRequestIdsByItemIds(MOCK_ITEM_IDS, requestContext); + Future>> future = circulationRequestsRetriever.getRequesterIdsToRequestsByItemIds(MOCK_ITEM_IDS, requestContext); vertxTestContext.assertComplete(future).onComplete(f -> { assertTrue(f.succeeded()); - assertEquals(7, f.result().size()); + var reqMap = f.result(); + assertEquals(2, reqMap.size()); + assertEquals(7, reqMap.values().stream().mapToInt(Collection::size).sum()); verify(restClient, times(1)).getAsJsonObject(any(RequestEntry.class), eq(requestContext)); vertxTestContext.completeNow(); }); diff --git a/src/test/java/org/folio/service/inventory/InventoryItemRequestServiceTest.java b/src/test/java/org/folio/service/inventory/InventoryItemRequestServiceTest.java index bc759542f..00623f45b 100644 --- a/src/test/java/org/folio/service/inventory/InventoryItemRequestServiceTest.java +++ b/src/test/java/org/folio/service/inventory/InventoryItemRequestServiceTest.java @@ -20,6 +20,8 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; +import java.time.Instant; +import java.time.temporal.ChronoUnit; import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -39,9 +41,15 @@ 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.orders.utils.CommonFields.CREATED_DATE; +import static org.folio.orders.utils.CommonFields.ID; +import static org.folio.orders.utils.CommonFields.METADATA; import static org.folio.rest.RestConstants.OKAPI_URL; import static org.folio.rest.impl.PurchaseOrdersApiTest.X_OKAPI_TENANT; -import static org.folio.service.inventory.util.RequestFields.DESTINATION_KEY; +import static org.folio.service.inventory.util.RequestFields.DESTINATION_ITEM_ID; +import static org.folio.service.inventory.util.RequestFields.ITEM_ID; +import static org.folio.service.inventory.util.RequestFields.REQUESTER_ID; +import static org.folio.service.inventory.util.RequestFields.STATUS; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.ArgumentMatchers.any; @@ -56,6 +64,8 @@ @ExtendWith(VertxExtension.class) public class InventoryItemRequestServiceTest { + private final JsonObject sampleMetadata = JsonObject.of(CREATED_DATE.getValue(), Instant.now().toString()); + @Autowired RestClient restClient; @@ -115,7 +125,7 @@ void getItemIdsWithNoActiveRequestsTest(VertxTestContext vertxTestContext) { doReturn(Future.succeededFuture(itemReqMap)).when(circulationRequestsRetriever).getNumbersOfRequestsByItemIds(anyList(), eq(requestContext)); - Future> future = inventoryItemRequestService.getItemsWithActiveRequests(itemIds, requestContext); + Future> future = inventoryItemRequestService.getItemIdsWithActiveRequests(itemIds, requestContext); vertxTestContext.assertComplete(future).onComplete(f -> { assertTrue(f.succeeded()); @@ -134,7 +144,7 @@ void getItemIdsWithActiveRequestsTest(VertxTestContext vertxTestContext) { doReturn(Future.succeededFuture(itemReqMap)).when(circulationRequestsRetriever).getNumbersOfRequestsByItemIds(anyList(), eq(requestContext)); - Future> future = inventoryItemRequestService.getItemsWithActiveRequests(itemIds, requestContext); + Future> future = inventoryItemRequestService.getItemIdsWithActiveRequests(itemIds, requestContext); vertxTestContext.assertComplete(future).onComplete(f -> { assertTrue(f.succeeded()); @@ -147,49 +157,49 @@ void getItemIdsWithActiveRequestsTest(VertxTestContext vertxTestContext) { @Test void cancelSingleItemRequest(VertxTestContext vertxTestContext) { - String itemId = UUID.randomUUID().toString(); - String reqId = UUID.randomUUID().toString(); + var itemIds = generateUUIDs(1); + var reqMap = generateRequests(itemIds); - doReturn(Future.succeededFuture()).when(restClient).delete(any(RequestEntry.class), eq(requestContext)); - doReturn(Future.succeededFuture(List.of(reqId))).when(circulationRequestsRetriever).getRequestIdsByItemIds(anyList(), eq(requestContext)); + doReturn(Future.succeededFuture()).when(restClient).put(any(RequestEntry.class), any(JsonObject.class), eq(requestContext)); + doReturn(Future.succeededFuture(reqMap)).when(circulationRequestsRetriever).getRequesterIdsToRequestsByItemIds(anyList(), eq(requestContext)); - Future future = inventoryItemRequestService.cancelItemsRequests(List.of(itemId), requestContext); + Future future = inventoryItemRequestService.cancelItemRequests(itemIds, requestContext); vertxTestContext.assertComplete(future).onComplete(f -> { assertTrue(f.succeeded()); - verify(restClient, times(1)).delete(any(RequestEntry.class), any(RequestContext.class)); + verify(restClient, times(1)).put(any(RequestEntry.class), any(JsonObject.class), eq(requestContext)); vertxTestContext.completeNow(); }); } @Test void cancelManyItemRequests(VertxTestContext vertxTestContext) { - List itemIds = generateUUIDs(10); - List reqIds = generateUUIDs(10); + var itemIds = generateUUIDs(10); + var reqMap = generateRequests(itemIds); - doReturn(Future.succeededFuture()).when(restClient).delete(any(RequestEntry.class), eq(requestContext)); - doReturn(Future.succeededFuture(reqIds)).when(circulationRequestsRetriever).getRequestIdsByItemIds(anyList(), eq(requestContext)); + doReturn(Future.succeededFuture()).when(restClient).put(any(RequestEntry.class), any(JsonObject.class), eq(requestContext)); + doReturn(Future.succeededFuture(reqMap)).when(circulationRequestsRetriever).getRequesterIdsToRequestsByItemIds(anyList(), eq(requestContext)); - Future future = inventoryItemRequestService.cancelItemsRequests(itemIds, requestContext); + Future future = inventoryItemRequestService.cancelItemRequests(itemIds, requestContext); vertxTestContext.assertComplete(future).onComplete(f -> { assertTrue(f.succeeded()); - verify(restClient, times(10)).delete(any(RequestEntry.class), eq(requestContext)); + verify(restClient, times(10)).put(any(RequestEntry.class), any(JsonObject.class), eq(requestContext)); vertxTestContext.completeNow(); }); } @Test void transferSingleItemRequest(VertxTestContext vertxTestContext) { - String itemId = UUID.randomUUID().toString(); - String reqId = UUID.randomUUID().toString(); + var itemIds = generateUUIDs(1); + var reqMap = generateRequests(itemIds); String destItemId = UUID.randomUUID().toString(); - JsonObject jsonObject = JsonObject.of(DESTINATION_KEY.getValue(), destItemId); + JsonObject jsonObject = JsonObject.of(DESTINATION_ITEM_ID.getValue(), destItemId); doReturn(Future.succeededFuture()).when(restClient).postJsonObject(any(RequestEntry.class), eq(jsonObject), eq(requestContext)); - doReturn(Future.succeededFuture(List.of(reqId))).when(circulationRequestsRetriever).getRequestIdsByItemIds(anyList(), eq(requestContext)); + doReturn(Future.succeededFuture(reqMap)).when(circulationRequestsRetriever).getRequesterIdsToRequestsByItemIds(anyList(), eq(requestContext)); - Future future = inventoryItemRequestService.transferItemsRequests(List.of(itemId), destItemId, requestContext); + Future future = inventoryItemRequestService.transferItemRequests(itemIds, destItemId, requestContext); vertxTestContext.assertComplete(future).onComplete(f -> { assertTrue(f.succeeded()); @@ -200,19 +210,54 @@ void transferSingleItemRequest(VertxTestContext vertxTestContext) { @Test void transferManyItemRequests(VertxTestContext vertxTestContext) { - List itemIds = generateUUIDs(10); - List reqIds = generateUUIDs(10); + var itemIds = generateUUIDs(10); + var reqMap = generateRequests(itemIds); + + String destItemId = UUID.randomUUID().toString(); + JsonObject jsonObject = JsonObject.of(DESTINATION_ITEM_ID.getValue(), destItemId); + + doReturn(Future.succeededFuture()).when(restClient).postJsonObject(any(RequestEntry.class), eq(jsonObject), eq(requestContext)); + doReturn(Future.succeededFuture(reqMap)).when(circulationRequestsRetriever).getRequesterIdsToRequestsByItemIds(anyList(), eq(requestContext)); + + Future future = inventoryItemRequestService.transferItemRequests(itemIds, destItemId, requestContext); + + vertxTestContext.assertComplete(future).onComplete(f -> { + assertTrue(f.succeeded()); + verify(restClient, times(10)).postJsonObject(any(RequestEntry.class), eq(jsonObject), eq(requestContext)); + vertxTestContext.completeNow(); + }); + } + + @Test + void transferManyItemRequestsAndCancelOne(VertxTestContext vertxTestContext) { + var itemIds = generateUUIDs(10); + var reqMap = generateRequests(itemIds); + + // Add request for a different item with same requester + var entries = reqMap.entrySet().stream().toList(); + var reqEntry = entries.get(0); + var newRequest = new JsonObject() + .put(ID.getValue(), UUID.randomUUID().toString()) + .put(ITEM_ID.getValue(), UUID.randomUUID().toString()) + .put(METADATA.getValue(), JsonObject.of(CREATED_DATE.getValue(), Instant.now().plus(1, ChronoUnit.DAYS).toString())) + .put(REQUESTER_ID.getValue(), reqEntry.getKey()); + reqEntry.getValue().add(newRequest); + String destItemId = UUID.randomUUID().toString(); - JsonObject jsonObject = JsonObject.of(DESTINATION_KEY.getValue(), destItemId); + JsonObject jsonObject = JsonObject.of(DESTINATION_ITEM_ID.getValue(), destItemId); doReturn(Future.succeededFuture()).when(restClient).postJsonObject(any(RequestEntry.class), eq(jsonObject), eq(requestContext)); - doReturn(Future.succeededFuture(reqIds)).when(circulationRequestsRetriever).getRequestIdsByItemIds(anyList(), eq(requestContext)); + doReturn(Future.succeededFuture()).when(restClient).put(any(RequestEntry.class), any(JsonObject.class), eq(requestContext)); + doReturn(Future.succeededFuture(reqMap)).when(circulationRequestsRetriever).getRequesterIdsToRequestsByItemIds(anyList(), eq(requestContext)); - Future future = inventoryItemRequestService.transferItemsRequests(itemIds, destItemId, requestContext); + // One requester will now have two requests, old one will be cancelled + Future future = inventoryItemRequestService.transferItemRequests(itemIds, destItemId, requestContext); vertxTestContext.assertComplete(future).onComplete(f -> { assertTrue(f.succeeded()); + newRequest.put(STATUS.getValue(), "Closed - Cancelled"); verify(restClient, times(10)).postJsonObject(any(RequestEntry.class), eq(jsonObject), eq(requestContext)); + verify(restClient, times(1)).put(any(RequestEntry.class), eq(newRequest), eq(requestContext)); vertxTestContext.completeNow(); }); } @@ -225,6 +270,20 @@ private List generateUUIDs(int n) { return ids; } + private Map> generateRequests(List itemIds) { + Map> reqMap = new HashMap<>(); + for (var itemId : itemIds) { + var requesterId = UUID.randomUUID().toString(); + var requests = new ArrayList(); + requests.add(new JsonObject() + .put(ID.getValue(), UUID.randomUUID().toString()) + .put(ITEM_ID.getValue(), itemId) + .put(METADATA.getValue(), sampleMetadata) + .put(REQUESTER_ID.getValue(), requesterId)); + reqMap.put(UUID.randomUUID().toString(), requests); + } + return reqMap; + } /** * Define unit test specific beans to override actual ones diff --git a/src/test/resources/mockdata/requests/requests.json b/src/test/resources/mockdata/requests/requests.json index ae8b462da..b89957879 100644 --- a/src/test/resources/mockdata/requests/requests.json +++ b/src/test/resources/mockdata/requests/requests.json @@ -1,12 +1,12 @@ { "requests": [ - {"id": "ab18897b-0e40-4f31-896b-9c9adc979a00", "itemId": "8d0350f6-3ca0-4876-9e44-0f6a6bd79e47"}, - {"id": "ab18897b-0e40-4f31-896b-9c9adc979a01", "itemId": "8d0350f6-3ca0-4876-9e44-0f6a6bd79e47"}, - {"id": "ab18897b-0e40-4f31-896b-9c9adc979a02", "itemId": "8d0350f6-3ca0-4876-9e44-0f6a6bd79e47"}, - {"id": "ab18897b-0e40-4f31-896b-9c9adc979a03", "itemId": "8d0350f6-3ca0-4876-9e44-0f6a6bd79e47"}, - {"id": "ab18897b-0e40-4f31-896b-9c9adc979a04", "itemId": "8d0350f6-3ca0-4876-9e44-0f6a6bd79e47"}, - {"id": "33d3d7f4-a937-4d16-af05-6eefde43c962", "itemId": "e477dd71-3144-48ee-9972-d93f726ce0d8"}, - {"id": "33d3d7f4-a937-4d16-af05-6eefde43c963", "itemId": "e477dd71-3144-48ee-9972-d93f726ce0d8"} + {"id": "ab18897b-0e40-4f31-896b-9c9adc979a00", "requesterId": "ac17d541-7c28-43c3-b9f2-9289a9988a44", "itemId": "8d0350f6-3ca0-4876-9e44-0f6a6bd79e47"}, + {"id": "ab18897b-0e40-4f31-896b-9c9adc979a01", "requesterId": "ac17d541-7c28-43c3-b9f2-9289a9988a45", "itemId": "8d0350f6-3ca0-4876-9e44-0f6a6bd79e47"}, + {"id": "ab18897b-0e40-4f31-896b-9c9adc979a02", "requesterId": "ac17d541-7c28-43c3-b9f2-9289a9988a46", "itemId": "8d0350f6-3ca0-4876-9e44-0f6a6bd79e47"}, + {"id": "ab18897b-0e40-4f31-896b-9c9adc979a03", "requesterId": "ac17d541-7c28-43c3-b9f2-9289a9988a47", "itemId": "8d0350f6-3ca0-4876-9e44-0f6a6bd79e47"}, + {"id": "ab18897b-0e40-4f31-896b-9c9adc979a04", "requesterId": "ac17d541-7c28-43c3-b9f2-9289a9988a48", "itemId": "8d0350f6-3ca0-4876-9e44-0f6a6bd79e47"}, + {"id": "33d3d7f4-a937-4d16-af05-6eefde43c962", "requesterId": "ac17d541-7c28-43c3-b9f2-9289a9988a44", "itemId": "e477dd71-3144-48ee-9972-d93f726ce0d8"}, + {"id": "33d3d7f4-a937-4d16-af05-6eefde43c963", "requesterId": "ac17d541-7c28-43c3-b9f2-9289a9988a45", "itemId": "e477dd71-3144-48ee-9972-d93f726ce0d8"} ], "totalRecords": 7 }