From 8f898b23240a39591185af6a2f46ef83a202e948 Mon Sep 17 00:00:00 2001 From: Antony Hruschev Date: Mon, 16 Dec 2024 14:03:17 +0400 Subject: [PATCH 1/6] UXPROD 5001 Support DCB re-requests (#130) UXPROD 5001 Support DCB re-requests --- descriptors/ModuleDescriptor-template.json | 36 ++++++- .../client/feign/CirculationItemClient.java | 2 +- .../controller/TransactionApiController.java | 8 ++ .../dcb/domain/mapper/TransactionMapper.java | 11 +++ .../kafka/CirculationEventListener.java | 4 +- .../folio/dcb/listener/kafka/EventData.java | 1 + .../folio/dcb/service/CirculationService.java | 14 ++- .../dcb/service/TransactionsService.java | 2 + .../dcb/service/impl/BaseLibraryService.java | 30 +++++- .../impl/CirculationItemServiceImpl.java | 20 ++-- .../service/impl/CirculationServiceImpl.java | 5 +- .../impl/PickupLibraryServiceImpl.java | 1 + .../service/impl/TransactionsServiceImpl.java | 15 +++ .../folio/dcb/utils/TransactionHelper.java | 6 ++ .../swagger.api/dcb_transaction.yaml | 23 +++++ .../schemas/CirculationRequest.yaml | 3 + .../swagger.api/schemas/dcbUpdateItem.yaml | 18 ++++ .../schemas/dcbUpdateTransaction.yaml | 6 ++ .../TransactionApiControllerTest.java | 94 +++++++++++++++---- .../CirculationRequestEventListenerTest.java | 14 ++- .../dcb/service/BaseLibraryServiceTest.java | 8 +- .../dcb/service/CirculationServiceTest.java | 41 +++++--- .../java/org/folio/dcb/utils/EntityUtils.java | 15 +++ .../resources/mappings/circulation-item.json | 22 ++++- src/test/resources/mappings/circulation.json | 5 +- src/test/resources/mappings/inventory.json | 13 +++ .../resources/mappings/material-types.json | 13 +++ .../mockdata/kafka/cancel_request.json | 3 +- .../mockdata/kafka/cancel_request_dcb.json | 3 +- .../kafka/cancellation_dcb_rerequest.json | 24 +++++ .../mockdata/kafka/check_in_dcb.json | 3 +- .../mockdata/kafka/check_in_transit.json | 3 +- .../mockdata/kafka/check_in_transit_dcb.json | 3 +- .../mockdata/kafka/request_sample.json | 3 +- .../mockdata/kafka/request_undefined.json | 3 +- 35 files changed, 410 insertions(+), 65 deletions(-) create mode 100644 src/main/resources/swagger.api/schemas/dcbUpdateItem.yaml create mode 100644 src/main/resources/swagger.api/schemas/dcbUpdateTransaction.yaml create mode 100644 src/test/resources/mockdata/kafka/cancellation_dcb_rerequest.json diff --git a/descriptors/ModuleDescriptor-template.json b/descriptors/ModuleDescriptor-template.json index 1b52f2b1..4393f8d4 100644 --- a/descriptors/ModuleDescriptor-template.json +++ b/descriptors/ModuleDescriptor-template.json @@ -141,6 +141,30 @@ "dcb.transactions.collection.get" ], "modulePermissions": [] + }, + { + "methods": [ + "PUT" + ], + "pathPattern": "/transactions/{dcbTransactionId}", + "permissionsRequired": [ + "dcb.transactions.item.put" + ], + "modulePermissions": [ + "inventory-storage.items.item.get", + "inventory-storage.items.collection.get", + "inventory-storage.holdings.item.get", + "circulation.requests.item.post", + "circulation.requests.item.put", + "circulation-item.item.post", + "circulation-item.collection.get", + "circulation-item.item.get", + "inventory-storage.material-types.collection.get", + "inventory-storage.loan-types.collection.get", + "circulation-storage.requests.item.get", + "circulation-storage.cancellation-reasons.item.get", + "circulation-storage.cancellation-reasons.item.post" + ] } ] }, @@ -232,7 +256,8 @@ "dcb.transactions.put", "dcb.transactions.get", "dcb.transactions.collection.get", - "dcb.ecs-request.transactions.post" + "dcb.ecs-request.transactions.post", + "dcb.transactions.item.put" ] }, { @@ -247,8 +272,8 @@ }, { "permissionName": "dcb.transactions.put", - "displayName": "update transaction details", - "description": "update transaction details" + "displayName": "update transaction status", + "description": "update transaction status" }, { "permissionName": "dcb.transactions.collection.get", @@ -259,6 +284,11 @@ "permissionName": "dcb.ecs-request.transactions.post", "displayName": "creates new ECS request transaction", "description": "creates new ECS request transaction" + }, + { + "permissionName": "dcb.transactions.item.put", + "displayName": "update transaction details", + "description": "update transaction details" } ], "metadata": { diff --git a/src/main/java/org/folio/dcb/client/feign/CirculationItemClient.java b/src/main/java/org/folio/dcb/client/feign/CirculationItemClient.java index 1216c069..c8694954 100644 --- a/src/main/java/org/folio/dcb/client/feign/CirculationItemClient.java +++ b/src/main/java/org/folio/dcb/client/feign/CirculationItemClient.java @@ -19,4 +19,4 @@ public interface CirculationItemClient { CirculationItem retrieveCirculationItemById(@PathVariable("circulationItemId") String circulationItemId); @GetMapping - CirculationItemCollection fetchItemByIdAndBarcode(@RequestParam("query") String query);} + CirculationItemCollection fetchItemByCqlQuery(@RequestParam("query") String query);} diff --git a/src/main/java/org/folio/dcb/controller/TransactionApiController.java b/src/main/java/org/folio/dcb/controller/TransactionApiController.java index c4279fb1..dd569b76 100644 --- a/src/main/java/org/folio/dcb/controller/TransactionApiController.java +++ b/src/main/java/org/folio/dcb/controller/TransactionApiController.java @@ -3,6 +3,7 @@ import lombok.AllArgsConstructor; import lombok.extern.log4j.Log4j2; import org.folio.dcb.domain.dto.DcbTransaction; +import org.folio.dcb.domain.dto.DcbUpdateTransaction; import org.folio.dcb.domain.dto.TransactionStatus; import org.folio.dcb.domain.dto.TransactionStatusResponseCollection; import org.folio.dcb.rest.resource.TransactionsApi; @@ -74,4 +75,11 @@ public ResponseEntity getTransactionStatusL .body(transactionsService.getTransactionStatusList(fromDate, toDate, pageNumber, pageSize)); } + @Override + public ResponseEntity updateTransactionDetails(String dcbTransactionId, DcbUpdateTransaction dcbUpdateTransaction) { + transactionsService.updateTransactionDetails(dcbTransactionId, dcbUpdateTransaction); + return ResponseEntity.status(HttpStatus.NO_CONTENT) + .build(); + } + } diff --git a/src/main/java/org/folio/dcb/domain/mapper/TransactionMapper.java b/src/main/java/org/folio/dcb/domain/mapper/TransactionMapper.java index 1b093f06..e4e91766 100644 --- a/src/main/java/org/folio/dcb/domain/mapper/TransactionMapper.java +++ b/src/main/java/org/folio/dcb/domain/mapper/TransactionMapper.java @@ -3,6 +3,7 @@ import org.folio.dcb.domain.dto.DcbItem; import org.folio.dcb.domain.dto.DcbPatron; import org.folio.dcb.domain.dto.DcbPickup; +import org.folio.dcb.domain.dto.DcbUpdateItem; import org.folio.dcb.domain.dto.TransactionStatusResponseList; import org.folio.dcb.domain.entity.TransactionAuditEntity; import org.folio.dcb.domain.entity.TransactionEntity; @@ -91,4 +92,14 @@ public DcbPickup mapTransactionEntityToDcbPickup(TransactionEntity transactionEn .build(); } + public DcbItem convertTransactionUpdateItemToDcbItem(DcbUpdateItem dcbUpdateItem, TransactionEntity entity) { + return DcbItem + .builder() + .lendingLibraryCode(dcbUpdateItem.getLendingLibraryCode()) + .barcode(dcbUpdateItem.getBarcode()) + .materialType(dcbUpdateItem.getMaterialType()) + .title(entity.getItemTitle()) + .build(); + } + } diff --git a/src/main/java/org/folio/dcb/listener/kafka/CirculationEventListener.java b/src/main/java/org/folio/dcb/listener/kafka/CirculationEventListener.java index 49069a2a..947fa2c5 100644 --- a/src/main/java/org/folio/dcb/listener/kafka/CirculationEventListener.java +++ b/src/main/java/org/folio/dcb/listener/kafka/CirculationEventListener.java @@ -78,14 +78,14 @@ public void handleRequestEvent(String data, MessageHeaders messageHeaders) { systemUserScopedExecutionService.executeAsyncSystemUserScoped(tenantId, () -> transactionRepository.findTransactionByRequestIdAndStatusNotInClosed(UUID.fromString(requestId)) .ifPresent(transactionEntity -> { - if (eventData.getType() == EventData.EventType.CANCEL) { + if (eventData.getType() == EventData.EventType.CANCEL && !eventData.isDcbReRequestCancellation()) { baseLibraryService.cancelTransactionEntity(transactionEntity); } else if (eventData.getType() == EventData.EventType.IN_TRANSIT && transactionEntity.getRole() == LENDER) { baseLibraryService.updateTransactionEntity(transactionEntity, TransactionStatus.StatusEnum.OPEN); } else if (eventData.getType() == EventData.EventType.AWAITING_PICKUP && (transactionEntity.getRole() == BORROWING_PICKUP || transactionEntity.getRole() == PICKUP)) { baseLibraryService.updateTransactionEntity(transactionEntity, TransactionStatus.StatusEnum.AWAITING_PICKUP); } else { - log.info("handleRequestEvent:: status for event {} can not be updated", eventData.getType()); + log.info("handleRequestEvent:: status for event {} can not be updated", eventData); } }) ); diff --git a/src/main/java/org/folio/dcb/listener/kafka/EventData.java b/src/main/java/org/folio/dcb/listener/kafka/EventData.java index 0b786a2b..4f7779ec 100644 --- a/src/main/java/org/folio/dcb/listener/kafka/EventData.java +++ b/src/main/java/org/folio/dcb/listener/kafka/EventData.java @@ -8,6 +8,7 @@ public class EventData { private String itemId; private String requestId; private boolean isDcb; + private boolean isDcbReRequestCancellation; public enum EventType { CHECK_IN, CHECK_OUT, IN_TRANSIT, AWAITING_PICKUP, CANCEL diff --git a/src/main/java/org/folio/dcb/service/CirculationService.java b/src/main/java/org/folio/dcb/service/CirculationService.java index 319c7e20..651955bb 100644 --- a/src/main/java/org/folio/dcb/service/CirculationService.java +++ b/src/main/java/org/folio/dcb/service/CirculationService.java @@ -17,5 +17,17 @@ public interface CirculationService { */ void checkOutByBarcode(TransactionEntity dcbTransaction); - void cancelRequest(TransactionEntity dcbTransaction); + /** + * Cancels a transaction request based on the provided transaction details. + *

+ * If {@code isItemUnavailableCancellation} is {@code true}, the notification for this + * cancellation will be suppressed by setting the {@code suppressNotification} flag + * to {@code true}. + *

+ * + * @param dcbTransaction the transaction entity representing the request to be canceled + * @param isItemUnavailableCancellation a flag indicating whether the cancellation is due to item unavailability + * (true if the item is unavailable, false otherwise) + */ + void cancelRequest(TransactionEntity dcbTransaction, boolean isItemUnavailableCancellation); } diff --git a/src/main/java/org/folio/dcb/service/TransactionsService.java b/src/main/java/org/folio/dcb/service/TransactionsService.java index 8d1fc190..549db7d7 100644 --- a/src/main/java/org/folio/dcb/service/TransactionsService.java +++ b/src/main/java/org/folio/dcb/service/TransactionsService.java @@ -1,6 +1,7 @@ package org.folio.dcb.service; import org.folio.dcb.domain.dto.DcbTransaction; +import org.folio.dcb.domain.dto.DcbUpdateTransaction; import org.folio.dcb.domain.dto.TransactionStatus; import org.folio.dcb.domain.dto.TransactionStatusResponse; import org.folio.dcb.domain.dto.TransactionStatusResponseCollection; @@ -17,5 +18,6 @@ public interface TransactionsService { TransactionStatusResponse updateTransactionStatus(String dcbTransactionId, TransactionStatus transactionStatus); TransactionStatusResponse getTransactionStatusById(String dcbTransactionId); TransactionStatusResponseCollection getTransactionStatusList(OffsetDateTime fromDate, OffsetDateTime toDate, Integer pageNumber, Integer pageSize); + void updateTransactionDetails(String dcbTransactionId, DcbUpdateTransaction dcbUpdateTransaction); } diff --git a/src/main/java/org/folio/dcb/service/impl/BaseLibraryService.java b/src/main/java/org/folio/dcb/service/impl/BaseLibraryService.java index dc06a555..f0f5ee05 100644 --- a/src/main/java/org/folio/dcb/service/impl/BaseLibraryService.java +++ b/src/main/java/org/folio/dcb/service/impl/BaseLibraryService.java @@ -5,7 +5,10 @@ import org.apache.commons.lang3.ObjectUtils; import org.folio.dcb.domain.dto.CirculationItem; import org.folio.dcb.domain.dto.CirculationRequest; +import org.folio.dcb.domain.dto.DcbItem; +import org.folio.dcb.domain.dto.DcbPatron; import org.folio.dcb.domain.dto.DcbTransaction; +import org.folio.dcb.domain.dto.DcbUpdateItem; import org.folio.dcb.domain.dto.TransactionStatus; import org.folio.dcb.domain.dto.TransactionStatusResponse; import org.folio.dcb.domain.entity.TransactionEntity; @@ -54,6 +57,7 @@ public TransactionStatusResponse createBorrowingLibraryTransaction(String dcbTra } checkItemExistsInInventoryAndThrow(itemVirtual.getBarcode()); CirculationItem item = circulationItemService.checkIfItemExistsAndCreate(itemVirtual, pickupServicePointId); + dcbTransaction.getItem().setId(item.getId()); checkOpenTransactionExistsAndThrow(item.getId()); CirculationRequest holdRequest = requestService.createHoldItemRequest(user, itemVirtual, pickupServicePointId); saveDcbTransaction(dcbTransactionId, dcbTransaction, holdRequest.getId()); @@ -106,7 +110,7 @@ public void updateTransactionStatus(TransactionEntity dcbTransaction, Transactio public void cancelTransactionRequest(TransactionEntity transactionEntity){ try { - circulationService.cancelRequest(transactionEntity); + circulationService.cancelRequest(transactionEntity, false); } catch (CirculationRequestException e) { updateTransactionEntity(transactionEntity, TransactionStatus.StatusEnum.ERROR); } @@ -133,4 +137,28 @@ public void updateTransactionEntity(TransactionEntity transactionEntity, Transac transactionEntity.setStatus(transactionStatusEnum); transactionRepository.save(transactionEntity); } + + public void updateTransactionDetails(TransactionEntity transactionEntity, DcbUpdateItem dcbUpdateItem) { + DcbPatron dcbPatron = transactionMapper.mapTransactionEntityToDcbPatron(transactionEntity); + DcbItem dcbItem = transactionMapper.convertTransactionUpdateItemToDcbItem(dcbUpdateItem, transactionEntity); + checkItemExistsInInventoryAndThrow(dcbItem.getBarcode()); + CirculationItem item = circulationItemService.checkIfItemExistsAndCreate(dcbItem, transactionEntity.getServicePointId()); + dcbItem.setId(item.getId()); + checkOpenTransactionExistsAndThrow(item.getId()); + circulationService.cancelRequest(transactionEntity, true); + CirculationRequest holdRequest = requestService.createHoldItemRequest(userService.fetchUser(dcbPatron), dcbItem, + transactionEntity.getServicePointId()); + updateItemDetailsAndSaveEntity(transactionEntity, item, dcbItem.getMaterialType(), holdRequest.getId()); + } + + private void updateItemDetailsAndSaveEntity(TransactionEntity transactionEntity, CirculationItem item, + String materialType, String requestId) { + transactionEntity.setItemId(item.getId()); + transactionEntity.setRequestId(UUID.fromString(requestId)); + transactionEntity.setItemBarcode(item.getBarcode()); + transactionEntity.setLendingLibraryCode(item.getLendingLibraryCode()); + transactionEntity.setMaterialType(materialType); + transactionEntity.setStatus(CREATED); + transactionRepository.save(transactionEntity); + } } diff --git a/src/main/java/org/folio/dcb/service/impl/CirculationItemServiceImpl.java b/src/main/java/org/folio/dcb/service/impl/CirculationItemServiceImpl.java index fe53443d..2e782a87 100644 --- a/src/main/java/org/folio/dcb/service/impl/CirculationItemServiceImpl.java +++ b/src/main/java/org/folio/dcb/service/impl/CirculationItemServiceImpl.java @@ -9,9 +9,11 @@ import org.folio.dcb.domain.dto.ItemStatus; import org.folio.dcb.service.CirculationItemService; import org.folio.dcb.service.ItemService; +import org.folio.util.StringUtil; import org.springframework.stereotype.Service; import java.util.Objects; +import java.util.UUID; import static org.folio.dcb.domain.dto.ItemStatus.NameEnum.IN_TRANSIT; import static org.folio.dcb.utils.DCBConstants.HOLDING_ID; @@ -29,18 +31,18 @@ public class CirculationItemServiceImpl implements CirculationItemService { @Override public CirculationItem checkIfItemExistsAndCreate(DcbItem dcbItem, String pickupServicePointId) { - var dcbItemId = dcbItem.getId(); - log.debug("checkIfItemExistsAndCreate:: generate Circulation item by DcbItem with id={} if nit doesn't exist.", dcbItemId); - var circulationItem = fetchCirculationItemByIdAndBarcode(dcbItemId, dcbItem.getBarcode()); + var dcbItemBarcode = dcbItem.getBarcode(); + log.debug("checkIfItemExistsAndCreate:: generate Circulation item with barcode {} if it doesn't exist.", dcbItemBarcode); + var circulationItem = fetchCirculationItemByBarcode(dcbItem.getBarcode()); if(Objects.isNull(circulationItem)) { - log.warn("Circulation item not found by id={}. Creating it.", dcbItemId); + log.warn("checkIfItemExistsAndCreate:: Circulation item not found by barcode={}. Creating it.", dcbItemBarcode); circulationItem = createCirculationItem(dcbItem, pickupServicePointId); } return circulationItem; } - private CirculationItem fetchCirculationItemByIdAndBarcode(String id, String barcode) { - return circulationItemClient.fetchItemByIdAndBarcode("id==" + id + " and barcode==" + barcode) + private CirculationItem fetchCirculationItemByBarcode(String barcode) { + return circulationItemClient.fetchItemByCqlQuery("barcode==" + StringUtil.cqlEncode(barcode)) .getItems() .stream() .findFirst() @@ -56,10 +58,10 @@ private CirculationItem createCirculationItem(DcbItem item, String pickupService //SetupDefaultMaterialTypeIfNotGiven String materialType = StringUtils.isBlank(item.getMaterialType()) ? MATERIAL_TYPE_NAME_BOOK : item.getMaterialType(); var materialTypeId = itemService.fetchItemMaterialTypeIdByMaterialTypeName(materialType); - + var itemId = UUID.randomUUID().toString(); CirculationItem circulationItem = CirculationItem.builder() - .id(item.getId()) + .id(itemId) .barcode(item.getBarcode()) .status(ItemStatus.builder() .name(IN_TRANSIT) @@ -72,6 +74,6 @@ private CirculationItem createCirculationItem(DcbItem item, String pickupService .lendingLibraryCode(item.getLendingLibraryCode()) .build(); - return circulationItemClient.createCirculationItem(item.getId(), circulationItem); + return circulationItemClient.createCirculationItem(itemId, circulationItem); } } diff --git a/src/main/java/org/folio/dcb/service/impl/CirculationServiceImpl.java b/src/main/java/org/folio/dcb/service/impl/CirculationServiceImpl.java index 9934385f..b0159392 100644 --- a/src/main/java/org/folio/dcb/service/impl/CirculationServiceImpl.java +++ b/src/main/java/org/folio/dcb/service/impl/CirculationServiceImpl.java @@ -41,11 +41,14 @@ public void checkOutByBarcode(TransactionEntity dcbTransaction) { } @Override - public void cancelRequest(TransactionEntity dcbTransaction) { + public void cancelRequest(TransactionEntity dcbTransaction, boolean isItemUnavailableCancellation) { log.debug("cancelRequest:: cancelling request using request id {} ", dcbTransaction.getRequestId()); CirculationRequest request = circulationStorageService.getCancellationRequestIfOpenOrNull(dcbTransaction.getRequestId().toString()); if (request != null){ try { + if (isItemUnavailableCancellation) { + request.setIsDcbReRequestCancellation(true); + } circulationClient.updateRequest(request.getId(), request); } catch (FeignException e) { log.warn("cancelRequest:: error cancelling request using request id {} ", dcbTransaction.getRequestId(), e); diff --git a/src/main/java/org/folio/dcb/service/impl/PickupLibraryServiceImpl.java b/src/main/java/org/folio/dcb/service/impl/PickupLibraryServiceImpl.java index 88a5495d..677235bb 100644 --- a/src/main/java/org/folio/dcb/service/impl/PickupLibraryServiceImpl.java +++ b/src/main/java/org/folio/dcb/service/impl/PickupLibraryServiceImpl.java @@ -33,6 +33,7 @@ public TransactionStatusResponse createCirculation(String dcbTransactionId, DcbT baseLibraryService.checkUserTypeAndThrowIfMismatch(user.getType()); baseLibraryService.checkItemExistsInInventoryAndThrow(itemVirtual.getBarcode()); CirculationItem item = circulationItemService.checkIfItemExistsAndCreate(itemVirtual, dcbTransaction.getPickup().getServicePointId()); + dcbTransaction.getItem().setId(item.getId()); baseLibraryService.checkOpenTransactionExistsAndThrow(item.getId()); CirculationRequest holdRequest = requestService.createHoldItemRequest(user, itemVirtual, dcbTransaction.getPickup().getServicePointId()); baseLibraryService.saveDcbTransaction(dcbTransactionId, dcbTransaction, holdRequest.getId()); diff --git a/src/main/java/org/folio/dcb/service/impl/TransactionsServiceImpl.java b/src/main/java/org/folio/dcb/service/impl/TransactionsServiceImpl.java index 64b41d20..050c3176 100644 --- a/src/main/java/org/folio/dcb/service/impl/TransactionsServiceImpl.java +++ b/src/main/java/org/folio/dcb/service/impl/TransactionsServiceImpl.java @@ -3,6 +3,7 @@ import lombok.RequiredArgsConstructor; import lombok.extern.log4j.Log4j2; import org.folio.dcb.domain.dto.DcbTransaction; +import org.folio.dcb.domain.dto.DcbUpdateTransaction; import org.folio.dcb.domain.dto.TransactionStatus; import org.folio.dcb.domain.dto.TransactionStatusResponse; import org.folio.dcb.domain.dto.TransactionStatusResponseCollection; @@ -39,6 +40,7 @@ public class TransactionsServiceImpl implements TransactionsService { private final StatusProcessorService statusProcessorService; private final TransactionMapper transactionMapper; private final TransactionAuditRepository transactionAuditRepository; + private final BaseLibraryService baseLibraryService; @Override public TransactionStatusResponse createCirculationRequest(String dcbTransactionId, DcbTransaction dcbTransaction) { @@ -109,6 +111,19 @@ public TransactionStatusResponseCollection getTransactionStatusList(OffsetDateTi .build(); } + @Override + public void updateTransactionDetails(String dcbTransactionId, DcbUpdateTransaction dcbUpdateTransaction) { + var transactionEntity = getTransactionEntityOrThrow(dcbTransactionId); + if (!TransactionStatus.StatusEnum.CREATED.equals(transactionEntity.getStatus())) { + throw new StatusException(String.format( + "Transaction details should not be updated from %s status, it can be updated only from CREATED status", transactionEntity.getStatus())); + } + if (DcbTransaction.RoleEnum.LENDER.equals(transactionEntity.getRole())) { + throw new IllegalArgumentException("Item details cannot be updated for lender role"); + } + baseLibraryService.updateTransactionDetails(transactionEntity, dcbUpdateTransaction.getItem()); + } + private TransactionStatusResponse generateTransactionStatusResponseFromTransactionEntity(TransactionEntity transactionEntity) { TransactionStatus.StatusEnum transactionStatus = transactionEntity.getStatus(); TransactionStatusResponse.StatusEnum transactionStatusResponseStatusEnum = TransactionStatusResponse.StatusEnum.fromValue(transactionStatus.getValue()); diff --git a/src/main/java/org/folio/dcb/utils/TransactionHelper.java b/src/main/java/org/folio/dcb/utils/TransactionHelper.java index 6468ad25..93888ed4 100644 --- a/src/main/java/org/folio/dcb/utils/TransactionHelper.java +++ b/src/main/java/org/folio/dcb/utils/TransactionHelper.java @@ -57,6 +57,7 @@ public static EventData parseRequestEvent(String eventPayload){ && kafkaEvent.getNewNode().has(STATUS)){ EventData eventData = new EventData(); eventData.setRequestId(kafkaEvent.getNewNode().get("id").asText()); + eventData.setDcbReRequestCancellation(getNodeAsBoolean(kafkaEvent, "dcbReRequestCancellation")); RequestStatus requestStatus = RequestStatus.from(kafkaEvent.getNewNode().get(STATUS).asText()); switch (requestStatus) { case OPEN_IN_TRANSIT -> eventData.setType(EventData.EventType.IN_TRANSIT); @@ -69,6 +70,11 @@ public static EventData parseRequestEvent(String eventPayload){ } return null; } + + private static boolean getNodeAsBoolean(KafkaEvent kafkaEvent, String name) { + return kafkaEvent.getNewNode().get(name).asBoolean(); + } + private static boolean checkDcbRequest(KafkaEvent kafkaEvent) { return (kafkaEvent.getNewNode().has(INSTANCE) && kafkaEvent.getNewNode().get(INSTANCE).has(TITLE) && kafkaEvent.getNewNode().get(INSTANCE).get(TITLE).asText().equals(DCB_INSTANCE_TITLE)) || (kafkaEvent.getNewNode().has(REQUESTER) diff --git a/src/main/resources/swagger.api/dcb_transaction.yaml b/src/main/resources/swagger.api/dcb_transaction.yaml index 5871cfa8..d8b0b562 100644 --- a/src/main/resources/swagger.api/dcb_transaction.yaml +++ b/src/main/resources/swagger.api/dcb_transaction.yaml @@ -24,6 +24,22 @@ paths: $ref: '#/components/responses/Conflict' '500': $ref: '#/components/responses/InternalServerError' + put: + description: Update the details of a transaction + operationId: updateTransactionDetails + tags: + - circulation + parameters: + - $ref: '#/components/parameters/dcbTransactionId' + requestBody: + $ref: "#/components/requestBodies/DcbUpdateTransaction" + responses: + '204': + description: 'Transaction updated successfully' + '404': + $ref: '#/components/responses/NotFound' + '500': + $ref: '#/components/responses/InternalServerError' /transactions/{dcbTransactionId}/status: parameters: - $ref: '#/components/parameters/dcbTransactionId' @@ -110,6 +126,13 @@ components: application/json: schema: $ref: "schemas/dcbTransaction.yaml#/DcbTransaction" + DcbUpdateTransaction: + description: DCB transaction update object + required: true + content: + application/json: + schema: + $ref: "schemas/dcbUpdateTransaction.yaml#/DcbUpdateTransaction" responses: TransactionStatus: description: Transaction Status object diff --git a/src/main/resources/swagger.api/schemas/CirculationRequest.yaml b/src/main/resources/swagger.api/schemas/CirculationRequest.yaml index c45a1fd3..a26f8555 100644 --- a/src/main/resources/swagger.api/schemas/CirculationRequest.yaml +++ b/src/main/resources/swagger.api/schemas/CirculationRequest.yaml @@ -78,6 +78,9 @@ CirculationRequest: pickupServicePointId: description: The ID of the Service Point where this request can be picked up type: string + isDcbReRequestCancellation: + description: Indicates whether the request was cancelled during a DCB transaction update + type: boolean item: $ref: CirculationRequest.yaml#/item requester: diff --git a/src/main/resources/swagger.api/schemas/dcbUpdateItem.yaml b/src/main/resources/swagger.api/schemas/dcbUpdateItem.yaml new file mode 100644 index 00000000..8c6914b1 --- /dev/null +++ b/src/main/resources/swagger.api/schemas/dcbUpdateItem.yaml @@ -0,0 +1,18 @@ +DcbUpdateItem: + description: Item metadata required for updating the existing transaction + type: object + properties: + barcode: + description: The barcode of the item as specified in the lending library + type: string + materialType: + description: The “hub-normalized” form of the item item type, used in the circulation rules for determining the correct loan policy. + type: string + lendingLibraryCode: + description: The code which identifies the lending library + type: string + additionalProperties: false + required: + - barcode + - materialType + - lendingLibraryCode diff --git a/src/main/resources/swagger.api/schemas/dcbUpdateTransaction.yaml b/src/main/resources/swagger.api/schemas/dcbUpdateTransaction.yaml new file mode 100644 index 00000000..c08ed5c1 --- /dev/null +++ b/src/main/resources/swagger.api/schemas/dcbUpdateTransaction.yaml @@ -0,0 +1,6 @@ +DcbUpdateTransaction: + type: object + properties: + item: + $ref: 'dcbUpdateItem.yaml#/DcbUpdateItem' + diff --git a/src/test/java/org/folio/dcb/controller/TransactionApiControllerTest.java b/src/test/java/org/folio/dcb/controller/TransactionApiControllerTest.java index 5edbc7ff..ab8b6dd7 100644 --- a/src/test/java/org/folio/dcb/controller/TransactionApiControllerTest.java +++ b/src/test/java/org/folio/dcb/controller/TransactionApiControllerTest.java @@ -1,15 +1,16 @@ package org.folio.dcb.controller; +import com.jayway.jsonpath.JsonPath; import org.folio.dcb.domain.dto.DcbItem; import org.folio.dcb.domain.dto.TransactionStatus; import org.folio.dcb.domain.entity.TransactionAuditEntity; import org.folio.dcb.repository.TransactionAuditRepository; import org.folio.dcb.repository.TransactionRepository; import org.folio.spring.service.SystemUserScopedExecutionService; -import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.MediaType; +import org.springframework.test.web.servlet.MvcResult; import java.time.OffsetDateTime; import java.time.ZoneOffset; @@ -19,6 +20,7 @@ import static org.folio.dcb.domain.dto.DcbTransaction.RoleEnum.BORROWING_PICKUP; import static org.folio.dcb.domain.dto.DcbTransaction.RoleEnum.LENDER; import static org.folio.dcb.domain.dto.DcbTransaction.RoleEnum.PICKUP; +import static org.folio.dcb.utils.EntityUtils.DCB_NEW_BARCODE; import static org.folio.dcb.utils.EntityUtils.DCB_TRANSACTION_ID; import static org.folio.dcb.utils.EntityUtils.DCB_TYPE_USER_ID; import static org.folio.dcb.utils.EntityUtils.EXISTED_INVENTORY_ITEM_BARCODE; @@ -28,6 +30,7 @@ import static org.folio.dcb.utils.EntityUtils.PATRON_TYPE_USER_ID; import static org.folio.dcb.utils.EntityUtils.createDcbItem; import static org.folio.dcb.utils.EntityUtils.createDcbPatronWithExactPatronId; +import static org.folio.dcb.utils.EntityUtils.createDcbTransactionUpdate; import static org.folio.dcb.utils.EntityUtils.createDefaultDcbPatron; import static org.folio.dcb.utils.EntityUtils.createDcbTransactionByRole; import static org.folio.dcb.utils.EntityUtils.createTransactionEntity; @@ -35,6 +38,9 @@ import static org.hamcrest.Matchers.contains; import static org.hamcrest.Matchers.containsInRelativeOrder; import static org.hamcrest.Matchers.is; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put; @@ -87,9 +93,9 @@ void createLendingCirculationRequestTest() throws Exception { () -> { TransactionAuditEntity auditExisting = transactionAuditRepository.findLatestTransactionAuditEntityByDcbTransactionId(DCB_TRANSACTION_ID) .orElse(null); - Assertions.assertNotNull(auditExisting); - Assertions.assertNotEquals(TRANSACTION_AUDIT_DUPLICATE_ERROR_ACTION, auditExisting.getAction()); - Assertions.assertNotEquals(DUPLICATE_ERROR_TRANSACTION_ID, auditExisting.getTransactionId()); } + assertNotNull(auditExisting); + assertNotEquals(TRANSACTION_AUDIT_DUPLICATE_ERROR_ACTION, auditExisting.getAction()); + assertNotEquals(DUPLICATE_ERROR_TRANSACTION_ID, auditExisting.getTransactionId()); } ); } @@ -162,9 +168,9 @@ void createBorrowingPickupCirculationRequestTest() throws Exception { () -> { TransactionAuditEntity auditExisting = transactionAuditRepository.findLatestTransactionAuditEntityByDcbTransactionId(DCB_TRANSACTION_ID) .orElse(null); - Assertions.assertNotNull(auditExisting); - Assertions.assertNotEquals(TRANSACTION_AUDIT_DUPLICATE_ERROR_ACTION, auditExisting.getAction()); - Assertions.assertNotEquals(DUPLICATE_ERROR_TRANSACTION_ID, auditExisting.getTransactionId()); } + assertNotNull(auditExisting); + assertNotEquals(TRANSACTION_AUDIT_DUPLICATE_ERROR_ACTION, auditExisting.getAction()); + assertNotEquals(DUPLICATE_ERROR_TRANSACTION_ID, auditExisting.getTransactionId()); } ); } @@ -190,8 +196,8 @@ void createLendingCirculationRequestWithInvalidItemId() throws Exception { () -> { TransactionAuditEntity auditExisting = transactionAuditRepository.findLatestTransactionAuditEntityByDcbTransactionId(trnId) .orElse(null); - Assertions.assertNotNull(auditExisting); - Assertions.assertEquals(TRANSACTION_AUDIT_ERROR_ACTION, auditExisting.getAction()); } + assertNotNull(auditExisting); + assertEquals(TRANSACTION_AUDIT_ERROR_ACTION, auditExisting.getAction()); } ); } @@ -216,8 +222,8 @@ void createBorrowingPickupCirculationRequestWithInvalidDefaultNotExistedPatronId () -> { TransactionAuditEntity auditExisting = transactionAuditRepository.findLatestTransactionAuditEntityByDcbTransactionId(trnId) .orElse(null); - Assertions.assertNotNull(auditExisting); - Assertions.assertEquals(TRANSACTION_AUDIT_ERROR_ACTION, auditExisting.getAction()); } + assertNotNull(auditExisting); + assertEquals(TRANSACTION_AUDIT_ERROR_ACTION, auditExisting.getAction()); } ); } @@ -455,9 +461,9 @@ void createTransactionForPickupLibrary() throws Exception { () -> { TransactionAuditEntity auditExisting = transactionAuditRepository.findLatestTransactionAuditEntityByDcbTransactionId(DCB_TRANSACTION_ID) .orElse(null); - Assertions.assertNotNull(auditExisting); - Assertions.assertNotEquals(TRANSACTION_AUDIT_DUPLICATE_ERROR_ACTION, auditExisting.getAction()); - Assertions.assertNotEquals(DUPLICATE_ERROR_TRANSACTION_ID, auditExisting.getTransactionId()); } + assertNotNull(auditExisting); + assertNotEquals(TRANSACTION_AUDIT_DUPLICATE_ERROR_ACTION, auditExisting.getAction()); + assertNotEquals(DUPLICATE_ERROR_TRANSACTION_ID, auditExisting.getTransactionId()); } ); } @@ -513,9 +519,9 @@ void createBorrowerCirculationRequestTest() throws Exception { () -> { TransactionAuditEntity auditExisting = transactionAuditRepository.findLatestTransactionAuditEntityByDcbTransactionId(DCB_TRANSACTION_ID) .orElse(null); - Assertions.assertNotNull(auditExisting); - Assertions.assertNotEquals(TRANSACTION_AUDIT_DUPLICATE_ERROR_ACTION, auditExisting.getAction()); - Assertions.assertNotEquals(DUPLICATE_ERROR_TRANSACTION_ID, auditExisting.getTransactionId()); } + assertNotNull(auditExisting); + assertNotEquals(TRANSACTION_AUDIT_DUPLICATE_ERROR_ACTION, auditExisting.getAction()); + assertNotEquals(DUPLICATE_ERROR_TRANSACTION_ID, auditExisting.getTransactionId()); } ); } @@ -537,7 +543,10 @@ void createBorrowerCirculationRequestWithoutExistingItemTest() throws Exception .accept(MediaType.APPLICATION_JSON)) .andExpect(status().isCreated()) .andExpect(jsonPath("$.status").value("CREATED")) - .andExpect(jsonPath("$.item").value(dcbItem)) + .andExpect(jsonPath("$.item.barcode").value(dcbItem.getBarcode())) + .andExpect(jsonPath("$.item.materialType").value(dcbItem.getMaterialType())) + .andExpect(jsonPath("$.item.lendingLibraryCode").value(dcbItem.getLendingLibraryCode())) + .andExpect(jsonPath("$.item.title").value(dcbItem.getTitle())) .andExpect(jsonPath("$.patron").value(createDcbPatronWithExactPatronId(EXISTED_PATRON_ID))); //Trying to create another transaction with same transaction id @@ -983,9 +992,58 @@ void getTransactionStatusUpdateListTest() throws Exception { .andExpect(jsonPath("$.maximumPageNumber", is(2))) .andExpect(jsonPath("$.transactions[*].status", containsInRelativeOrder("ITEM_CHECKED_OUT", "ITEM_CHECKED_IN"))); + } + @Test + void createAndUpdateBorrowerTransactionTest() throws Exception { + removeExistedTransactionFromDbIfSoExists(); + removeExistingTransactionsByItemId(ITEM_ID); + + MvcResult result = this.mockMvc.perform( + post("/transactions/" + DCB_TRANSACTION_ID) + .content(asJsonString(createDcbTransactionByRole(BORROWER))) + .headers(defaultHeaders()) + .contentType(MediaType.APPLICATION_JSON) + .accept(MediaType.APPLICATION_JSON)) + .andExpect(status().isCreated()) + .andExpect(jsonPath("$.status").value("CREATED")) + .andExpect(jsonPath("$.item").value(createDcbItem())) + .andExpect(jsonPath("$.patron").value(createDcbPatronWithExactPatronId(EXISTED_PATRON_ID))) + .andReturn(); // Capture the response for assertion + + String responseContent = result.getResponse().getContentAsString(); + String itemId = JsonPath.parse(responseContent).read("$.item.id", String.class); + String itemBarcode = JsonPath.parse(responseContent).read("$.item.barcode", String.class); + String lendingLibraryCode = JsonPath.parse(responseContent).read("$.item.lendingLibraryCode", String.class); + String materialType = JsonPath.parse(responseContent).read("$.item.materialType", String.class); + + //Trying to update the transaction with same transaction id + this.mockMvc.perform( + put("/transactions/" + DCB_TRANSACTION_ID) + .content(asJsonString(createDcbTransactionUpdate())) + .headers(defaultHeaders()) + .contentType(MediaType.APPLICATION_JSON) + .accept(MediaType.APPLICATION_JSON)) + .andExpectAll(status().isNoContent()); + + // check whether item related data is updated + systemUserScopedExecutionService.executeAsyncSystemUserScoped( + TENANT, + () -> { + var transactionEntity = transactionRepository.findById(DCB_TRANSACTION_ID) + .orElse(null); + assertNotNull(transactionEntity); + assertNotEquals(itemId, transactionEntity.getItemId()); + assertNotEquals(itemBarcode, transactionEntity.getItemBarcode()); + assertEquals(DCB_NEW_BARCODE, transactionEntity.getItemBarcode()); + assertNotEquals(lendingLibraryCode, transactionEntity.getLendingLibraryCode()); + assertEquals("LEN", transactionEntity.getLendingLibraryCode()); + assertNotEquals(materialType, transactionEntity.getMaterialType()); + assertEquals("DVD", transactionEntity.getMaterialType()); + }); } + private void removeExistedTransactionFromDbIfSoExists() { systemUserScopedExecutionService.executeAsyncSystemUserScoped(TENANT, () -> { if (transactionRepository.existsById(DCB_TRANSACTION_ID)){ diff --git a/src/test/java/org/folio/dcb/listener/CirculationRequestEventListenerTest.java b/src/test/java/org/folio/dcb/listener/CirculationRequestEventListenerTest.java index ba2ac32f..e20d9d43 100644 --- a/src/test/java/org/folio/dcb/listener/CirculationRequestEventListenerTest.java +++ b/src/test/java/org/folio/dcb/listener/CirculationRequestEventListenerTest.java @@ -36,8 +36,8 @@ class CirculationRequestEventListenerTest extends BaseIT { private static final String CHECK_IN_TRANSIT_EVENT_FOR_DCB_SAMPLE = getMockDataAsString("mockdata/kafka/check_in_transit_dcb.json"); private static final String CHECK_IN_UNDEFINED_EVENT_SAMPLE = getMockDataAsString("mockdata/kafka/request_undefined.json"); private static final String REQUEST_CANCEL_EVENT_SAMPLE = getMockDataAsString("mockdata/kafka/cancel_request.json"); - private static final String REQUEST_CANCEL_EVENT_FOR_DCB_SAMPLE = getMockDataAsString("mockdata/kafka/cancel_request_dcb.json"); + private static final String CANCELLATION_DCB_REREQUEST_SAMPLE = getMockDataAsString("mockdata/kafka/cancellation_dcb_rerequest.json"); @Autowired private CirculationEventListener eventListener ; @@ -54,6 +54,17 @@ void handleNonDcbRequestTest() { eventListener.handleRequestEvent(REQUEST_EVENT_SAMPLE_NON_DCB, messageHeaders); Mockito.verify(transactionRepository, times(0)).save(any()); } + + @Test + void handleCancelRequestEventWhenTransactionDcbUpdates() { + var transactionEntity = createTransactionEntity(); + MessageHeaders messageHeaders = getMessageHeaders(); + when(transactionRepository.findTransactionByRequestIdAndStatusNotInClosed(any())).thenReturn(Optional.of(transactionEntity)); + eventListener.handleRequestEvent(CANCELLATION_DCB_REREQUEST_SAMPLE, messageHeaders); + Mockito.verify(transactionRepository, times(0)).save(any()); + } + + @Test void handleCheckInEventInPickupForDcbFromOpenToAwaitingPickupTest() { var transactionEntity = createTransactionEntity(); @@ -104,7 +115,6 @@ void handleCancelRequestForDcbTest() { Mockito.verify(transactionRepository).save(any()); } - @Test void handleOpenRequestTest() { var transactionEntity = createTransactionEntity(); diff --git a/src/test/java/org/folio/dcb/service/BaseLibraryServiceTest.java b/src/test/java/org/folio/dcb/service/BaseLibraryServiceTest.java index 82853ca4..32241206 100644 --- a/src/test/java/org/folio/dcb/service/BaseLibraryServiceTest.java +++ b/src/test/java/org/folio/dcb/service/BaseLibraryServiceTest.java @@ -39,6 +39,7 @@ import static org.junit.jupiter.api.Assertions.assertThrows; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.when; import static org.mockito.Mockito.times; @@ -110,15 +111,18 @@ void createBorrowingTransactionTest() { var patron = createDcbPatronWithExactPatronId(EXISTED_PATRON_ID); var user = createUser(); user.setType("shadow"); + var circulationItem = createCirculationItem(); when(userService.fetchUser(any())) .thenReturn(user); when(requestService.createHoldItemRequest(any(), any(), anyString())).thenReturn(createCirculationRequest()); when(transactionMapper.mapToEntity(any(), any())).thenReturn(createTransactionEntity()); when(itemService.fetchItemByBarcode(item.getBarcode())).thenReturn(new ResultList<>()); - when(circulationItemService.checkIfItemExistsAndCreate(any(), any())).thenReturn(createCirculationItem()); + when(circulationItemService.checkIfItemExistsAndCreate(any(), any())).thenReturn(circulationItem); var response = baseLibraryService.createBorrowingLibraryTransaction(DCB_TRANSACTION_ID, createDcbTransactionByRole(BORROWER), PICKUP_SERVICE_POINT_ID); verify(userService).fetchUser(patron); + // Circulation item id will be set as dcb item id in the code, hence setting it for assertion + item.setId(circulationItem.getId()); verify(requestService).createHoldItemRequest(user, item, PICKUP_SERVICE_POINT_ID); verify(transactionRepository).save(any()); Assertions.assertEquals(TransactionStatusResponse.StatusEnum.CREATED, response.getStatus()); @@ -185,7 +189,7 @@ void testTransactionCancelTest(){ transactionEntity.setStatus(OPEN); TransactionStatus transactionStatus = TransactionStatus.builder().status(CANCELLED).build(); baseLibraryService.updateTransactionStatus(transactionEntity, transactionStatus); - verify(circulationService).cancelRequest(any()); + verify(circulationService).cancelRequest(any(), eq(false)); } @Test diff --git a/src/test/java/org/folio/dcb/service/CirculationServiceTest.java b/src/test/java/org/folio/dcb/service/CirculationServiceTest.java index eb461539..8ec991ae 100644 --- a/src/test/java/org/folio/dcb/service/CirculationServiceTest.java +++ b/src/test/java/org/folio/dcb/service/CirculationServiceTest.java @@ -1,7 +1,18 @@ package org.folio.dcb.service; -import feign.FeignException; +import static org.folio.dcb.utils.EntityUtils.createCirculationRequest; +import static org.folio.dcb.utils.EntityUtils.createTransactionEntity; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.util.UUID; + import org.folio.dcb.client.feign.CirculationClient; +import org.folio.dcb.domain.dto.CirculationRequest; +import org.folio.dcb.domain.entity.TransactionEntity; import org.folio.dcb.exception.CirculationRequestException; import org.folio.dcb.service.impl.CirculationServiceImpl; import org.junit.jupiter.api.Test; @@ -10,15 +21,7 @@ import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; -import java.util.UUID; - -import static org.folio.dcb.utils.EntityUtils.createCirculationRequest; -import static org.folio.dcb.utils.EntityUtils.createTransactionEntity; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; +import feign.FeignException; @ExtendWith(MockitoExtension.class) class CirculationServiceTest { @@ -44,18 +47,32 @@ void checkInByBarcodeWithServicePointTest(){ verify(circulationClient).checkInByBarcode(any()); } + @Test + void shouldUpdateRequestApiWithIsDcbRerequestCancellationTrue() { + CirculationRequest fetchedRequest = createCirculationRequest(); + fetchedRequest.setIsDcbReRequestCancellation(null); + CirculationRequest requestToBeCancelled = createCirculationRequest(); + requestToBeCancelled.setIsDcbReRequestCancellation(true); + when(circulationRequestService.getCancellationRequestIfOpenOrNull(anyString())).thenReturn(fetchedRequest); + circulationService.cancelRequest(createTransactionEntity(), true); + verify(circulationClient).updateRequest(requestToBeCancelled.getId(), requestToBeCancelled); + } + @Test void cancelRequestTest() { when(circulationRequestService.getCancellationRequestIfOpenOrNull(anyString())).thenReturn(createCirculationRequest()); - circulationService.cancelRequest(createTransactionEntity()); + circulationService.cancelRequest(createTransactionEntity(), false); verify(circulationClient).updateRequest(anyString(), any()); } @Test void shouldThrowExceptionWhenRequestIsNotUpdated() { + TransactionEntity transactionEntity = createTransactionEntity(); when(circulationRequestService.getCancellationRequestIfOpenOrNull(anyString())).thenReturn(createCirculationRequest()); when(circulationClient.updateRequest(anyString(), any())).thenThrow(FeignException.BadRequest.class); - assertThrows(CirculationRequestException.class, () -> circulationService.cancelRequest(createTransactionEntity())); + assertThrows(CirculationRequestException.class, () -> { + circulationService.cancelRequest(transactionEntity, false); + }); } } diff --git a/src/test/java/org/folio/dcb/utils/EntityUtils.java b/src/test/java/org/folio/dcb/utils/EntityUtils.java index 9e469962..d8305d82 100644 --- a/src/test/java/org/folio/dcb/utils/EntityUtils.java +++ b/src/test/java/org/folio/dcb/utils/EntityUtils.java @@ -11,6 +11,8 @@ import org.folio.dcb.domain.dto.DcbItem; import org.folio.dcb.domain.dto.DcbPatron; import org.folio.dcb.domain.dto.DcbPickup; +import org.folio.dcb.domain.dto.DcbUpdateTransaction; +import org.folio.dcb.domain.dto.DcbUpdateItem; import org.folio.dcb.domain.dto.TransactionStatusResponse; import org.folio.dcb.domain.dto.User; import org.folio.dcb.domain.dto.TransactionStatus; @@ -61,6 +63,7 @@ public class EntityUtils { public static String EXISTED_INVENTORY_ITEM_BARCODE = "INVENTORY_ITEM"; public static String PATRON_TYPE_USER_ID = "18c1741d-e678-4c8e-9fe7-cfaeefab5eea"; public static String REQUEST_ID = "398501a2-5c97-4ba6-9ee7-d1cd6433cb98"; + public static final String DCB_NEW_BARCODE = "398501a2-5c97-4ba6-9ee7-d1cd6433cb91"; public static DcbTransaction createDcbTransactionByRole(DcbTransaction.RoleEnum role) { return DcbTransaction.builder() @@ -136,6 +139,18 @@ public static DcbItem createDcbItem() { .build(); } + public static DcbUpdateTransaction createDcbTransactionUpdate() { + return DcbUpdateTransaction + .builder() + .item(DcbUpdateItem + .builder() + .barcode(DCB_NEW_BARCODE) + .lendingLibraryCode("LEN") + .materialType("DVD") + .build()) + .build(); + } + public static CirculationRequest createCirculationRequest() { return CirculationRequest.builder() .id(CIRCULATION_REQUEST_ID) diff --git a/src/test/resources/mappings/circulation-item.json b/src/test/resources/mappings/circulation-item.json index d8a08687..860e677e 100644 --- a/src/test/resources/mappings/circulation-item.json +++ b/src/test/resources/mappings/circulation-item.json @@ -3,11 +3,12 @@ { "request": { "method": "POST", - "url": "/circulation-item/5b95877d-86c0-4cb7-a0cd-7660b348ae5a" + "urlPathPattern": "/circulation-item/.*" }, "response": { "status": 201, - "body": "{\"id\": \"5b95877d-86c0-4cb7-a0cd-7660b348ae5a\", \"holdingsRecordId\": \"10cd3a5a-d36f-4c7a-bc4f-e1ae3cf820c9\", \"status\": {\"name\":\"In transit\"}, \"materialTypeId\": \"1a54b431-2e4f-452d-9cae-9cee66c9a892\", \"permanentLoanTypeId\": \"2b94c631-fca9-4892-a730-03ee529ffe27\", \"instanceTitle\": \"ITEM\", \"itemBarcode\": \"DCB_ITEM\", \"pickupLocation\": \"3a40852d-49fd-4df2-a1f9-6e2641a6e91f\"}", + "body": "{{jsonPath request.body '$'}}", + "transformers": ["response-template"], "headers": { "Content-Type": "application/json" } @@ -55,7 +56,7 @@ { "request": { "method": "GET", - "url": "/circulation-item?query=id%3D%3D5b95877d-86c0-4cb7-a0cd-7660b348ae5a%20and%20barcode%3D%3DDCB_ITEM" + "url": "/circulation-item?query=barcode%3D%3D%22DCB_ITEM%22" }, "response": { "status": 200, @@ -68,7 +69,7 @@ { "request": { "method": "GET", - "url": "/circulation-item?query=id%3D%3D5b95877d-86c0-4cb7-a0cd-7660b348ae5a%20and%20barcode%3D%3DnewItem" + "url": "/circulation-item?query=barcode%3D%3D%22newItem%22" }, "response": { "status": 200, @@ -90,6 +91,19 @@ "Content-Type": "application/json" } } + }, + { + "request": { + "method": "GET", + "url": "/circulation-item?query=barcode%3D%3D%22398501a2-5c97-4ba6-9ee7-d1cd6433cb91%22" + }, + "response": { + "status": 200, + "body": "{\n \"totalRecords\": 0,\n \"items\": []\n}", + "headers": { + "Content-Type": "application/json" + } + } } ] } diff --git a/src/test/resources/mappings/circulation.json b/src/test/resources/mappings/circulation.json index 00ec8fc2..72e25d33 100644 --- a/src/test/resources/mappings/circulation.json +++ b/src/test/resources/mappings/circulation.json @@ -6,8 +6,9 @@ "url": "/circulation/requests" }, "response": { - "status": 200, - "body": "{\"id\": \"571b0a2c-8883-40b5-a449-d41fe6017083\"}", + "status": 201, + "body": "{{jsonPath request.body '$'}}", + "transformers": ["response-template"], "headers": { "Content-Type": "application/json" } diff --git a/src/test/resources/mappings/inventory.json b/src/test/resources/mappings/inventory.json index 512502ee..6573a5c6 100644 --- a/src/test/resources/mappings/inventory.json +++ b/src/test/resources/mappings/inventory.json @@ -220,6 +220,19 @@ "Content-Type": "application/json" } } + }, + { + "request": { + "method": "GET", + "url": "/item-storage/items?query=barcode%3D%3D398501a2-5c97-4ba6-9ee7-d1cd6433cb91" + }, + "response": { + "status": 200, + "body": "{\n\"items\": [],\n\"totalRecords\": 0,\n \"resultInfo\": {\n \"totalRecords\": 0,\n \"facets\": [],\n \"diagnostics\": []\n }\n}", + "headers": { + "Content-Type": "application/json" + } + } } ] } diff --git a/src/test/resources/mappings/material-types.json b/src/test/resources/mappings/material-types.json index 366e39c5..51d43f7e 100644 --- a/src/test/resources/mappings/material-types.json +++ b/src/test/resources/mappings/material-types.json @@ -12,6 +12,19 @@ "Content-Type": "application/json" } } + }, + { + "request": { + "method": "GET", + "url": "/material-types?query=name%3D%3D%22DVD%22" + }, + "response": { + "status": 200, + "body": "{\"mtypes\": [{\"name\": \"DVD\",\n \"id\": \"1b54b431-2e4f-452d-9cae-9cee66c9a892\",\n \"source\": \"folio\"}]}", + "headers": { + "Content-Type": "application/json" + } + } } ] } diff --git a/src/test/resources/mockdata/kafka/cancel_request.json b/src/test/resources/mockdata/kafka/cancel_request.json index f45127d8..3e9be1ce 100644 --- a/src/test/resources/mockdata/kafka/cancel_request.json +++ b/src/test/resources/mockdata/kafka/cancel_request.json @@ -17,7 +17,8 @@ "cancellationReasonId": "50ed35b2-1397-4e83-a76b-642adf91ca2a", "cancelledByUserId": "7187c6f3-41ec-5731-b551-7f0092abb4c6", "cancellationAdditionalInformation": "test", - "cancelledDate": "2023-11-06T12:18:36.938+00:00" + "cancelledDate": "2023-11-06T12:18:36.938+00:00", + "dcbReRequestCancellation": false } } } diff --git a/src/test/resources/mockdata/kafka/cancel_request_dcb.json b/src/test/resources/mockdata/kafka/cancel_request_dcb.json index 4924e559..0950f43d 100644 --- a/src/test/resources/mockdata/kafka/cancel_request_dcb.json +++ b/src/test/resources/mockdata/kafka/cancel_request_dcb.json @@ -24,7 +24,8 @@ "requester": { "lastName": "DcbSystem", "barcode": "dcbDemo21" - } + }, + "dcbReRequestCancellation": false } } } diff --git a/src/test/resources/mockdata/kafka/cancellation_dcb_rerequest.json b/src/test/resources/mockdata/kafka/cancellation_dcb_rerequest.json new file mode 100644 index 00000000..d16838b2 --- /dev/null +++ b/src/test/resources/mockdata/kafka/cancellation_dcb_rerequest.json @@ -0,0 +1,24 @@ +{ + "id": "4a8123fe-a0aa-47ae-9836-71a6cc3cb119", + "type": "UPDATED", + "tenant": "diku", + "timestamp": 1695390940007, + "data": { + "new": { + "id": "299b5ad1-baa6-44fe-a855-0236f852c943", + "requestLevel": "Item", + "requestType": "Page", + "requestDate": "2023-11-06T12:17:52.573+00:00", + "requesterId": "2205005b-ca51-4a04-87fd-938eefa8f6de", + "instanceId": "9d1b77e4-f02e-4b7f-b296-3f2042ddac54", + "holdingsRecordId": "10cd3a5a-d36f-4c7a-bc4f-e1ae3cf820c9", + "itemId": "5b95877d-86c0-4cb7-a0cd-7660b348ae5a", + "status": "Closed - Cancelled", + "cancellationReasonId": "50ed35b2-1397-4e83-a76b-642adf91ca2a", + "cancelledByUserId": "7187c6f3-41ec-5731-b551-7f0092abb4c6", + "cancellationAdditionalInformation": "test", + "cancelledDate": "2023-11-06T12:18:36.938+00:00", + "dcbReRequestCancellation": true + } + } +} \ No newline at end of file diff --git a/src/test/resources/mockdata/kafka/check_in_dcb.json b/src/test/resources/mockdata/kafka/check_in_dcb.json index 014cb2fd..28446d6f 100644 --- a/src/test/resources/mockdata/kafka/check_in_dcb.json +++ b/src/test/resources/mockdata/kafka/check_in_dcb.json @@ -25,7 +25,8 @@ "requester": { "lastName": "DcbSystem", "barcode": "user2" - } + }, + "dcbReRequestCancellation": false } } } diff --git a/src/test/resources/mockdata/kafka/check_in_transit.json b/src/test/resources/mockdata/kafka/check_in_transit.json index 7f48d141..9af72b41 100644 --- a/src/test/resources/mockdata/kafka/check_in_transit.json +++ b/src/test/resources/mockdata/kafka/check_in_transit.json @@ -13,7 +13,8 @@ "instanceId": "9d1b77e4-f02e-4b7f-b296-3f2042ddac54", "holdingsRecordId": "10cd3a5a-d36f-4c7a-bc4f-e1ae3cf820c9", "itemId": "5d2625ef-81eb-4e61-a8a9-87c94ba3764e", - "status": "Open - In transit" + "status": "Open - In transit", + "dcbReRequestCancellation": false } } } diff --git a/src/test/resources/mockdata/kafka/check_in_transit_dcb.json b/src/test/resources/mockdata/kafka/check_in_transit_dcb.json index 8ea68aba..48377923 100644 --- a/src/test/resources/mockdata/kafka/check_in_transit_dcb.json +++ b/src/test/resources/mockdata/kafka/check_in_transit_dcb.json @@ -24,7 +24,8 @@ "requester": { "lastName": "DcbSystem", "barcode": "systemDemoUser1" - } + }, + "dcbReRequestCancellation": false } } } diff --git a/src/test/resources/mockdata/kafka/request_sample.json b/src/test/resources/mockdata/kafka/request_sample.json index 7c9f3f9f..9002dc06 100644 --- a/src/test/resources/mockdata/kafka/request_sample.json +++ b/src/test/resources/mockdata/kafka/request_sample.json @@ -97,7 +97,8 @@ }, "shelvingOrder": "K1 .M44 v.72:no.6-7,10-12 1986:July-Aug.,Oct.-Dec.", "pickupServicePointName": "Circ Desk 1" - } + }, + "dcbReRequestCancellation": false } } } diff --git a/src/test/resources/mockdata/kafka/request_undefined.json b/src/test/resources/mockdata/kafka/request_undefined.json index 1854bdb2..a46099d9 100644 --- a/src/test/resources/mockdata/kafka/request_undefined.json +++ b/src/test/resources/mockdata/kafka/request_undefined.json @@ -13,7 +13,8 @@ "instanceId": "9d1b77e4-f02e-4b7f-b296-3f2042ddac54", "holdingsRecordId": "10cd3a5a-d36f-4c7a-bc4f-e1ae3cf820c9", "itemId": "5d2625ef-81eb-4e61-a8a9-87c94ba3764e", - "status": "aaa" + "status": "aaa", + "dcbReRequestCancellation": false } } } From fcd5fd671023a003401f63f1ae44c8b80ab93648 Mon Sep 17 00:00:00 2001 From: Gurleen Kaur <34331959+gurleenkaurbp@users.noreply.github.com> Date: Thu, 26 Dec 2024 15:56:07 +0530 Subject: [PATCH 2/6] UXPROD-5054 - Support DCB Requests on Unavailable Items (#123) UXPROD-5054 - Support DCB Requests on Unavailable Items --- descriptors/ModuleDescriptor-template.json | 14 +++--- .../dcb/repository/TransactionRepository.java | 2 +- .../org/folio/dcb/service/RequestService.java | 4 +- .../impl/LendingLibraryServiceImpl.java | 2 +- .../dcb/service/impl/RequestServiceImpl.java | 23 +++++++-- .../org/folio/dcb/utils/DCBConstants.java | 11 +++++ .../service/LendingLibraryServiceTest.java | 4 +- .../folio/dcb/service/RequestServiceTest.java | 49 +++++++++++++++++-- .../java/org/folio/dcb/utils/EntityUtils.java | 5 ++ 9 files changed, 94 insertions(+), 20 deletions(-) diff --git a/descriptors/ModuleDescriptor-template.json b/descriptors/ModuleDescriptor-template.json index 4393f8d4..e348f28a 100644 --- a/descriptors/ModuleDescriptor-template.json +++ b/descriptors/ModuleDescriptor-template.json @@ -113,7 +113,7 @@ ], "pathPattern": "/transactions/{dcbTransactionId}/status", "permissionsRequired": [ - "dcb.transactions.get" + "dcb.transactions.status.get" ], "modulePermissions": [] }, @@ -123,7 +123,7 @@ ], "pathPattern": "/transactions/{dcbTransactionId}/status", "permissionsRequired": [ - "dcb.transactions.put" + "dcb.transactions.status.put" ], "modulePermissions": [ "circulation.check-out-by-barcode.post", @@ -253,8 +253,8 @@ "description": "All permissions for dcb module", "subPermissions": [ "dcb.transactions.post", - "dcb.transactions.put", - "dcb.transactions.get", + "dcb.transactions.status.put", + "dcb.transactions.status.get", "dcb.transactions.collection.get", "dcb.ecs-request.transactions.post", "dcb.transactions.item.put" @@ -266,12 +266,12 @@ "description": "creates new transaction" }, { - "permissionName": "dcb.transactions.get", + "permissionName": "dcb.transactions.status.get", "displayName": "get transaction details", "description": "get transaction details" }, { - "permissionName": "dcb.transactions.put", + "permissionName": "dcb.transactions.status.put", "displayName": "update transaction status", "description": "update transaction status" }, @@ -358,4 +358,4 @@ { "name": "ENV", "value": "folio" } ] } -} +} \ No newline at end of file diff --git a/src/main/java/org/folio/dcb/repository/TransactionRepository.java b/src/main/java/org/folio/dcb/repository/TransactionRepository.java index f56f68e5..71060239 100644 --- a/src/main/java/org/folio/dcb/repository/TransactionRepository.java +++ b/src/main/java/org/folio/dcb/repository/TransactionRepository.java @@ -12,7 +12,7 @@ @Repository public interface TransactionRepository extends JpaRepository { - @Query(value = "SELECT * FROM transactions where item_id = :itemId AND status not in ('CLOSED', 'CANCELLED', 'ERROR')", nativeQuery = true) + @Query(value = "SELECT * FROM transactions where item_id = :itemId AND status not in ('CLOSED', 'CANCELLED', 'ERROR', 'CREATED', 'OPEN')", nativeQuery = true) Optional findTransactionByItemIdAndStatusNotInClosed(@Param("itemId") UUID itemId); @Query(value = "SELECT * FROM transactions where item_id = :itemId AND status not in ('CLOSED', 'CANCELLED', 'ERROR')", nativeQuery = true) diff --git a/src/main/java/org/folio/dcb/service/RequestService.java b/src/main/java/org/folio/dcb/service/RequestService.java index 49358cb8..8f73bc79 100644 --- a/src/main/java/org/folio/dcb/service/RequestService.java +++ b/src/main/java/org/folio/dcb/service/RequestService.java @@ -6,11 +6,11 @@ public interface RequestService { /** - * Create page item request + * Create request based on status of the item * @param user - userEntity * @param dcbItem - dcbItemEntity */ - CirculationRequest createPageItemRequest(User user, DcbItem dcbItem, String pickupServicePointId); + CirculationRequest createRequestBasedOnItemStatus(User user, DcbItem dcbItem, String pickupServicePointId); CirculationRequest createHoldItemRequest(User user, DcbItem dcbItem, String pickupServicePointId); void updateCirculationRequest(CirculationRequest circulationRequest); } diff --git a/src/main/java/org/folio/dcb/service/impl/LendingLibraryServiceImpl.java b/src/main/java/org/folio/dcb/service/impl/LendingLibraryServiceImpl.java index de65af3e..538bf473 100644 --- a/src/main/java/org/folio/dcb/service/impl/LendingLibraryServiceImpl.java +++ b/src/main/java/org/folio/dcb/service/impl/LendingLibraryServiceImpl.java @@ -47,7 +47,7 @@ public TransactionStatusResponse createCirculation(String dcbTransactionId, DcbT baseLibraryService.checkUserTypeAndThrowIfMismatch(user.getType()); ServicePointRequest pickupServicePoint = servicePointService.createServicePointIfNotExists(dcbTransaction.getPickup()); dcbTransaction.getPickup().setServicePointId(pickupServicePoint.getId()); - CirculationRequest pageRequest = requestService.createPageItemRequest(user, item, pickupServicePoint.getId()); + CirculationRequest pageRequest = requestService.createRequestBasedOnItemStatus(user, item, pickupServicePoint.getId()); baseLibraryService.saveDcbTransaction(dcbTransactionId, dcbTransaction, pageRequest.getId()); return TransactionStatusResponse.builder() diff --git a/src/main/java/org/folio/dcb/service/impl/RequestServiceImpl.java b/src/main/java/org/folio/dcb/service/impl/RequestServiceImpl.java index f8fe764f..7957a403 100644 --- a/src/main/java/org/folio/dcb/service/impl/RequestServiceImpl.java +++ b/src/main/java/org/folio/dcb/service/impl/RequestServiceImpl.java @@ -2,8 +2,10 @@ import static org.folio.dcb.domain.dto.CirculationRequest.RequestTypeEnum.PAGE; import static org.folio.dcb.domain.dto.CirculationRequest.RequestTypeEnum.HOLD; +import static org.folio.dcb.domain.dto.ItemStatus.NameEnum.AVAILABLE; import static org.folio.dcb.utils.DCBConstants.HOLDING_ID; import static org.folio.dcb.utils.DCBConstants.INSTANCE_ID; +import static org.folio.dcb.utils.DCBConstants.holdItemStatus; import lombok.RequiredArgsConstructor; import lombok.extern.log4j.Log4j2; @@ -13,6 +15,7 @@ import org.folio.dcb.domain.dto.Item; import org.folio.dcb.domain.dto.Requester; import org.folio.dcb.domain.dto.User; +import org.folio.dcb.exception.StatusException; import org.folio.dcb.service.HoldingsService; import org.folio.dcb.service.ItemService; import org.folio.dcb.service.RequestService; @@ -31,13 +34,25 @@ public class RequestServiceImpl implements RequestService { private final CirculationClient circulationClient; @Override - public CirculationRequest createPageItemRequest(User user, DcbItem item, String pickupServicePointId) { - log.debug("createPageItemRequest:: creating a new page request for userBarcode {} , itemBarcode {}", + public CirculationRequest createRequestBasedOnItemStatus(User user, DcbItem item, String pickupServicePointId) { + log.debug("createRequestBasedOnItemStatus:: creating a new request for userBarcode {} , itemBarcode {}", user.getBarcode(), item.getBarcode()); var inventoryItem = itemService.fetchItemByIdAndBarcode(item.getId(), item.getBarcode()); var inventoryHolding = holdingsService.fetchInventoryHoldingDetailsByHoldingId(inventoryItem.getHoldingsRecordId()); - var circulationRequest = createCirculationRequest(PAGE, user, item, inventoryItem.getHoldingsRecordId(), inventoryHolding.getInstanceId(), pickupServicePointId); - return circulationClient.createRequest(circulationRequest); + var inventoryItemStatus = inventoryItem.getStatus().getName(); + if (inventoryItemStatus.equals(AVAILABLE)) { + log.info("createRequestBasedOnItemStatus:: Creating page request for item with barcode {}", item.getBarcode()); + var circulationRequest = createCirculationRequest(PAGE, user, item, inventoryItem.getHoldingsRecordId(), inventoryHolding.getInstanceId(), pickupServicePointId); + return circulationClient.createRequest(circulationRequest); + } else if (holdItemStatus.contains(inventoryItemStatus)) { + log.info("createRequestBasedOnItemStatus:: Creating hold request for item with barcode {}", item.getBarcode()); + var circulationRequest = createCirculationRequest(HOLD, user, item, inventoryItem.getHoldingsRecordId(), inventoryHolding.getInstanceId(), pickupServicePointId); + return circulationClient.createRequest(circulationRequest); + } else { + String errorMsg = String.format("Request will not be created for item barcode %s as it is %s", item.getBarcode(), inventoryItemStatus); + log.error(errorMsg); + throw new StatusException(errorMsg); + } } @Override diff --git a/src/main/java/org/folio/dcb/utils/DCBConstants.java b/src/main/java/org/folio/dcb/utils/DCBConstants.java index 12bca85e..acf871b1 100644 --- a/src/main/java/org/folio/dcb/utils/DCBConstants.java +++ b/src/main/java/org/folio/dcb/utils/DCBConstants.java @@ -1,5 +1,15 @@ package org.folio.dcb.utils; +import org.folio.dcb.domain.dto.ItemStatus; + +import java.util.List; + +import static org.folio.dcb.domain.dto.ItemStatus.NameEnum.AWAITING_DELIVERY; +import static org.folio.dcb.domain.dto.ItemStatus.NameEnum.AWAITING_PICKUP; +import static org.folio.dcb.domain.dto.ItemStatus.NameEnum.CHECKED_OUT; +import static org.folio.dcb.domain.dto.ItemStatus.NameEnum.IN_TRANSIT; +import static org.folio.dcb.domain.dto.ItemStatus.NameEnum.PAGED; + public class DCBConstants { private DCBConstants() {} @@ -26,4 +36,5 @@ private DCBConstants() {} public static final String SHADOW_TYPE = "shadow"; public static final String HOLDING_SOURCE = "folio"; public static final String DCB_CALENDAR_NAME = "DCB Calendar"; + public static final List holdItemStatus = List.of(IN_TRANSIT, CHECKED_OUT, PAGED, AWAITING_PICKUP, AWAITING_DELIVERY); } diff --git a/src/test/java/org/folio/dcb/service/LendingLibraryServiceTest.java b/src/test/java/org/folio/dcb/service/LendingLibraryServiceTest.java index 2b55800f..cf2990a7 100644 --- a/src/test/java/org/folio/dcb/service/LendingLibraryServiceTest.java +++ b/src/test/java/org/folio/dcb/service/LendingLibraryServiceTest.java @@ -69,12 +69,12 @@ void createTransactionTest() { when(userService.fetchOrCreateUser(any())) .thenReturn(user); when(servicePointService.createServicePointIfNotExists(dcbPickup)).thenReturn(servicePoint); - when(requestService.createPageItemRequest(any(), any(), anyString())).thenReturn(createCirculationRequest()); + when(requestService.createRequestBasedOnItemStatus(any(), any(), anyString())).thenReturn(createCirculationRequest()); doNothing().when(baseLibraryService).saveDcbTransaction(any(), any(), any()); var response = lendingLibraryService.createCirculation(DCB_TRANSACTION_ID, dcbTransaction); verify(userService).fetchOrCreateUser(patron); - verify(requestService).createPageItemRequest(user, item, dcbTransaction.getPickup().getServicePointId()); + verify(requestService).createRequestBasedOnItemStatus(user, item, dcbTransaction.getPickup().getServicePointId()); verify(baseLibraryService).saveDcbTransaction(DCB_TRANSACTION_ID, dcbTransaction, CIRCULATION_REQUEST_ID); Assertions.assertEquals(TransactionStatusResponse.StatusEnum.CREATED, response.getStatus()); assertEquals(servicePoint.getId(), dcbTransaction.getPickup().getServicePointId()); diff --git a/src/test/java/org/folio/dcb/service/RequestServiceTest.java b/src/test/java/org/folio/dcb/service/RequestServiceTest.java index dc6511a8..df9e1bae 100644 --- a/src/test/java/org/folio/dcb/service/RequestServiceTest.java +++ b/src/test/java/org/folio/dcb/service/RequestServiceTest.java @@ -1,11 +1,17 @@ package org.folio.dcb.service; import org.folio.dcb.client.feign.CirculationClient; +import org.folio.dcb.domain.dto.CirculationRequest; +import org.folio.dcb.domain.dto.ItemStatus; +import org.folio.dcb.exception.StatusException; import org.folio.dcb.service.impl.HoldingsServiceImpl; import org.folio.dcb.service.impl.ItemServiceImpl; import org.folio.dcb.service.impl.RequestServiceImpl; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; +import org.mockito.ArgumentCaptor; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; @@ -15,6 +21,9 @@ import static org.folio.dcb.utils.EntityUtils.createInventoryHolding; import static org.folio.dcb.utils.EntityUtils.createInventoryItem; import static org.folio.dcb.utils.EntityUtils.createUser; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.ArgumentCaptor.forClass; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -32,13 +41,47 @@ class RequestServiceTest { private CirculationClient circulationClient; @Test - void createPageItemRequestTest() { + void createItemRequestWithPageStatusTest() { when(itemService.fetchItemByIdAndBarcode(any(), any())).thenReturn(createInventoryItem()); when(holdingsService.fetchInventoryHoldingDetailsByHoldingId(any())).thenReturn(createInventoryHolding()); - requestService.createPageItemRequest(createUser(), createDcbItem(), createDcbPickup().getServicePointId()); + requestService.createRequestBasedOnItemStatus(createUser(), createDcbItem(), createDcbPickup().getServicePointId()); verify(itemService).fetchItemByIdAndBarcode(any(), any()); verify(holdingsService).fetchInventoryHoldingDetailsByHoldingId(any()); - verify(circulationClient).createRequest(any()); + ArgumentCaptor requestTypeCaptor = forClass(CirculationRequest.class); + verify(circulationClient).createRequest(requestTypeCaptor.capture()); + var capturedRequest = requestTypeCaptor.getValue(); + assertEquals(CirculationRequest.RequestTypeEnum.PAGE, capturedRequest.getRequestType()); + } + + @ParameterizedTest + @ValueSource(strings = {"Awaiting delivery", "Paged", "In transit", "Checked out", "Awaiting pickup"}) + void createItemRequestWithHoldStatusTest(String holdStatus) { + var inventoryItem = createInventoryItem(); + inventoryItem.setStatus(inventoryItem.getStatus().name(ItemStatus.NameEnum.fromValue(holdStatus))); + when(itemService.fetchItemByIdAndBarcode(any(), any())).thenReturn(inventoryItem); + when(holdingsService.fetchInventoryHoldingDetailsByHoldingId(any())).thenReturn(createInventoryHolding()); + requestService.createRequestBasedOnItemStatus(createUser(), createDcbItem(), createDcbPickup().getServicePointId()); + verify(itemService).fetchItemByIdAndBarcode(any(), any()); + verify(holdingsService).fetchInventoryHoldingDetailsByHoldingId(any()); + ArgumentCaptor requestTypeCaptor = forClass(CirculationRequest.class); + verify(circulationClient).createRequest(requestTypeCaptor.capture()); + var capturedRequest = requestTypeCaptor.getValue(); + assertEquals(CirculationRequest.RequestTypeEnum.HOLD, capturedRequest.getRequestType()); + } + + @ParameterizedTest + @ValueSource(strings = {"Aged to lost", "Claimed returned", "Declared lost", "Lost and paid", + "Long missing", "Missing", "In process (non-requestable)", "Intellectual item", "On order", + "Order closed", "Restricted", "Unavailable", "Unknown", "Withdrawn"}) + void createItemRequestWithInvalidStatusTest(String invalidStatus) { + var inventoryItem = createInventoryItem(); + inventoryItem.setStatus(inventoryItem.getStatus().name(ItemStatus.NameEnum.fromValue(invalidStatus))); + var user = createUser(); + var item = createDcbItem(); + var pickupServicePointId = createDcbPickup().getServicePointId(); + when(itemService.fetchItemByIdAndBarcode(any(), any())).thenReturn(inventoryItem); + when(holdingsService.fetchInventoryHoldingDetailsByHoldingId(any())).thenReturn(createInventoryHolding()); + assertThrows(StatusException.class, () -> requestService.createRequestBasedOnItemStatus(user, item, pickupServicePointId)); } } diff --git a/src/test/java/org/folio/dcb/utils/EntityUtils.java b/src/test/java/org/folio/dcb/utils/EntityUtils.java index d8305d82..b867f75f 100644 --- a/src/test/java/org/folio/dcb/utils/EntityUtils.java +++ b/src/test/java/org/folio/dcb/utils/EntityUtils.java @@ -13,6 +13,7 @@ import org.folio.dcb.domain.dto.DcbPickup; import org.folio.dcb.domain.dto.DcbUpdateTransaction; import org.folio.dcb.domain.dto.DcbUpdateItem; +import org.folio.dcb.domain.dto.ItemStatus; import org.folio.dcb.domain.dto.TransactionStatusResponse; import org.folio.dcb.domain.dto.User; import org.folio.dcb.domain.dto.TransactionStatus; @@ -252,6 +253,10 @@ public static InventoryItem createInventoryItem() { .id(UUID.randomUUID().toString()) .holdingsRecordId(UUID.randomUUID().toString()) .barcode("DCB_ITEM") + .status(ItemStatus + .builder() + .name(ItemStatus.NameEnum.AVAILABLE) + .build()) .build(); } From a562a439a46433345e49506d5cc5ee99469cc6ef Mon Sep 17 00:00:00 2001 From: Antony Hruschev Date: Thu, 26 Dec 2024 14:33:10 +0400 Subject: [PATCH 3/6] UXPROD 5001 Support DCB re-requests (#131) UXPROD 5001 Support DCB re-requests --- descriptors/ModuleDescriptor-template.json | 6 ++-- .../dcb/service/impl/BaseLibraryService.java | 2 ++ .../folio/dcb/utils/TransactionHelper.java | 15 ++++++++-- .../CirculationRequestEventListenerTest.java | 30 +++++++++++++++---- .../cancellation_dcb_rerequest_false.json | 24 +++++++++++++++ ...n => cancellation_dcb_rerequest_true.json} | 0 ...equest_without_dcb_rerequest_property.json | 23 ++++++++++++++ 7 files changed, 89 insertions(+), 11 deletions(-) create mode 100644 src/test/resources/mockdata/kafka/cancellation_dcb_rerequest_false.json rename src/test/resources/mockdata/kafka/{cancellation_dcb_rerequest.json => cancellation_dcb_rerequest_true.json} (100%) create mode 100644 src/test/resources/mockdata/kafka/cancellation_dcb_rerequest_without_dcb_rerequest_property.json diff --git a/descriptors/ModuleDescriptor-template.json b/descriptors/ModuleDescriptor-template.json index e348f28a..357accfd 100644 --- a/descriptors/ModuleDescriptor-template.json +++ b/descriptors/ModuleDescriptor-template.json @@ -268,12 +268,14 @@ { "permissionName": "dcb.transactions.status.get", "displayName": "get transaction details", - "description": "get transaction details" + "description": "get transaction details", + "replaces": ["dcb.transactions.get"] }, { "permissionName": "dcb.transactions.status.put", "displayName": "update transaction status", - "description": "update transaction status" + "description": "update transaction status", + "replaces": ["dcb.transactions.put"] }, { "permissionName": "dcb.transactions.collection.get", diff --git a/src/main/java/org/folio/dcb/service/impl/BaseLibraryService.java b/src/main/java/org/folio/dcb/service/impl/BaseLibraryService.java index f0f5ee05..bb0e42e9 100644 --- a/src/main/java/org/folio/dcb/service/impl/BaseLibraryService.java +++ b/src/main/java/org/folio/dcb/service/impl/BaseLibraryService.java @@ -112,6 +112,8 @@ public void cancelTransactionRequest(TransactionEntity transactionEntity){ try { circulationService.cancelRequest(transactionEntity, false); } catch (CirculationRequestException e) { + log.error("cancelTransactionRequest:: error during updating circulation request " + + "to cancel status", e); updateTransactionEntity(transactionEntity, TransactionStatus.StatusEnum.ERROR); } } diff --git a/src/main/java/org/folio/dcb/utils/TransactionHelper.java b/src/main/java/org/folio/dcb/utils/TransactionHelper.java index 93888ed4..02e29dfd 100644 --- a/src/main/java/org/folio/dcb/utils/TransactionHelper.java +++ b/src/main/java/org/folio/dcb/utils/TransactionHelper.java @@ -7,10 +7,13 @@ import java.nio.charset.StandardCharsets; import java.util.Collections; import java.util.List; +import java.util.Objects; import static org.folio.dcb.utils.KafkaEvent.ACTION; import static org.folio.dcb.utils.KafkaEvent.STATUS; +import com.fasterxml.jackson.databind.JsonNode; + @Log4j2 public class TransactionHelper { private static final String LOAN_ACTION_CHECKED_OUT = "checkedout"; @@ -57,7 +60,8 @@ public static EventData parseRequestEvent(String eventPayload){ && kafkaEvent.getNewNode().has(STATUS)){ EventData eventData = new EventData(); eventData.setRequestId(kafkaEvent.getNewNode().get("id").asText()); - eventData.setDcbReRequestCancellation(getNodeAsBoolean(kafkaEvent, "dcbReRequestCancellation")); + eventData.setDcbReRequestCancellation( + getNodeAsBooleanOrDefault(kafkaEvent, "dcbReRequestCancellation", false)); RequestStatus requestStatus = RequestStatus.from(kafkaEvent.getNewNode().get(STATUS).asText()); switch (requestStatus) { case OPEN_IN_TRANSIT -> eventData.setType(EventData.EventType.IN_TRANSIT); @@ -71,8 +75,13 @@ public static EventData parseRequestEvent(String eventPayload){ return null; } - private static boolean getNodeAsBoolean(KafkaEvent kafkaEvent, String name) { - return kafkaEvent.getNewNode().get(name).asBoolean(); + private static boolean getNodeAsBooleanOrDefault(KafkaEvent kafkaEvent, String name, + boolean defaultValue) { + + JsonNode booleanNode = kafkaEvent.getNewNode().get(name); + return Objects.nonNull(booleanNode) + ? booleanNode.asBoolean() + : defaultValue; } private static boolean checkDcbRequest(KafkaEvent kafkaEvent) { diff --git a/src/test/java/org/folio/dcb/listener/CirculationRequestEventListenerTest.java b/src/test/java/org/folio/dcb/listener/CirculationRequestEventListenerTest.java index e20d9d43..500bad1f 100644 --- a/src/test/java/org/folio/dcb/listener/CirculationRequestEventListenerTest.java +++ b/src/test/java/org/folio/dcb/listener/CirculationRequestEventListenerTest.java @@ -8,6 +8,9 @@ import org.folio.dcb.service.CirculationItemService; import org.folio.spring.integration.XOkapiHeaders; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; import org.mockito.Mockito; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; @@ -17,6 +20,7 @@ import java.util.HashMap; import java.util.Map; import java.util.Optional; +import java.util.stream.Stream; import static org.folio.dcb.domain.dto.DcbTransaction.RoleEnum.*; import static org.folio.dcb.utils.EntityUtils.createCirculationItem; @@ -37,7 +41,12 @@ class CirculationRequestEventListenerTest extends BaseIT { private static final String CHECK_IN_UNDEFINED_EVENT_SAMPLE = getMockDataAsString("mockdata/kafka/request_undefined.json"); private static final String REQUEST_CANCEL_EVENT_SAMPLE = getMockDataAsString("mockdata/kafka/cancel_request.json"); private static final String REQUEST_CANCEL_EVENT_FOR_DCB_SAMPLE = getMockDataAsString("mockdata/kafka/cancel_request_dcb.json"); - private static final String CANCELLATION_DCB_REREQUEST_SAMPLE = getMockDataAsString("mockdata/kafka/cancellation_dcb_rerequest.json"); + private static final String CANCELLATION_DCB_REREQUEST_TRUE_SAMPLE = getMockDataAsString( + "mockdata/kafka/cancellation_dcb_rerequest_true.json"); + private static final String CANCELLATION_DCB_REREQUEST_FALSE_SAMPLE = getMockDataAsString( + "mockdata/kafka/cancellation_dcb_rerequest_false.json"); + private static final String CANCELLATION_DCB_REREQUEST_WITHOUT_SAMPLE = getMockDataAsString( + "mockdata/kafka/cancellation_dcb_rerequest_without_dcb_rerequest_property.json"); @Autowired private CirculationEventListener eventListener ; @@ -55,15 +64,24 @@ void handleNonDcbRequestTest() { Mockito.verify(transactionRepository, times(0)).save(any()); } - @Test - void handleCancelRequestEventWhenTransactionDcbUpdates() { + @ParameterizedTest + @MethodSource("pathToExecutionTimes") + void handleCancelRequestEventWhenTransactionDcbUpdates(String path, int executionTimes) { var transactionEntity = createTransactionEntity(); MessageHeaders messageHeaders = getMessageHeaders(); - when(transactionRepository.findTransactionByRequestIdAndStatusNotInClosed(any())).thenReturn(Optional.of(transactionEntity)); - eventListener.handleRequestEvent(CANCELLATION_DCB_REREQUEST_SAMPLE, messageHeaders); - Mockito.verify(transactionRepository, times(0)).save(any()); + when(transactionRepository.findTransactionByRequestIdAndStatusNotInClosed(any())) + .thenReturn(Optional.of(transactionEntity)); + eventListener.handleRequestEvent(path, messageHeaders); + Mockito.verify(transactionRepository, times(executionTimes)).save(any()); } + private static Stream pathToExecutionTimes() { + return Stream.of( + Arguments.of(CANCELLATION_DCB_REREQUEST_FALSE_SAMPLE, 1), + Arguments.of(CANCELLATION_DCB_REREQUEST_TRUE_SAMPLE, 0), + Arguments.of(CANCELLATION_DCB_REREQUEST_WITHOUT_SAMPLE, 1) + ); + } @Test void handleCheckInEventInPickupForDcbFromOpenToAwaitingPickupTest() { diff --git a/src/test/resources/mockdata/kafka/cancellation_dcb_rerequest_false.json b/src/test/resources/mockdata/kafka/cancellation_dcb_rerequest_false.json new file mode 100644 index 00000000..a680b137 --- /dev/null +++ b/src/test/resources/mockdata/kafka/cancellation_dcb_rerequest_false.json @@ -0,0 +1,24 @@ +{ + "id": "4a8123fe-a0aa-47ae-9836-71a6cc3cb119", + "type": "UPDATED", + "tenant": "diku", + "timestamp": 1695390940007, + "data": { + "new": { + "id": "299b5ad1-baa6-44fe-a855-0236f852c943", + "requestLevel": "Item", + "requestType": "Page", + "requestDate": "2023-11-06T12:17:52.573+00:00", + "requesterId": "2205005b-ca51-4a04-87fd-938eefa8f6de", + "instanceId": "9d1b77e4-f02e-4b7f-b296-3f2042ddac54", + "holdingsRecordId": "10cd3a5a-d36f-4c7a-bc4f-e1ae3cf820c9", + "itemId": "5b95877d-86c0-4cb7-a0cd-7660b348ae5a", + "status": "Closed - Cancelled", + "cancellationReasonId": "50ed35b2-1397-4e83-a76b-642adf91ca2a", + "cancelledByUserId": "7187c6f3-41ec-5731-b551-7f0092abb4c6", + "cancellationAdditionalInformation": "test", + "cancelledDate": "2023-11-06T12:18:36.938+00:00", + "dcbReRequestCancellation": false + } + } +} \ No newline at end of file diff --git a/src/test/resources/mockdata/kafka/cancellation_dcb_rerequest.json b/src/test/resources/mockdata/kafka/cancellation_dcb_rerequest_true.json similarity index 100% rename from src/test/resources/mockdata/kafka/cancellation_dcb_rerequest.json rename to src/test/resources/mockdata/kafka/cancellation_dcb_rerequest_true.json diff --git a/src/test/resources/mockdata/kafka/cancellation_dcb_rerequest_without_dcb_rerequest_property.json b/src/test/resources/mockdata/kafka/cancellation_dcb_rerequest_without_dcb_rerequest_property.json new file mode 100644 index 00000000..cea96935 --- /dev/null +++ b/src/test/resources/mockdata/kafka/cancellation_dcb_rerequest_without_dcb_rerequest_property.json @@ -0,0 +1,23 @@ +{ + "id": "4a8123fe-a0aa-47ae-9836-71a6cc3cb119", + "type": "UPDATED", + "tenant": "diku", + "timestamp": 1695390940007, + "data": { + "new": { + "id": "299b5ad1-baa6-44fe-a855-0236f852c943", + "requestLevel": "Item", + "requestType": "Page", + "requestDate": "2023-11-06T12:17:52.573+00:00", + "requesterId": "2205005b-ca51-4a04-87fd-938eefa8f6de", + "instanceId": "9d1b77e4-f02e-4b7f-b296-3f2042ddac54", + "holdingsRecordId": "10cd3a5a-d36f-4c7a-bc4f-e1ae3cf820c9", + "itemId": "5b95877d-86c0-4cb7-a0cd-7660b348ae5a", + "status": "Closed - Cancelled", + "cancellationReasonId": "50ed35b2-1397-4e83-a76b-642adf91ca2a", + "cancelledByUserId": "7187c6f3-41ec-5731-b551-7f0092abb4c6", + "cancellationAdditionalInformation": "test", + "cancelledDate": "2023-11-06T12:18:36.938+00:00" + } + } +} \ No newline at end of file From 4c6f9d4d5ddc26ed0a83b45e3cc5fa8c4682c7d7 Mon Sep 17 00:00:00 2001 From: gurleenkaurbp Date: Fri, 17 Jan 2025 14:36:44 +0530 Subject: [PATCH 4/6] Update NEWS.md --- NEWS.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/NEWS.md b/NEWS.md index dff8aade..e433ed56 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,3 +1,7 @@ +## v1.2.5 2025-01-17 +* MODDCB-142: [DCB Re-Requests] Update transaction API +* MODDCB-154: Support DCB Requests on Unavailable Items + ## v1.2.4 2024-12-12 * MODDCB-152: Support for intermediate requests From 2813cbed895d50aa72a59880b1f139535ace0a80 Mon Sep 17 00:00:00 2001 From: gurleenkaurbp Date: Fri, 17 Jan 2025 14:37:34 +0530 Subject: [PATCH 5/6] [maven-release-plugin] prepare release v1.2.5 --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index fdd41d90..253e365a 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ org.folio mod-dcb mod-dcb - 1.2.5-SNAPSHOT + 1.2.5 Manage DCB related transactions in folio jar @@ -405,7 +405,7 @@ https://github.com/folio-org/${project.artifactId} scm:git:git://github.com/folio-org/${project.artifactId}.git scm:git:git@github.com:folio-org/${project.artifactId}.git - HEAD + v1.2.5 From b82f1d85fc4e00330a31c97848f1ded3007d6f20 Mon Sep 17 00:00:00 2001 From: gurleenkaurbp Date: Fri, 17 Jan 2025 14:37:34 +0530 Subject: [PATCH 6/6] [maven-release-plugin] prepare for next development iteration --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 253e365a..c1d98765 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ org.folio mod-dcb mod-dcb - 1.2.5 + 1.2.6-SNAPSHOT Manage DCB related transactions in folio jar @@ -405,7 +405,7 @@ https://github.com/folio-org/${project.artifactId} scm:git:git://github.com/folio-org/${project.artifactId}.git scm:git:git@github.com:folio-org/${project.artifactId}.git - v1.2.5 + HEAD