diff --git a/descriptors/ModuleDescriptor-template.json b/descriptors/ModuleDescriptor-template.json index aa7fa99e..cb5e4150 100644 --- a/descriptors/ModuleDescriptor-template.json +++ b/descriptors/ModuleDescriptor-template.json @@ -88,10 +88,14 @@ "inventory-storage.items.item.get", "inventory-storage.holdings.item.get", "circulation.requests.item.post", + "circulation.requests.item.put", "circulation-item-storage.items.item.post", "circulation-item-storage.items.item.get", "inventory-storage.material-types.collection.get", - "inventory-storage.loan-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" ] }, { @@ -154,7 +158,9 @@ "inventory-storage.location-units.libraries.collection.get", "inventory-storage.loan-types.collection.get", "inventory-storage.loan-types.collection.post", - "inventory-storage.loan-types.item.post" + "inventory-storage.loan-types.item.post", + "circulation-storage.cancellation-reasons.item.get", + "circulation-storage.cancellation-reasons.item.post" ] }, { diff --git a/src/main/java/org/folio/dcb/client/feign/CancellationReasonClient.java b/src/main/java/org/folio/dcb/client/feign/CancellationReasonClient.java new file mode 100644 index 00000000..6d6da885 --- /dev/null +++ b/src/main/java/org/folio/dcb/client/feign/CancellationReasonClient.java @@ -0,0 +1,34 @@ +package org.folio.dcb.client.feign; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; +import org.folio.spring.config.FeignClientConfiguration; +import org.springframework.cloud.openfeign.FeignClient; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; + +@FeignClient(name = "cancellation-reason-storage", configuration = FeignClientConfiguration.class) +public interface CancellationReasonClient { + + @GetMapping("/cancellation-reasons/{cancellationReasonId}") + CancellationReason findCancellationReason(@PathVariable("cancellationReasonId") String cancellationReasonId); + + @PostMapping("/cancellation-reasons") + void createCancellationReason(@RequestBody CancellationReason cancellationReason); + + @Data + @Builder + @NoArgsConstructor + @AllArgsConstructor + @JsonIgnoreProperties(ignoreUnknown = true) + class CancellationReason { + private String id; + private String name; + private String description; + } +} diff --git a/src/main/java/org/folio/dcb/client/feign/CirculationClient.java b/src/main/java/org/folio/dcb/client/feign/CirculationClient.java index cc257637..40340069 100644 --- a/src/main/java/org/folio/dcb/client/feign/CirculationClient.java +++ b/src/main/java/org/folio/dcb/client/feign/CirculationClient.java @@ -5,7 +5,9 @@ import org.folio.dcb.domain.dto.CirculationRequest; import org.folio.spring.config.FeignClientConfiguration; import org.springframework.cloud.openfeign.FeignClient; +import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; import org.springframework.web.bind.annotation.RequestBody; @FeignClient(name = "circulation", configuration = FeignClientConfiguration.class) @@ -18,4 +20,7 @@ public interface CirculationClient { @PostMapping("/check-out-by-barcode") void checkOutByBarcode(@RequestBody CheckOutRequest checkOutRequest); + + @PutMapping("/requests/{requestId}") + CirculationRequest cancelRequest(@PathVariable("requestId") String requestId, @RequestBody CirculationRequest circulationRequest); } diff --git a/src/main/java/org/folio/dcb/client/feign/CirculationRequestClient.java b/src/main/java/org/folio/dcb/client/feign/CirculationRequestClient.java new file mode 100644 index 00000000..e5b8554f --- /dev/null +++ b/src/main/java/org/folio/dcb/client/feign/CirculationRequestClient.java @@ -0,0 +1,14 @@ +package org.folio.dcb.client.feign; + +import org.folio.dcb.domain.dto.CirculationRequest; +import org.folio.spring.config.FeignClientConfiguration; +import org.springframework.cloud.openfeign.FeignClient; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; + +@FeignClient(name = "request-storage", configuration = FeignClientConfiguration.class) +public interface CirculationRequestClient { + @GetMapping("/requests/{circulationItemId}") + CirculationRequest fetchRequestById(@PathVariable("circulationItemId") String circulationItemId); + +} diff --git a/src/main/java/org/folio/dcb/domain/entity/TransactionEntity.java b/src/main/java/org/folio/dcb/domain/entity/TransactionEntity.java index 14967d5b..f0a979d2 100644 --- a/src/main/java/org/folio/dcb/domain/entity/TransactionEntity.java +++ b/src/main/java/org/folio/dcb/domain/entity/TransactionEntity.java @@ -19,6 +19,8 @@ import org.folio.dcb.domain.dto.TransactionStatus.StatusEnum; import org.folio.dcb.listener.entity.TransactionAuditEntityListener; +import java.util.UUID; + @Entity @Table(name = "transactions") @EntityListeners(TransactionAuditEntityListener.class) @@ -46,6 +48,7 @@ public class TransactionEntity extends AuditableEntity { private String patronGroup; private String patronBarcode; private String borrowingLibraryCode; + private UUID requestId; @Enumerated(EnumType.STRING) private StatusEnum status; @Enumerated(EnumType.STRING) diff --git a/src/main/java/org/folio/dcb/exception/CirculationRequestException.java b/src/main/java/org/folio/dcb/exception/CirculationRequestException.java new file mode 100644 index 00000000..7b352c49 --- /dev/null +++ b/src/main/java/org/folio/dcb/exception/CirculationRequestException.java @@ -0,0 +1,9 @@ +package org.folio.dcb.exception; + +public class CirculationRequestException extends RuntimeException { + + public CirculationRequestException(String errorMsg) { + super(errorMsg); + } + +} diff --git a/src/main/java/org/folio/dcb/listener/kafka/CirculationCheckInEventListener.java b/src/main/java/org/folio/dcb/listener/kafka/CirculationCheckInEventListener.java deleted file mode 100644 index e69de29b..00000000 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 a95f4c3a..3ed4b3ef 100644 --- a/src/main/java/org/folio/dcb/listener/kafka/CirculationEventListener.java +++ b/src/main/java/org/folio/dcb/listener/kafka/CirculationEventListener.java @@ -4,6 +4,7 @@ import lombok.extern.log4j.Log4j2; import org.folio.dcb.repository.TransactionRepository; import org.folio.dcb.service.LibraryService; +import org.folio.dcb.service.impl.BaseLibraryService; import org.folio.spring.integration.XOkapiHeaders; import org.folio.spring.service.SystemUserScopedExecutionService; import org.springframework.beans.factory.annotation.Qualifier; @@ -23,6 +24,8 @@ public class CirculationEventListener { public static final String CHECK_IN_LISTENER_ID = "mod-dcb-check-in-listener-id"; public static final String CHECK_OUT_LOAN_LISTENER_ID = "mod-dcb-loan-listener-id"; + public static final String REQUEST_LISTENER_ID = "mod-dcb-request-listener-id"; + @Qualifier("lendingLibraryService") private final LibraryService lendingLibraryService; @Qualifier("borrowingPickupLibraryService") @@ -31,6 +34,7 @@ public class CirculationEventListener { private final LibraryService pickupLibraryService; private final TransactionRepository transactionRepository; private final SystemUserScopedExecutionService systemUserScopedExecutionService; + private final BaseLibraryService baseLibraryService; @KafkaListener( id = CHECK_IN_LISTENER_ID, @@ -82,4 +86,24 @@ public void handleCheckOutEvent(String data, MessageHeaders messageHeaders) { } } } + + @KafkaListener( + id = REQUEST_LISTENER_ID, + topicPattern = "#{folioKafkaProperties.listener['request'].topicPattern}", + concurrency = "#{folioKafkaProperties.listener['request'].concurrency}") + public void handleRequestCancelEvent(String data, MessageHeaders messageHeaders) { + String tenantId = getHeaderValue(messageHeaders, XOkapiHeaders.TENANT, null).get(0); + var eventData = parseEvent(data); + if (Objects.nonNull(eventData) && eventData.getType() == EventData.EventType.CANCEL) { + String itemID = eventData.getItemId(); + if (Objects.nonNull(itemID)) { + log.info("updateTransactionStatus:: Received cancel event for itemId: {}", itemID); + systemUserScopedExecutionService.executeAsyncSystemUserScoped(tenantId, () -> + transactionRepository.findTransactionByItemIdAndStatusNotInClosed(UUID.fromString(itemID)) + .ifPresent(baseLibraryService::cancelTransactionEntity) + ); + } + } + } + } 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 d2a6034c..0daba60d 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,6 @@ public class EventData { private String itemId; public enum EventType { - CHECK_IN, CHECK_OUT + CHECK_IN, CHECK_OUT, CANCEL } } diff --git a/src/main/java/org/folio/dcb/listener/kafka/service/KafkaService.java b/src/main/java/org/folio/dcb/listener/kafka/service/KafkaService.java index f311dc23..d450ed4c 100644 --- a/src/main/java/org/folio/dcb/listener/kafka/service/KafkaService.java +++ b/src/main/java/org/folio/dcb/listener/kafka/service/KafkaService.java @@ -7,6 +7,7 @@ import static org.folio.dcb.listener.kafka.CirculationEventListener.CHECK_IN_LISTENER_ID; import static org.folio.dcb.listener.kafka.CirculationEventListener.CHECK_OUT_LOAN_LISTENER_ID; +import static org.folio.dcb.listener.kafka.CirculationEventListener.REQUEST_LISTENER_ID; @Component @Log4j2 @@ -19,6 +20,7 @@ public class KafkaService { public void restartEventListeners() { restartEventListener(CHECK_OUT_LOAN_LISTENER_ID); restartEventListener(CHECK_IN_LISTENER_ID); + restartEventListener(REQUEST_LISTENER_ID); } private void restartEventListener(String listenerId) { diff --git a/src/main/java/org/folio/dcb/service/CirculationRequestService.java b/src/main/java/org/folio/dcb/service/CirculationRequestService.java new file mode 100644 index 00000000..d4624e78 --- /dev/null +++ b/src/main/java/org/folio/dcb/service/CirculationRequestService.java @@ -0,0 +1,7 @@ +package org.folio.dcb.service; + +import org.folio.dcb.domain.dto.CirculationRequest; + +public interface CirculationRequestService { + CirculationRequest getCancellationRequestIfOpenOrNull(String requestId); +} diff --git a/src/main/java/org/folio/dcb/service/CirculationService.java b/src/main/java/org/folio/dcb/service/CirculationService.java index b14d8e94..319c7e20 100644 --- a/src/main/java/org/folio/dcb/service/CirculationService.java +++ b/src/main/java/org/folio/dcb/service/CirculationService.java @@ -6,7 +6,6 @@ public interface CirculationService { /** * Check in item by barcode * @param dcbTransaction dcbTransactionEntity - * @return */ void checkInByBarcode(TransactionEntity dcbTransaction); @@ -15,8 +14,8 @@ public interface CirculationService { /** * Check out item by barcode * @param dcbTransaction dcbTransactionEntity - * @return */ void checkOutByBarcode(TransactionEntity dcbTransaction); + void cancelRequest(TransactionEntity dcbTransaction); } diff --git a/src/main/java/org/folio/dcb/service/RequestService.java b/src/main/java/org/folio/dcb/service/RequestService.java index 787d4a2c..0ac6842f 100644 --- a/src/main/java/org/folio/dcb/service/RequestService.java +++ b/src/main/java/org/folio/dcb/service/RequestService.java @@ -1,5 +1,6 @@ package org.folio.dcb.service; +import org.folio.dcb.domain.dto.CirculationRequest; import org.folio.dcb.domain.dto.DcbItem; import org.folio.dcb.domain.dto.User; @@ -9,6 +10,6 @@ public interface RequestService { * @param user - userEntity * @param dcbItem - dcbItemEntity */ - void createPageItemRequest(User user, DcbItem dcbItem, String pickupServicePointId); - void createHoldItemRequest(User user, DcbItem dcbItem, String pickupServicePointId); + CirculationRequest createPageItemRequest(User user, DcbItem dcbItem, String pickupServicePointId); + CirculationRequest createHoldItemRequest(User user, DcbItem dcbItem, String pickupServicePointId); } 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 4ef53df3..608f10ce 100644 --- a/src/main/java/org/folio/dcb/service/impl/BaseLibraryService.java +++ b/src/main/java/org/folio/dcb/service/impl/BaseLibraryService.java @@ -3,11 +3,13 @@ import lombok.RequiredArgsConstructor; import lombok.extern.log4j.Log4j2; import org.folio.dcb.domain.dto.CirculationItemRequest; +import org.folio.dcb.domain.dto.CirculationRequest; import org.folio.dcb.domain.dto.DcbTransaction; import org.folio.dcb.domain.dto.TransactionStatus; import org.folio.dcb.domain.dto.TransactionStatusResponse; import org.folio.dcb.domain.entity.TransactionEntity; import org.folio.dcb.domain.mapper.TransactionMapper; +import org.folio.dcb.exception.CirculationRequestException; import org.folio.dcb.repository.TransactionRepository; import org.folio.dcb.service.CirculationItemService; import org.folio.dcb.service.CirculationService; @@ -20,6 +22,7 @@ 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.TransactionStatus.StatusEnum.CANCELLED; import static org.folio.dcb.domain.dto.TransactionStatus.StatusEnum.CLOSED; import static org.folio.dcb.domain.dto.TransactionStatus.StatusEnum.CREATED; import static org.folio.dcb.domain.dto.TransactionStatus.StatusEnum.ITEM_CHECKED_IN; @@ -47,8 +50,8 @@ public TransactionStatusResponse createBorrowingLibraryTransaction(String dcbTra var user = userService.fetchUser(patron); //user is needed, but shouldn't be generated. it should be fetched. circulationItemService.checkIfItemExistsAndCreate(itemVirtual, pickupServicePointId); - requestService.createHoldItemRequest(user, itemVirtual, pickupServicePointId); - saveDcbTransaction(dcbTransactionId, dcbTransaction); + CirculationRequest holdRequest = requestService.createHoldItemRequest(user, itemVirtual, pickupServicePointId); + saveDcbTransaction(dcbTransactionId, dcbTransaction, holdRequest.getId()); return TransactionStatusResponse.builder() .status(TransactionStatusResponse.StatusEnum.CREATED) @@ -57,11 +60,12 @@ public TransactionStatusResponse createBorrowingLibraryTransaction(String dcbTra .build(); } - public void saveDcbTransaction(String dcbTransactionId, DcbTransaction dcbTransaction) { + public void saveDcbTransaction(String dcbTransactionId, DcbTransaction dcbTransaction, String requestId) { TransactionEntity transactionEntity = transactionMapper.mapToEntity(dcbTransactionId, dcbTransaction); if (Objects.isNull(transactionEntity)) { throw new IllegalArgumentException("Transaction Entity is null"); } + transactionEntity.setRequestId(UUID.fromString(requestId)); transactionEntity.setStatus(CREATED); transactionRepository.save(transactionEntity); } @@ -102,6 +106,9 @@ public void updateTransactionStatus(TransactionEntity dcbTransaction, Transactio log.info("updateTransactionStatus:: transaction status transition from {} to {} for the item with barcode {} ", ITEM_CHECKED_IN.getValue(), CLOSED.getValue(), dcbTransaction.getItemBarcode()); updateTransactionEntity(dcbTransaction, requestedStatus); + } else if(CANCELLED == requestedStatus) { + log.info("updateTransactionStatus:: Cancelling transaction with id: {} for Borrower/Pickup role", dcbTransaction.getId()); + cancelTransactionRequest(dcbTransaction); } else { String errorMessage = String.format("updateTransactionStatus:: status update from %s to %s is not implemented", currentStatus, requestedStatus); log.warn(errorMessage); @@ -109,6 +116,19 @@ public void updateTransactionStatus(TransactionEntity dcbTransaction, Transactio } } + public void cancelTransactionRequest(TransactionEntity transactionEntity){ + try { + circulationService.cancelRequest(transactionEntity); + } catch (CirculationRequestException e) { + updateTransactionEntity(transactionEntity, TransactionStatus.StatusEnum.ERROR); + } + } + + public void cancelTransactionEntity(TransactionEntity transactionEntity) { + log.info("cancelTransactionEntity:: Transaction cancelled for itemId: {}", transactionEntity.getItemId()); + updateTransactionEntity(transactionEntity, CANCELLED); + } + private void updateTransactionEntity(TransactionEntity transactionEntity, TransactionStatus.StatusEnum transactionStatusEnum) { log.debug("updateTransactionEntity:: updating transaction entity from {} to {}", transactionEntity.getStatus(), transactionStatusEnum); transactionEntity.setStatus(transactionStatusEnum); diff --git a/src/main/java/org/folio/dcb/service/impl/BorrowingLibraryServiceImpl.java b/src/main/java/org/folio/dcb/service/impl/BorrowingLibraryServiceImpl.java index 553ea94f..a08540d5 100644 --- a/src/main/java/org/folio/dcb/service/impl/BorrowingLibraryServiceImpl.java +++ b/src/main/java/org/folio/dcb/service/impl/BorrowingLibraryServiceImpl.java @@ -18,6 +18,7 @@ import static org.folio.dcb.domain.dto.TransactionStatus.StatusEnum.ITEM_CHECKED_IN; import static org.folio.dcb.domain.dto.TransactionStatus.StatusEnum.ITEM_CHECKED_OUT; import static org.folio.dcb.domain.dto.TransactionStatus.StatusEnum.OPEN; +import static org.folio.dcb.domain.dto.TransactionStatus.StatusEnum.CANCELLED; @Log4j2 @Service("borrowingLibraryService") @@ -35,7 +36,6 @@ public TransactionStatusResponse createCirculation(String dcbTransactionId, DcbT @Override public void updateStatusByTransactionEntity(TransactionEntity transactionEntity) { - } @Override @@ -60,6 +60,9 @@ public void updateTransactionStatus(TransactionEntity dcbTransaction, Transactio log.info("updateTransactionStatus:: transaction status transition from {} to {} for the item with barcode {} ", ITEM_CHECKED_IN.getValue(), CLOSED.getValue(), dcbTransaction.getItemBarcode()); updateTransactionEntity(dcbTransaction, requestedStatus); + } else if(CANCELLED == requestedStatus) { + log.info("updateTransactionStatus:: Cancelling transaction with id: {} for Borrower role", dcbTransaction.getId()); + libraryService.cancelTransactionRequest(dcbTransaction); } else { String error = String.format("updateTransactionStatus:: status update from %s to %s is not implemented", currentStatus, requestedStatus); log.warn(error); diff --git a/src/main/java/org/folio/dcb/service/impl/CirculationRequestServiceImpl.java b/src/main/java/org/folio/dcb/service/impl/CirculationRequestServiceImpl.java new file mode 100644 index 00000000..64700355 --- /dev/null +++ b/src/main/java/org/folio/dcb/service/impl/CirculationRequestServiceImpl.java @@ -0,0 +1,48 @@ +package org.folio.dcb.service.impl; + +import feign.FeignException; +import lombok.RequiredArgsConstructor; +import lombok.extern.log4j.Log4j2; +import org.folio.dcb.client.feign.CirculationRequestClient; +import org.folio.dcb.domain.dto.CirculationRequest; +import org.folio.dcb.service.CirculationRequestService; +import org.folio.dcb.utils.DCBConstants; +import org.folio.dcb.utils.RequestStatus; +import org.folio.spring.FolioExecutionContext; +import org.springframework.stereotype.Service; + +import java.time.OffsetDateTime; +import java.util.UUID; + +@Service +@Log4j2 +@RequiredArgsConstructor +public class CirculationRequestServiceImpl implements CirculationRequestService { + + private final CirculationRequestClient circulationRequestClient; + private final FolioExecutionContext folioExecutionContext; + + private CirculationRequest fetchRequestById(String requestId) { + log.info("fetchRequestById:: fetching request for id {} ", requestId); + try { + return circulationRequestClient.fetchRequestById(requestId); + } catch (FeignException.NotFound e) { + log.warn("Circulation request not found by id={}", requestId); + return null; + } + } + + @Override + public CirculationRequest getCancellationRequestIfOpenOrNull(String requestId){ + CirculationRequest request = fetchRequestById(requestId); + if(request != null && RequestStatus.isRequestOpen(RequestStatus.from(request.getStatus()))){ + request.setStatus(RequestStatus.CLOSED_CANCELLED.getValue()); + request.setCancelledDate(OffsetDateTime.now().toString()); + request.setCancellationAdditionalInformation("Request cancelled by DCB"); + request.setCancelledByUserId(folioExecutionContext.getUserId()); + request.setCancellationReasonId(UUID.fromString(DCBConstants.CANCELLATION_REASON_ID)); + return request; + } + return null; + } +} 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 06ce6502..9c88acd2 100644 --- a/src/main/java/org/folio/dcb/service/impl/CirculationServiceImpl.java +++ b/src/main/java/org/folio/dcb/service/impl/CirculationServiceImpl.java @@ -1,21 +1,26 @@ package org.folio.dcb.service.impl; +import feign.FeignException; import lombok.RequiredArgsConstructor; import lombok.extern.log4j.Log4j2; import org.folio.dcb.client.feign.CirculationClient; import org.folio.dcb.domain.dto.CheckInRequest; import org.folio.dcb.domain.dto.CheckOutRequest; +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.CirculationService; +import org.folio.dcb.service.CirculationRequestService; import org.springframework.stereotype.Service; - import java.time.OffsetDateTime; @Service @Log4j2 @RequiredArgsConstructor public class CirculationServiceImpl implements CirculationService { + private final CirculationClient circulationClient; + private final CirculationRequestService circulationStorageService; @Override public void checkInByBarcode(TransactionEntity dcbTransaction) { @@ -35,6 +40,20 @@ public void checkOutByBarcode(TransactionEntity dcbTransaction) { circulationClient.checkOutByBarcode(createCheckOutRequest(dcbTransaction.getItemBarcode(), dcbTransaction.getPatronBarcode(), dcbTransaction.getServicePointId())); } + @Override + public void cancelRequest(TransactionEntity dcbTransaction) { + log.debug("cancelRequest:: cancelling request using request id {} ", dcbTransaction.getRequestId()); + CirculationRequest request = circulationStorageService.getCancellationRequestIfOpenOrNull(dcbTransaction.getRequestId().toString()); + if (request != null){ + try { + circulationClient.cancelRequest(request.getId(), request); + } catch (FeignException e) { + log.warn("cancelRequest:: error cancelling request using request id {} ", dcbTransaction.getRequestId(), e); + throw new CirculationRequestException(String.format("Error cancelling request using request id %s", dcbTransaction.getRequestId())); + } + } + } + private CheckInRequest createCheckInRequest(String itemBarcode, String servicePointId){ return CheckInRequest.builder() .itemBarcode(itemBarcode) diff --git a/src/main/java/org/folio/dcb/service/impl/CustomTenantService.java b/src/main/java/org/folio/dcb/service/impl/CustomTenantService.java index fe0c82fb..15cb3b2d 100644 --- a/src/main/java/org/folio/dcb/service/impl/CustomTenantService.java +++ b/src/main/java/org/folio/dcb/service/impl/CustomTenantService.java @@ -2,6 +2,7 @@ import feign.FeignException; import lombok.extern.log4j.Log4j2; +import org.folio.dcb.client.feign.CancellationReasonClient; import org.folio.dcb.client.feign.HoldingSourcesClient; import org.folio.dcb.client.feign.HoldingsStorageClient; import org.folio.dcb.client.feign.InstanceClient; @@ -27,7 +28,9 @@ import static org.folio.dcb.service.impl.ServicePointServiceImpl.HOLD_SHELF_CLOSED_LIBRARY_DATE_MANAGEMENT; import static org.folio.dcb.utils.DCBConstants.CAMPUS_ID; +import static org.folio.dcb.utils.DCBConstants.CANCELLATION_REASON_ID; import static org.folio.dcb.utils.DCBConstants.CODE; +import static org.folio.dcb.utils.DCBConstants.DCB_CANCELLATION_REASON_NAME; 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.INSTANCE_TITLE; @@ -56,11 +59,12 @@ public class CustomTenantService extends TenantService { private final HoldingSourcesClient holdingSourcesClient; private final InventoryServicePointClient servicePointClient; private final LocationUnitClient locationUnitClient; + private final CancellationReasonClient cancellationReasonClient; private final LoanTypeClient loanTypeClient; public CustomTenantService(JdbcTemplate jdbcTemplate, FolioExecutionContext context, - FolioSpringLiquibase folioSpringLiquibase, PrepareSystemUserService systemUserService, KafkaService kafkaService, InstanceClient inventoryClient, InstanceTypeClient instanceTypeClient, HoldingsStorageClient holdingsStorageClient, LocationsClient locationsClient, HoldingSourcesClient holdingSourcesClient, InventoryServicePointClient servicePointClient, LocationUnitClient locationUnitClient, LoanTypeClient loanTypeClient) { + FolioSpringLiquibase folioSpringLiquibase, PrepareSystemUserService systemUserService, KafkaService kafkaService, InstanceClient inventoryClient, InstanceTypeClient instanceTypeClient, HoldingsStorageClient holdingsStorageClient, LocationsClient locationsClient, HoldingSourcesClient holdingSourcesClient, InventoryServicePointClient servicePointClient, LocationUnitClient locationUnitClient, LoanTypeClient loanTypeClient, CancellationReasonClient cancellationReasonClient) { super(jdbcTemplate, context, folioSpringLiquibase); this.systemUserService = systemUserService; @@ -73,6 +77,7 @@ public CustomTenantService(JdbcTemplate jdbcTemplate, FolioExecutionContext cont this.servicePointClient = servicePointClient; this.locationUnitClient = locationUnitClient; this.loanTypeClient = loanTypeClient; + this.cancellationReasonClient = cancellationReasonClient; } @Override @@ -88,6 +93,7 @@ protected void afterTenantUpdate(TenantAttributes tenantAttributes) { createServicePoint(); createLocation(); createHolding(); + createCancellationReason(); createLoanType(); } @@ -235,4 +241,17 @@ private void createHolding() { log.info("createHolding:: holding created"); } } + + private void createCancellationReason(){ + try { + cancellationReasonClient.findCancellationReason(CANCELLATION_REASON_ID); + } catch (FeignException.NotFound ex) { + log.debug("createCancellationReason:: creating cancellation reason"); + cancellationReasonClient.createCancellationReason(CancellationReasonClient.CancellationReason.builder() + .id(CANCELLATION_REASON_ID) + .description(DCB_CANCELLATION_REASON_NAME) + .name(DCB_CANCELLATION_REASON_NAME).build()); + log.info("createCancellationReason:: cancellation reason created"); + } + } } 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 530bcf6c..7e627ed9 100644 --- a/src/main/java/org/folio/dcb/service/impl/LendingLibraryServiceImpl.java +++ b/src/main/java/org/folio/dcb/service/impl/LendingLibraryServiceImpl.java @@ -2,6 +2,7 @@ import lombok.RequiredArgsConstructor; import lombok.extern.log4j.Log4j2; +import org.folio.dcb.domain.dto.CirculationRequest; import org.folio.dcb.domain.dto.DcbTransaction; import org.folio.dcb.domain.dto.TransactionStatus; import org.folio.dcb.domain.dto.TransactionStatusResponse; @@ -19,6 +20,7 @@ import static org.folio.dcb.domain.dto.TransactionStatus.StatusEnum.ITEM_CHECKED_IN; import static org.folio.dcb.domain.dto.TransactionStatus.StatusEnum.ITEM_CHECKED_OUT; import static org.folio.dcb.domain.dto.TransactionStatus.StatusEnum.OPEN; +import static org.folio.dcb.domain.dto.TransactionStatus.StatusEnum.CANCELLED; @Service("lendingLibraryService") @RequiredArgsConstructor @@ -30,7 +32,6 @@ public class LendingLibraryServiceImpl implements LibraryService { private final TransactionRepository transactionRepository; private final CirculationService circulationService; private final BaseLibraryService baseLibraryService; - @Override public TransactionStatusResponse createCirculation(String dcbTransactionId, DcbTransaction dcbTransaction, String pickupServicePointId) { log.debug("createTransaction:: creating a new transaction with dcbTransactionId {} , dcbTransaction {}", @@ -40,8 +41,8 @@ public TransactionStatusResponse createCirculation(String dcbTransactionId, DcbT var patron = dcbTransaction.getPatron(); var user = userService.fetchOrCreateUser(patron); - requestService.createPageItemRequest(user, item, pickupServicePointId); - baseLibraryService.saveDcbTransaction(dcbTransactionId, dcbTransaction); + CirculationRequest pageRequest = requestService.createPageItemRequest(user, item, pickupServicePointId); + baseLibraryService.saveDcbTransaction(dcbTransactionId, dcbTransaction, pageRequest.getId()); return TransactionStatusResponse.builder() .status(TransactionStatusResponse.StatusEnum.CREATED) @@ -65,6 +66,9 @@ public void updateTransactionStatus(TransactionEntity dcbTransaction, Transactio updateTransactionEntity(dcbTransaction, requestedStatus); } else if (ITEM_CHECKED_OUT == currentStatus && ITEM_CHECKED_IN == requestedStatus) { updateTransactionEntity(dcbTransaction, requestedStatus); + } else if(CANCELLED == requestedStatus) { + log.info("updateTransactionStatus:: Cancelling transaction with id: {} for Lender role", dcbTransaction.getId()); + baseLibraryService.cancelTransactionRequest(dcbTransaction); } else { String errorMessage = String.format("updateTransactionStatus:: status update from %s to %s is not implemented", currentStatus, requestedStatus); 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 5b49d8e2..dd07c7ba 100644 --- a/src/main/java/org/folio/dcb/service/impl/PickupLibraryServiceImpl.java +++ b/src/main/java/org/folio/dcb/service/impl/PickupLibraryServiceImpl.java @@ -2,6 +2,7 @@ import lombok.RequiredArgsConstructor; import lombok.extern.log4j.Log4j2; +import org.folio.dcb.domain.dto.CirculationRequest; import org.folio.dcb.domain.dto.DcbTransaction; import org.folio.dcb.domain.dto.TransactionStatus; import org.folio.dcb.domain.dto.TransactionStatusResponse; @@ -30,8 +31,8 @@ public TransactionStatusResponse createCirculation(String dcbTransactionId, DcbT var user = userService.fetchOrCreateUser(patron); circulationItemService.checkIfItemExistsAndCreate(itemVirtual, pickupServicePointId); - requestService.createHoldItemRequest(user, itemVirtual, pickupServicePointId); - baseLibraryService.saveDcbTransaction(dcbTransactionId, dcbTransaction); + CirculationRequest holdRequest = requestService.createHoldItemRequest(user, itemVirtual, pickupServicePointId); + baseLibraryService.saveDcbTransaction(dcbTransactionId, dcbTransaction, holdRequest.getId()); return TransactionStatusResponse.builder() .status(TransactionStatusResponse.StatusEnum.CREATED) 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 df1e9e59..3643aed8 100644 --- a/src/main/java/org/folio/dcb/service/impl/RequestServiceImpl.java +++ b/src/main/java/org/folio/dcb/service/impl/RequestServiceImpl.java @@ -31,21 +31,21 @@ public class RequestServiceImpl implements RequestService { private final CirculationClient circulationClient; @Override - public void createPageItemRequest(User user, DcbItem item, String pickupServicePointId) { + public CirculationRequest createPageItemRequest(User user, DcbItem item, String pickupServicePointId) { log.debug("createPageItemRequest:: creating a new page request for userBarcode {} , itemBarcode {}", user.getBarcode(), item.getBarcode()); var inventoryItem = itemService.fetchItemDetailsById(item.getId()); var inventoryHolding = holdingsService.fetchInventoryHoldingDetailsByHoldingId(inventoryItem.getHoldingsRecordId()); var circulationRequest = createCirculationRequest(PAGE, user, item, inventoryItem.getHoldingsRecordId(), inventoryHolding.getInstanceId(), pickupServicePointId); - circulationClient.createRequest(circulationRequest); + return circulationClient.createRequest(circulationRequest); } @Override - public void createHoldItemRequest(User user, DcbItem item, String pickupServicePointId) { + public CirculationRequest createHoldItemRequest(User user, DcbItem item, String pickupServicePointId) { log.debug("createHoldItemRequest:: creating a new hold request for userBarcode {} , itemBarcode {}", user.getBarcode(), item.getBarcode()); var circulationRequest = createCirculationRequest(HOLD, user, item, HOLDING_ID, INSTANCE_ID, pickupServicePointId); - circulationClient.createRequest(circulationRequest); + return circulationClient.createRequest(circulationRequest); } private CirculationRequest createCirculationRequest(CirculationRequest.RequestTypeEnum type, User user, DcbItem item, String holdingsId, String instanceId, String pickupServicePointId) { diff --git a/src/main/java/org/folio/dcb/utils/DCBConstants.java b/src/main/java/org/folio/dcb/utils/DCBConstants.java index d189ddb6..59f3bd5d 100644 --- a/src/main/java/org/folio/dcb/utils/DCBConstants.java +++ b/src/main/java/org/folio/dcb/utils/DCBConstants.java @@ -12,6 +12,7 @@ private DCBConstants() {} public static final String LOCATION_ID = "9d1b77e8-f02e-4b7f-b296-3f2042ddac54"; public static final String SERVICE_POINT_ID = "9d1b77e8-f02e-4b7f-b296-3f2042ddac54"; public static final String HOLDING_ID = "10cd3a5a-d36f-4c7a-bc4f-e1ae3cf820c9"; + public static final String CANCELLATION_REASON_ID = "50ed35b2-1397-4e83-a76b-642adf91ca2a"; public static final String INSTANCE_TITLE = "DCB_INSTANCE"; public static final String SOURCE = "FOLIO"; public static final String INSTANCE_TYPE_SOURCE = "local"; @@ -20,4 +21,5 @@ private DCBConstants() {} public static final String LOAN_TYPE_ID = "4dec5417-0765-4767-bed6-b363a2d7d4e2"; public static final String DCB_LOAN_TYPE_NAME = "DCB Can circulate"; public static final String MATERIAL_TYPE_NAME_BOOK = "book"; + public static final String DCB_CANCELLATION_REASON_NAME = "DCB Cancelled"; } diff --git a/src/main/java/org/folio/dcb/utils/RequestStatus.java b/src/main/java/org/folio/dcb/utils/RequestStatus.java new file mode 100644 index 00000000..6b503c25 --- /dev/null +++ b/src/main/java/org/folio/dcb/utils/RequestStatus.java @@ -0,0 +1,45 @@ +package org.folio.dcb.utils; + +import java.util.Arrays; +import java.util.EnumSet; + +import static org.apache.commons.lang3.StringUtils.equalsIgnoreCase; + +public enum RequestStatus { + NONE(""), + OPEN_NOT_YET_FILLED("Open - Not yet filled"), + OPEN_AWAITING_PICKUP("Open - Awaiting pickup"), + OPEN_IN_TRANSIT("Open - In transit"), + OPEN_AWAITING_DELIVERY("Open - Awaiting delivery"), + CLOSED_FILLED("Closed - Filled"), + CLOSED_CANCELLED("Closed - Cancelled"), + CLOSED_UNFILLED("Closed - Unfilled"), + CLOSED_PICKUP_EXPIRED("Closed - Pickup expired"); + + private static final EnumSet OPEN_STATUSES = EnumSet.of( + OPEN_NOT_YET_FILLED, OPEN_AWAITING_PICKUP, OPEN_IN_TRANSIT, OPEN_AWAITING_DELIVERY); + + private final String value; + + RequestStatus(String value) { + this.value = value; + } + + public static RequestStatus from(String value) { + return Arrays.stream(values()) + .filter(status -> status.valueMatches(value)) + .findFirst() + .orElse(NONE); + } + + private boolean valueMatches(String value) { + return equalsIgnoreCase(getValue(), value); + } + + public String getValue() { + return value; + } + public static boolean isRequestOpen(RequestStatus currentStatus){ + return OPEN_STATUSES.contains(currentStatus); + } +} diff --git a/src/main/java/org/folio/dcb/utils/TransactionHelper.java b/src/main/java/org/folio/dcb/utils/TransactionHelper.java index 28b94cb9..216803e5 100644 --- a/src/main/java/org/folio/dcb/utils/TransactionHelper.java +++ b/src/main/java/org/folio/dcb/utils/TransactionHelper.java @@ -41,6 +41,12 @@ public static EventData parseEvent(String eventPayload) { eventData.setType(EventData.EventType.CHECK_IN); } + return eventData; + } else if(typeNode.equals("UPDATED") && newDataNode != null && newDataNode.has("status") + && RequestStatus.CLOSED_CANCELLED == RequestStatus.from(newDataNode.get("status").asText())){ + EventData eventData = new EventData(); + eventData.setItemId(newDataNode.get("itemId").asText()); + eventData.setType(EventData.EventType.CANCEL); return eventData; } } catch (Exception e) { diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 4df8bd6d..bb4a0952 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -52,6 +52,10 @@ folio: concurrency: ${KAFKA_EVENTS_CONCURRENCY:5} topic-pattern: ${KAFKA_EVENTS_CONSUMER_PATTERN:(${folio.environment}\.)[a-zA-z0-9-]+\.\w+\.loan} group-id: ${folio.environment}-mod-dcb-group + request: + concurrency: ${KAFKA_EVENTS_CONCURRENCY:5} + topic-pattern: ${KAFKA_EVENTS_CONSUMER_PATTERN:(${folio.environment}\.)[a-zA-z0-9-]+\.\w+\.request} + group-id: ${folio.environment}-mod-dcb-group system-user: username: ${SYSTEM_USER_NAME:dcb-system-user} password: ${SYSTEM_USER_PASSWORD} diff --git a/src/main/resources/db/changelog/changelog-master.xml b/src/main/resources/db/changelog/changelog-master.xml index 1cd7e38b..26ffbe85 100644 --- a/src/main/resources/db/changelog/changelog-master.xml +++ b/src/main/resources/db/changelog/changelog-master.xml @@ -6,4 +6,5 @@ + diff --git a/src/main/resources/db/changelog/changes/add-request-to-transaction.xml b/src/main/resources/db/changelog/changes/add-request-to-transaction.xml new file mode 100644 index 00000000..d07360a7 --- /dev/null +++ b/src/main/resources/db/changelog/changes/add-request-to-transaction.xml @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/db/changelog/changes/create-transaction-table.xml b/src/main/resources/db/changelog/changes/create-transaction-table.xml index f6a0ffab..0919ce79 100644 --- a/src/main/resources/db/changelog/changes/create-transaction-table.xml +++ b/src/main/resources/db/changelog/changes/create-transaction-table.xml @@ -4,7 +4,6 @@ xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.4.xsd"> - eventListener.handleRequestCancelEvent(null, messageHeaders)); + } + + private MessageHeaders getMessageHeaders() { + Map header = new HashMap<>(); + header.put(XOkapiHeaders.TENANT, TENANT.getBytes()); + return new MessageHeaders(header); + } +} diff --git a/src/test/java/org/folio/dcb/service/BaseLibraryServiceTest.java b/src/test/java/org/folio/dcb/service/BaseLibraryServiceTest.java index 91813237..cbe75511 100644 --- a/src/test/java/org/folio/dcb/service/BaseLibraryServiceTest.java +++ b/src/test/java/org/folio/dcb/service/BaseLibraryServiceTest.java @@ -1,3 +1,4 @@ + package org.folio.dcb.service; import org.folio.dcb.domain.dto.CirculationItemRequest; @@ -18,10 +19,14 @@ import static org.folio.dcb.domain.dto.DcbTransaction.RoleEnum.BORROWER; import static org.folio.dcb.domain.dto.DcbTransaction.RoleEnum.BORROWING_PICKUP; +import static org.folio.dcb.domain.dto.TransactionStatus.StatusEnum.CANCELLED; import static org.folio.dcb.domain.dto.TransactionStatus.StatusEnum.CLOSED; +import static org.folio.dcb.domain.dto.TransactionStatus.StatusEnum.OPEN; +import static org.folio.dcb.utils.EntityUtils.CIRCULATION_REQUEST_ID; import static org.folio.dcb.utils.EntityUtils.DCB_TRANSACTION_ID; import static org.folio.dcb.utils.EntityUtils.EXISTED_PATRON_ID; import static org.folio.dcb.utils.EntityUtils.PICKUP_SERVICE_POINT_ID; +import static org.folio.dcb.utils.EntityUtils.createCirculationRequest; import static org.folio.dcb.utils.EntityUtils.createDcbItem; import static org.folio.dcb.utils.EntityUtils.createDcbPatronWithExactPatronId; import static org.folio.dcb.utils.EntityUtils.createDcbTransactionByRole; @@ -30,6 +35,7 @@ import static org.folio.dcb.utils.EntityUtils.createUser; 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.doNothing; import static org.mockito.Mockito.when; import static org.mockito.Mockito.times; @@ -112,7 +118,7 @@ void createBorrowingTransactionTest() { when(userService.fetchUser(any())) .thenReturn(user); - doNothing().when(requestService).createHoldItemRequest(any(), any(), any()); + when(requestService.createHoldItemRequest(any(), any(), anyString())).thenReturn(createCirculationRequest()); when(transactionMapper.mapToEntity(any(), any())).thenReturn(createTransactionEntity()); var response = baseLibraryService.createBorrowingLibraryTransaction(DCB_TRANSACTION_ID, createDcbTransactionByRole(BORROWER), PICKUP_SERVICE_POINT_ID); @@ -122,11 +128,20 @@ void createBorrowingTransactionTest() { Assertions.assertEquals(TransactionStatusResponse.StatusEnum.CREATED, response.getStatus()); } + @Test + void testTransactionCancelTest(){ + var transactionEntity = createTransactionEntity(); + transactionEntity.setStatus(OPEN); + TransactionStatus transactionStatus = TransactionStatus.builder().status(CANCELLED).build(); + baseLibraryService.updateTransactionStatus(transactionEntity, transactionStatus); + verify(circulationService).cancelRequest(any()); + } + @Test void saveTransactionTest() { when(transactionMapper.mapToEntity(any(), any())).thenReturn(createTransactionEntity()); - baseLibraryService.saveDcbTransaction(DCB_TRANSACTION_ID, createDcbTransactionByRole(BORROWER)); + baseLibraryService.saveDcbTransaction(DCB_TRANSACTION_ID, createDcbTransactionByRole(BORROWER), CIRCULATION_REQUEST_ID); verify(transactionRepository).save(any()); } diff --git a/src/test/java/org/folio/dcb/service/CirculationRequestServiceTests.java b/src/test/java/org/folio/dcb/service/CirculationRequestServiceTests.java new file mode 100644 index 00000000..6bffead7 --- /dev/null +++ b/src/test/java/org/folio/dcb/service/CirculationRequestServiceTests.java @@ -0,0 +1,42 @@ +package org.folio.dcb.service; + +import org.folio.dcb.client.feign.CirculationRequestClient; +import org.folio.dcb.domain.dto.CirculationRequest; +import org.folio.dcb.service.impl.CirculationRequestServiceImpl; +import org.folio.dcb.utils.RequestStatus; +import org.folio.spring.FolioExecutionContext; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +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.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +class CirculationRequestServiceTests { + + @InjectMocks + private CirculationRequestServiceImpl circulationRequestService; + + @Mock + private CirculationRequestClient circulationStorageClient; + + @Mock + private FolioExecutionContext folioExecutionContext; + + @Test + void getCancellationRequestIfOpenOrNullTest() { + CirculationRequest openRequest = createCirculationRequest(); + openRequest.setStatus(RequestStatus.OPEN_AWAITING_PICKUP.getValue()); + when(circulationStorageClient.fetchRequestById(anyString())).thenReturn(openRequest); + when(folioExecutionContext.getUserId()).thenReturn(UUID.randomUUID()); + var cancelRequest = circulationRequestService.getCancellationRequestIfOpenOrNull(anyString()); + Assertions.assertEquals(RequestStatus.CLOSED_CANCELLED, RequestStatus.from(cancelRequest.getStatus())); + } +} diff --git a/src/test/java/org/folio/dcb/service/CirculationServiceTest.java b/src/test/java/org/folio/dcb/service/CirculationServiceTest.java index ccf30848..59c2a93f 100644 --- a/src/test/java/org/folio/dcb/service/CirculationServiceTest.java +++ b/src/test/java/org/folio/dcb/service/CirculationServiceTest.java @@ -1,6 +1,8 @@ package org.folio.dcb.service; +import feign.FeignException; import org.folio.dcb.client.feign.CirculationClient; +import org.folio.dcb.exception.CirculationRequestException; import org.folio.dcb.service.impl.CirculationServiceImpl; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -10,9 +12,13 @@ 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; @ExtendWith(MockitoExtension.class) class CirculationServiceTest { @@ -23,6 +29,9 @@ class CirculationServiceTest { @Mock private CirculationClient circulationClient; + @Mock + private CirculationRequestService circulationRequestService; + @Test void checkInByBarcodeTest(){ circulationService.checkInByBarcode(createTransactionEntity()); @@ -35,4 +44,18 @@ void checkInByBarcodeWithServicePointTest(){ verify(circulationClient).checkInByBarcode(any()); } + @Test + void cancelRequestTest() { + when(circulationRequestService.getCancellationRequestIfOpenOrNull(anyString())).thenReturn(createCirculationRequest()); + circulationService.cancelRequest(createTransactionEntity()); + verify(circulationClient).cancelRequest(anyString(), any()); + } + + @Test + void shouldThrowExceptionWhenRequestIsNotUpdated() { + when(circulationRequestService.getCancellationRequestIfOpenOrNull(anyString())).thenReturn(createCirculationRequest()); + when(circulationClient.cancelRequest(anyString(), any())).thenThrow(FeignException.BadRequest.class); + assertThrows(CirculationRequestException.class, () -> circulationService.cancelRequest(createTransactionEntity())); + } + } diff --git a/src/test/java/org/folio/dcb/service/CustomTenantServiceTest.java b/src/test/java/org/folio/dcb/service/CustomTenantServiceTest.java index 14a807be..d1b20638 100644 --- a/src/test/java/org/folio/dcb/service/CustomTenantServiceTest.java +++ b/src/test/java/org/folio/dcb/service/CustomTenantServiceTest.java @@ -1,6 +1,7 @@ package org.folio.dcb.service; import feign.FeignException; +import org.folio.dcb.client.feign.CancellationReasonClient; import org.folio.dcb.client.feign.HoldingSourcesClient; import org.folio.dcb.client.feign.HoldingsStorageClient; import org.folio.dcb.client.feign.InstanceTypeClient; @@ -47,6 +48,8 @@ class CustomTenantServiceTest { private LocationUnitClient locationUnitClient; @Mock private LoanTypeClient loanTypeClient; + @Mock + private CancellationReasonClient cancellationReasonClient; @InjectMocks diff --git a/src/test/java/org/folio/dcb/service/LendingLibraryServiceTest.java b/src/test/java/org/folio/dcb/service/LendingLibraryServiceTest.java index 1d6bdbd3..39158b3e 100644 --- a/src/test/java/org/folio/dcb/service/LendingLibraryServiceTest.java +++ b/src/test/java/org/folio/dcb/service/LendingLibraryServiceTest.java @@ -18,8 +18,10 @@ import org.mockito.junit.jupiter.MockitoExtension; import static org.folio.dcb.domain.dto.DcbTransaction.RoleEnum.LENDER; +import static org.folio.dcb.utils.EntityUtils.CIRCULATION_REQUEST_ID; import static org.folio.dcb.utils.EntityUtils.DCB_TRANSACTION_ID; import static org.folio.dcb.utils.EntityUtils.PICKUP_SERVICE_POINT_ID; +import static org.folio.dcb.utils.EntityUtils.createCirculationRequest; import static org.folio.dcb.utils.EntityUtils.createDcbItem; import static org.folio.dcb.utils.EntityUtils.createDefaultDcbPatron; import static org.folio.dcb.utils.EntityUtils.createDcbTransactionByRole; @@ -29,6 +31,7 @@ import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; 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.doNothing; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; @@ -59,13 +62,13 @@ void createTransactionTest() { when(userService.fetchOrCreateUser(any())) .thenReturn(user); - doNothing().when(requestService).createPageItemRequest(any(), any(), any()); - doNothing().when(baseLibraryService).saveDcbTransaction(any(), any()); + when(requestService.createPageItemRequest(any(), any(), anyString())).thenReturn(createCirculationRequest()); + doNothing().when(baseLibraryService).saveDcbTransaction(any(), any(), any()); var response = lendingLibraryService.createCirculation(DCB_TRANSACTION_ID, createDcbTransactionByRole(LENDER), PICKUP_SERVICE_POINT_ID); verify(userService).fetchOrCreateUser(patron); verify(requestService).createPageItemRequest(user, item, PICKUP_SERVICE_POINT_ID); - verify(baseLibraryService).saveDcbTransaction(DCB_TRANSACTION_ID, createDcbTransactionByRole(LENDER)); + verify(baseLibraryService).saveDcbTransaction(DCB_TRANSACTION_ID, createDcbTransactionByRole(LENDER), CIRCULATION_REQUEST_ID); Assertions.assertEquals(TransactionStatusResponse.StatusEnum.CREATED, response.getStatus()); } diff --git a/src/test/java/org/folio/dcb/service/PickupLibraryServiceTest.java b/src/test/java/org/folio/dcb/service/PickupLibraryServiceTest.java index 60f51dcb..ac81c371 100644 --- a/src/test/java/org/folio/dcb/service/PickupLibraryServiceTest.java +++ b/src/test/java/org/folio/dcb/service/PickupLibraryServiceTest.java @@ -12,12 +12,14 @@ import static org.folio.dcb.domain.dto.DcbTransaction.RoleEnum.PICKUP; import static org.folio.dcb.utils.EntityUtils.DCB_TRANSACTION_ID; import static org.folio.dcb.utils.EntityUtils.PICKUP_SERVICE_POINT_ID; +import static org.folio.dcb.utils.EntityUtils.createCirculationRequest; import static org.folio.dcb.utils.EntityUtils.createDcbItem; import static org.folio.dcb.utils.EntityUtils.createDcbTransactionByRole; import static org.folio.dcb.utils.EntityUtils.createDefaultDcbPatron; import static org.folio.dcb.utils.EntityUtils.createUser; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -46,9 +48,9 @@ void createTransactionTest() { when(userService.fetchOrCreateUser(any())) .thenReturn(user); - doNothing().when(requestService).createHoldItemRequest(any(), any(), any()); + when(requestService.createHoldItemRequest(any(), any(), anyString())).thenReturn(createCirculationRequest()); doNothing().when(circulationItemService).checkIfItemExistsAndCreate(any(), any()); - doNothing().when(baseLibraryService).saveDcbTransaction(any(), any()); + doNothing().when(baseLibraryService).saveDcbTransaction(any(), any(), any()); var response = pickupLibraryService.createCirculation(DCB_TRANSACTION_ID, createDcbTransactionByRole(PICKUP), PICKUP_SERVICE_POINT_ID); verify(userService).fetchOrCreateUser(patron); diff --git a/src/test/java/org/folio/dcb/utils/EntityUtils.java b/src/test/java/org/folio/dcb/utils/EntityUtils.java index 7a82a8d9..b64060a9 100644 --- a/src/test/java/org/folio/dcb/utils/EntityUtils.java +++ b/src/test/java/org/folio/dcb/utils/EntityUtils.java @@ -3,6 +3,7 @@ import lombok.SneakyThrows; import org.folio.dcb.DcbApplication; import org.folio.dcb.client.feign.HoldingsStorageClient; +import org.folio.dcb.domain.dto.CirculationRequest; import org.folio.dcb.domain.dto.DcbTransaction; import org.folio.dcb.domain.dto.DcbItem; import org.folio.dcb.domain.dto.DcbPatron; @@ -46,6 +47,7 @@ public class EntityUtils { public static String EXISTED_PATRON_ID = "284056f5-0670-4e1e-9e2f-61b9f1ee2d18"; public static String PICKUP_SERVICE_POINT_ID = "0da8c1e4-1c1f-4dd9-b189-70ba978b7d94"; public static String DCB_TRANSACTION_ID = "571b0a2c-8883-40b5-a449-d41fe6017082"; + public static String CIRCULATION_REQUEST_ID = "571b0a2c-8883-40b5-a449-d41fe6017083"; public static String DCB_USER_TYPE = "dcb"; public static DcbTransaction createDcbTransactionByRole(DcbTransaction.RoleEnum role) { return DcbTransaction.builder() @@ -86,6 +88,12 @@ public static DcbItem createDcbItem() { .build(); } + public static CirculationRequest createCirculationRequest() { + return CirculationRequest.builder() + .id(CIRCULATION_REQUEST_ID) + .build(); + } + public static DcbPatron createDcbPatronWithExactPatronId(String patronId) { return DcbPatron.builder() .id(patronId) @@ -146,6 +154,7 @@ public static TransactionEntity createTransactionEntity() { .materialType("book") .lendingLibraryCode("LEN") .borrowingLibraryCode("BOR") + .requestId(UUID.fromString(CIRCULATION_REQUEST_ID)) .build(); } diff --git a/src/test/resources/mappings/cancellation.json b/src/test/resources/mappings/cancellation.json new file mode 100644 index 00000000..7f5c46bf --- /dev/null +++ b/src/test/resources/mappings/cancellation.json @@ -0,0 +1,17 @@ +{ + "mappings": [ + { + "request": { + "method": "POST", + "url": "/cancellation-reason-storage/cancellation-reasons" + }, + "response": { + "status": 201, + "body": "{\"id\": \"5b95877d-86c0-4cb7-a0cd-7660b348ae5a\", \"name\": \"DCB Cancellation\", \"description\": \"DCB Cancellation\"}", + "headers": { + "Content-Type": "application/json" + } + } + } + ] +} diff --git a/src/test/resources/mappings/circulation.json b/src/test/resources/mappings/circulation.json index e6b58198..ac492f17 100644 --- a/src/test/resources/mappings/circulation.json +++ b/src/test/resources/mappings/circulation.json @@ -7,6 +7,7 @@ }, "response": { "status": 200, + "body": "{\"id\": \"571b0a2c-8883-40b5-a449-d41fe6017083\"}", "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 new file mode 100644 index 00000000..f45127d8 --- /dev/null +++ b/src/test/resources/mockdata/kafka/cancel_request.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" + } + } +}