diff --git a/NEWS.md b/NEWS.md index 8880f4a6..c25416fd 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,3 +1,8 @@ +## 1.0.2 2024-12-12 +* Copy Secure Patron name when cloning users (MODTLR-116) +* Support for intermediate requests (MODTLR-98) +* Search Slips API (MODTLR-75) + ## 1.0.1 2024-11-30 * Merge `ecs-tlr-feature` branch into `master` (MODTLR-69) * Create pickup service point in lending tenant (MODTLR-17) diff --git a/descriptors/ModuleDescriptor-template.json b/descriptors/ModuleDescriptor-template.json index cc79c41d..8072b311 100644 --- a/descriptors/ModuleDescriptor-template.json +++ b/descriptors/ModuleDescriptor-template.json @@ -29,7 +29,8 @@ "users.item.post", "inventory-storage.service-points.item.get", "inventory-storage.service-points.collection.get", - "inventory-storage.service-points.item.post" + "inventory-storage.service-points.item.post", + "user-tenants.collection.get" ] }, { @@ -123,7 +124,6 @@ "permissionsRequired": ["tlr.pick-slips.collection.get"], "modulePermissions": [ "user-tenants.collection.get", - "search.instances.collection.get", "circulation-storage.requests.item.get", "circulation-storage.requests.collection.get", "users.item.get", @@ -135,7 +135,31 @@ "addresstypes.item.get", "addresstypes.collection.get", "inventory-storage.service-points.item.get", - "inventory-storage.service-points.collection.get" + "inventory-storage.service-points.collection.get", + "inventory-storage.instances.item.get", + "inventory-storage.instances.collection.get" + ] + }, + { + "methods": ["GET"], + "pathPattern": "/tlr/staff-slips/search-slips/{servicePointId}", + "permissionsRequired": ["tlr.search-slips.collection.get"], + "modulePermissions": [ + "user-tenants.collection.get", + "circulation-storage.requests.item.get", + "circulation-storage.requests.collection.get", + "users.item.get", + "users.collection.get", + "usergroups.item.get", + "usergroups.collection.get", + "departments.item.get", + "departments.collection.get", + "addresstypes.item.get", + "addresstypes.collection.get", + "inventory-storage.service-points.item.get", + "inventory-storage.service-points.collection.get", + "inventory-storage.instances.item.get", + "inventory-storage.instances.collection.get" ] } ] @@ -244,6 +268,10 @@ "permissionName": "tlr.pick-slips.collection.get", "displayName": "ecs-tlr - pick slips", "description": "Get pick slips" + }, { + "permissionName": "tlr.search-slips.collection.get", + "displayName": "ecs-tlr - search slips", + "description": "Get search slips" }, { "permissionName": "tlr.ecs-request-external.post", diff --git a/pom.xml b/pom.xml index f8f92315..00af855e 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ org.folio mod-tlr mod-tlr - 1.0.2-SNAPSHOT + 1.0.3-SNAPSHOT FOLIO mod-tlr module jar diff --git a/src/main/java/org/folio/client/feign/HoldingClient.java b/src/main/java/org/folio/client/feign/HoldingClient.java new file mode 100644 index 00000000..4d69cbf4 --- /dev/null +++ b/src/main/java/org/folio/client/feign/HoldingClient.java @@ -0,0 +1,16 @@ +package org.folio.client.feign; + +import org.folio.domain.dto.HoldingsRecord; +import org.folio.domain.dto.HoldingsRecords; +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 = "holdings", url = "holdings-storage/holdings", configuration = FeignClientConfiguration.class) +public interface HoldingClient extends GetByQueryClient { + + @GetMapping("/{id}") + HoldingsRecord get(@PathVariable String id); + +} diff --git a/src/main/java/org/folio/client/feign/InstanceClient.java b/src/main/java/org/folio/client/feign/InstanceClient.java index f4a211d1..6d30b8e8 100644 --- a/src/main/java/org/folio/client/feign/InstanceClient.java +++ b/src/main/java/org/folio/client/feign/InstanceClient.java @@ -1,5 +1,6 @@ package org.folio.client.feign; +import org.folio.domain.dto.Instances; import org.folio.domain.dto.InventoryInstance; import org.folio.spring.config.FeignClientConfiguration; import org.springframework.cloud.openfeign.FeignClient; @@ -7,7 +8,7 @@ import org.springframework.web.bind.annotation.PathVariable; @FeignClient(name = "instances", url = "instance-storage/instances", configuration = FeignClientConfiguration.class) -public interface InstanceClient { +public interface InstanceClient extends GetByQueryClient { @GetMapping("/{id}") InventoryInstance get(@PathVariable String id); diff --git a/src/main/java/org/folio/controller/StaffSlipsController.java b/src/main/java/org/folio/controller/StaffSlipsController.java index 0a4f031f..f53cedf1 100644 --- a/src/main/java/org/folio/controller/StaffSlipsController.java +++ b/src/main/java/org/folio/controller/StaffSlipsController.java @@ -5,10 +5,13 @@ import java.util.UUID; import org.folio.domain.dto.PickSlipsResponse; +import org.folio.domain.dto.SearchSlipsResponse; import org.folio.domain.dto.StaffSlip; -import org.folio.rest.resource.PickSlipsApi; +import org.folio.rest.resource.StaffSlipsApi; import org.folio.service.impl.PickSlipsService; +import org.folio.service.impl.SearchSlipsService; import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import lombok.AllArgsConstructor; @@ -17,9 +20,11 @@ @RestController @Log4j2 @AllArgsConstructor -public class StaffSlipsController implements PickSlipsApi { +@RequestMapping("/tlr/staff-slips") +public class StaffSlipsController implements StaffSlipsApi { private final PickSlipsService pickSlipsService; + private final SearchSlipsService searchSlipsService; @Override public ResponseEntity getPickSlips(UUID servicePointId) { @@ -30,4 +35,14 @@ public ResponseEntity getPickSlips(UUID servicePointId) { .pickSlips(new ArrayList<>(pickSlips)) .totalRecords(pickSlips.size())); } + + @Override + public ResponseEntity getSearchSlips(UUID servicePointId) { + log.info("getSearchSlips:: servicePointId={}", servicePointId); + Collection searchSlips = searchSlipsService.getStaffSlips(servicePointId.toString()); + + return ResponseEntity.ok(new SearchSlipsResponse() + .searchSlips(new ArrayList<>(searchSlips)) + .totalRecords(searchSlips.size())); + } } diff --git a/src/main/java/org/folio/domain/entity/EcsTlrEntity.java b/src/main/java/org/folio/domain/entity/EcsTlrEntity.java index 3afd5616..28f41f39 100644 --- a/src/main/java/org/folio/domain/entity/EcsTlrEntity.java +++ b/src/main/java/org/folio/domain/entity/EcsTlrEntity.java @@ -40,4 +40,8 @@ public class EcsTlrEntity { private UUID secondaryRequestId; private String secondaryRequestTenantId; private UUID secondaryRequestDcbTransactionId; + private UUID intermediateRequestId; + private String intermediateRequestTenantId; + private UUID intermediateRequestDcbTransactionId; + } diff --git a/src/main/java/org/folio/service/DcbService.java b/src/main/java/org/folio/service/DcbService.java index c687bfcd..7b8cb372 100644 --- a/src/main/java/org/folio/service/DcbService.java +++ b/src/main/java/org/folio/service/DcbService.java @@ -8,8 +8,11 @@ import org.folio.domain.entity.EcsTlrEntity; public interface DcbService { + void createTransactions(EcsTlrEntity ecsTlr, Request secondaryRequest); void createLendingTransaction(EcsTlrEntity ecsTlr); - void createBorrowingTransaction(EcsTlrEntity ecsTlr, Request request); + void createBorrowerTransaction(EcsTlrEntity ecsTlr, Request request); + void createBorrowingPickupTransaction(EcsTlrEntity ecsTlr, Request request); + void createPickupTransaction(EcsTlrEntity ecsTlr, Request request); TransactionStatusResponse getTransactionStatus(UUID transactionId, String tenantId); TransactionStatusResponse updateTransactionStatus(UUID transactionId, TransactionStatus.StatusEnum newStatus, String tenantId); diff --git a/src/main/java/org/folio/service/InventoryService.java b/src/main/java/org/folio/service/InventoryService.java index 4f3b4359..0cdce9d1 100644 --- a/src/main/java/org/folio/service/InventoryService.java +++ b/src/main/java/org/folio/service/InventoryService.java @@ -3,6 +3,8 @@ import java.util.Collection; import org.folio.domain.dto.Campus; +import org.folio.domain.dto.HoldingsRecord; +import org.folio.domain.dto.Instance; import org.folio.domain.dto.Institution; import org.folio.domain.dto.Item; import org.folio.domain.dto.Library; @@ -12,7 +14,9 @@ public interface InventoryService { Collection findItems(CqlQuery query, String idIndex, Collection ids); - Collection findItems(Collection ids); + Collection findHoldings(CqlQuery query, String idIndex, Collection ids); + Collection findHoldings(Collection ids); + Collection findInstances(Collection ids); Collection findMaterialTypes(Collection ids); Collection findLoanTypes(Collection ids); Collection findLibraries(Collection ids); diff --git a/src/main/java/org/folio/service/RequestService.java b/src/main/java/org/folio/service/RequestService.java index 6ef95b05..e9e86fe1 100644 --- a/src/main/java/org/folio/service/RequestService.java +++ b/src/main/java/org/folio/service/RequestService.java @@ -9,28 +9,28 @@ import org.folio.domain.dto.InventoryItem; import org.folio.domain.dto.ReorderQueue; import org.folio.domain.dto.Request; -import org.folio.domain.entity.EcsTlrEntity; import org.folio.support.CqlQuery; public interface RequestService { - RequestWrapper createPrimaryRequest(Request request, String borrowingTenantId); + RequestWrapper createPrimaryRequest(Request request, String primaryRequestTenantId, + String secondaryRequestTenantId); - RequestWrapper createSecondaryRequest(Request request, String borrowingTenantId, - Collection lendingTenantIds); + RequestWrapper createSecondaryRequest(Request request, String primaryRequestTenantId, + Collection secondaryRequestTenantIds); - CirculationItem createCirculationItem(EcsTlrEntity ecsTlr, Request secondaryRequest, - String borrowingTenantId, String lendingTenantId); + RequestWrapper createIntermediateRequest(Request intermediateRequest, + String primaryRequestTenantId, String intermediateRequestTenantId, + String secondaryRequestTenantId); CirculationItem updateCirculationItemOnRequestCreation(CirculationItem circulationItem, Request secondaryRequest); InventoryItem getItemFromStorage(String itemId, String tenantId); - InventoryInstance getInstanceFromStorage(String instanceId, String tenantId); - Request getRequestFromStorage(String requestId, String tenantId); Request getRequestFromStorage(String requestId); Collection getRequestsFromStorage(CqlQuery query, String idIndex, Collection ids); + Collection getRequestsFromStorage(CqlQuery query); Request updateRequestInStorage(Request request, String tenantId); List getRequestsQueueByInstanceId(String instanceId, String tenantId); List getRequestsQueueByInstanceId(String instanceId); diff --git a/src/main/java/org/folio/service/TenantService.java b/src/main/java/org/folio/service/TenantService.java index c3d0da04..ec83538f 100644 --- a/src/main/java/org/folio/service/TenantService.java +++ b/src/main/java/org/folio/service/TenantService.java @@ -5,7 +5,7 @@ import org.folio.domain.entity.EcsTlrEntity; public interface TenantService { - String getBorrowingTenant(EcsTlrEntity ecsTlr); + String getPrimaryRequestTenantId(EcsTlrEntity ecsTlr); - List getLendingTenants(EcsTlrEntity ecsTlr); + List getSecondaryRequestTenants(EcsTlrEntity ecsTlr); } diff --git a/src/main/java/org/folio/service/impl/AllowedServicePointsServiceImpl.java b/src/main/java/org/folio/service/impl/AllowedServicePointsServiceImpl.java index 8ff2866f..f93cc6f9 100644 --- a/src/main/java/org/folio/service/impl/AllowedServicePointsServiceImpl.java +++ b/src/main/java/org/folio/service/impl/AllowedServicePointsServiceImpl.java @@ -62,8 +62,6 @@ private AllowedServicePointsResponse getForCreate(AllowedServicePointsRequest re Map recall = new HashMap<>(); for (String tenantId : getLendingTenants(request)) { var servicePoints = getAllowedServicePointsFromTenant(request, patronGroupId, tenantId); - log.info("getForCreate:: service points from {}: {}", tenantId, servicePoints); - combineAndFilterDuplicates(page, servicePoints.getPage()); combineAndFilterDuplicates(hold, servicePoints.getHold()); combineAndFilterDuplicates(recall, servicePoints.getRecall()); diff --git a/src/main/java/org/folio/service/impl/DcbServiceImpl.java b/src/main/java/org/folio/service/impl/DcbServiceImpl.java index 01f425d4..99017e46 100644 --- a/src/main/java/org/folio/service/impl/DcbServiceImpl.java +++ b/src/main/java/org/folio/service/impl/DcbServiceImpl.java @@ -1,7 +1,9 @@ package org.folio.service.impl; import static org.folio.domain.dto.DcbTransaction.RoleEnum.BORROWER; +import static org.folio.domain.dto.DcbTransaction.RoleEnum.BORROWING_PICKUP; import static org.folio.domain.dto.DcbTransaction.RoleEnum.LENDER; +import static org.folio.domain.dto.DcbTransaction.RoleEnum.PICKUP; import java.util.UUID; @@ -9,6 +11,7 @@ import org.folio.client.feign.DcbTransactionClient; import org.folio.domain.dto.DcbItem; import org.folio.domain.dto.DcbTransaction; +import org.folio.domain.dto.DcbTransaction.RoleEnum; import org.folio.domain.dto.Request; import org.folio.domain.dto.TransactionStatus; import org.folio.domain.dto.TransactionStatusResponse; @@ -43,27 +46,53 @@ public void createLendingTransaction(EcsTlrEntity ecsTlr) { DcbTransaction transaction = new DcbTransaction() .requestId(ecsTlr.getSecondaryRequestId().toString()) .role(LENDER); - final UUID lendingTransactionId = createTransaction(transaction, ecsTlr.getSecondaryRequestTenantId()); - ecsTlr.setSecondaryRequestDcbTransactionId(lendingTransactionId); + final UUID transactionId = createTransaction(transaction, ecsTlr.getSecondaryRequestTenantId()); + ecsTlr.setSecondaryRequestDcbTransactionId(transactionId); log.info("createTransactions:: lending transaction {} for ECS TLR {} created", - () -> lendingTransactionId, ecsTlr::getId); + () -> transactionId, ecsTlr::getId); } @Override - public void createBorrowingTransaction(EcsTlrEntity ecsTlr, Request request) { - log.info("createBorrowingTransaction:: creating borrowing transaction for ECS TLR {}", ecsTlr::getId); + public void createBorrowerTransaction(EcsTlrEntity ecsTlr, Request request) { + log.info("createBorrowerTransaction:: creating borrower transaction for ECS TLR {}", ecsTlr::getId); + DcbTransaction transaction = buildTransaction(request, BORROWER, ecsTlr.getIntermediateRequestId()); + final UUID transactionId = createTransaction(transaction, ecsTlr.getIntermediateRequestTenantId()); + ecsTlr.setIntermediateRequestDcbTransactionId(transactionId); + log.info("createBorrowerTransaction:: borrower transaction {} for ECS TLR {} created", + () -> transactionId, ecsTlr::getId); + } + + @Override + public void createBorrowingPickupTransaction(EcsTlrEntity ecsTlr, Request request) { + log.info("createBorrowingPickupTransaction:: creating borrowing-pickup transaction for ECS TLR {}", + ecsTlr::getId); + DcbTransaction transaction = buildTransaction(request, BORROWING_PICKUP, ecsTlr.getPrimaryRequestId()); + final UUID transactionId = createTransaction(transaction, ecsTlr.getPrimaryRequestTenantId()); + ecsTlr.setPrimaryRequestDcbTransactionId(transactionId); + log.info("createBorrowingPickupTransaction:: borrowing-pickup transaction {} for ECS TLR {} created", + () -> transactionId, ecsTlr::getId); + } + + @Override + public void createPickupTransaction(EcsTlrEntity ecsTlr, Request request) { + log.info("createPickupTransaction:: creating pickup transaction for ECS TLR {}", ecsTlr.getId()); + DcbTransaction transaction = buildTransaction(request, PICKUP, ecsTlr.getPrimaryRequestId()); + final UUID transactionId = createTransaction(transaction, ecsTlr.getPrimaryRequestTenantId()); + ecsTlr.setPrimaryRequestDcbTransactionId(transactionId); + log.info("createPickupTransaction:: pickup transaction {} for ECS TLR {} created", + () -> transactionId, ecsTlr::getId); + } + + private DcbTransaction buildTransaction(Request request, RoleEnum role, UUID requestId) { DcbItem dcbItem = new DcbItem() .id(request.getItemId()) .title(request.getInstance().getTitle()) .barcode(request.getItem().getBarcode()); - DcbTransaction transaction = new DcbTransaction() - .requestId(ecsTlr.getPrimaryRequestId().toString()) + + return new DcbTransaction() + .requestId(requestId.toString()) .item(dcbItem) - .role(BORROWER); - final UUID borrowingTransactionId = createTransaction(transaction, ecsTlr.getPrimaryRequestTenantId()); - ecsTlr.setPrimaryRequestDcbTransactionId(borrowingTransactionId); - log.info("createBorrowingTransaction:: borrowing transaction {} for ECS TLR {} created", - () -> borrowingTransactionId, ecsTlr::getId); + .role(role); } private UUID createTransaction(DcbTransaction transaction, String tenantId) { @@ -97,4 +126,21 @@ public TransactionStatusResponse updateTransactionStatus(UUID transactionId, transactionId.toString(), new TransactionStatus().status(newStatus))); } + @Override + public void createTransactions(EcsTlrEntity ecsTlr, Request secondaryRequest) { + log.info("createTransactions:: creating transactions for ECS TLR {}", ecsTlr::getId); + if (secondaryRequest.getItemId() == null) { + log.info("createDcbTransactions:: secondary request has no item ID"); + return; + } + createLendingTransaction(ecsTlr); + log.info("createTransactions:: intermediate request ID: {}", ecsTlr::getIntermediateRequestId); + if (ecsTlr.getIntermediateRequestId() == null) { + createBorrowingPickupTransaction(ecsTlr, secondaryRequest); + } else { + createBorrowerTransaction(ecsTlr, secondaryRequest); + createPickupTransaction(ecsTlr, secondaryRequest); + } + } + } diff --git a/src/main/java/org/folio/service/impl/EcsTlrServiceImpl.java b/src/main/java/org/folio/service/impl/EcsTlrServiceImpl.java index b3496747..a9b3b702 100644 --- a/src/main/java/org/folio/service/impl/EcsTlrServiceImpl.java +++ b/src/main/java/org/folio/service/impl/EcsTlrServiceImpl.java @@ -1,14 +1,19 @@ package org.folio.service.impl; +import static java.util.Optional.of; +import static org.folio.domain.dto.Request.EcsRequestPhaseEnum.INTERMEDIATE; +import static org.folio.domain.dto.Request.EcsRequestPhaseEnum.PRIMARY; + import java.util.Collection; import java.util.List; import java.util.Optional; import java.util.UUID; +import java.util.stream.Collectors; import org.folio.domain.RequestWrapper; -import org.folio.domain.dto.CirculationItem; import org.folio.domain.dto.EcsTlr; import org.folio.domain.dto.Request; +import org.folio.domain.dto.Request.EcsRequestPhaseEnum; import org.folio.domain.entity.EcsTlrEntity; import org.folio.domain.mapper.EcsTlrMapper; import org.folio.exception.TenantPickingException; @@ -17,6 +22,7 @@ import org.folio.service.EcsTlrService; import org.folio.service.RequestService; import org.folio.service.TenantService; +import org.folio.service.UserTenantsService; import org.springframework.stereotype.Service; import lombok.RequiredArgsConstructor; @@ -32,6 +38,7 @@ public class EcsTlrServiceImpl implements EcsTlrService { private final TenantService tenantService; private final RequestService requestService; private final DcbService dcbService; + private final UserTenantsService userTenantsService; @Override public Optional get(UUID id) { @@ -47,23 +54,35 @@ public EcsTlr create(EcsTlr ecsTlrDto) { ecsTlrDto.getInstanceId(), ecsTlrDto.getItemId(), ecsTlrDto.getRequesterId()); final EcsTlrEntity ecsTlr = requestsMapper.mapDtoToEntity(ecsTlrDto); - String borrowingTenantId = getBorrowingTenant(ecsTlr); - Collection lendingTenantIds = getLendingTenants(ecsTlr); - RequestWrapper secondaryRequest = requestService.createSecondaryRequest( - buildSecondaryRequest(ecsTlr), borrowingTenantId, lendingTenantIds); - - log.info("create:: Creating circulation item for ECS TLR (ILR) {}", ecsTlrDto.getId()); - CirculationItem circulationItem = requestService.createCirculationItem(ecsTlr, - secondaryRequest.request(), borrowingTenantId, secondaryRequest.tenantId()); + String primaryRequestTenantId = getPrimaryRequestTenant(ecsTlr); + Collection secondaryRequestsTenantIds = getSecondaryRequestTenants(ecsTlr).stream() + .filter(tenantId -> !tenantId.equals(primaryRequestTenantId)) + .collect(Collectors.toList()); - RequestWrapper primaryRequest = requestService.createPrimaryRequest( - buildPrimaryRequest(secondaryRequest.request()), borrowingTenantId); + log.info("create:: Creating secondary request for ECS TLR (ILR), instance {}, item {}, requester {}", + ecsTlrDto.getInstanceId(), ecsTlrDto.getItemId(), ecsTlrDto.getRequesterId()); + RequestWrapper secondaryRequestWrapper = requestService.createSecondaryRequest( + buildSecondaryRequest(ecsTlr), primaryRequestTenantId, secondaryRequestsTenantIds); + Request secondaryRequest = secondaryRequestWrapper.request(); + String secondaryRequestTenantId = secondaryRequestWrapper.tenantId(); - requestService.updateCirculationItemOnRequestCreation(circulationItem, - secondaryRequest.request()); + log.info("create:: Creating primary request for ECS TLR (ILR), instance {}, item {}, requester {}", + ecsTlrDto.getInstanceId(), ecsTlrDto.getItemId(), ecsTlrDto.getRequesterId()); + RequestWrapper primaryRequestWrapper = requestService.createPrimaryRequest( + buildPrimaryRequest(secondaryRequest), primaryRequestTenantId, secondaryRequestTenantId); + + updateEcsTlr(ecsTlr, primaryRequestWrapper, secondaryRequestWrapper); + + var centralTenantId = userTenantsService.getCentralTenantId(); + if (!primaryRequestTenantId.equals(centralTenantId)) { + log.info("create:: Primary request tenant is not central, creating intermediate request"); + RequestWrapper intermediateRequest = requestService.createIntermediateRequest( + buildIntermediateRequest(secondaryRequest), primaryRequestTenantId, centralTenantId, + secondaryRequestTenantId); + updateEcsTlrWithIntermediateRequest(ecsTlr, intermediateRequest); + } - updateEcsTlr(ecsTlr, primaryRequest, secondaryRequest); - createDcbTransactions(ecsTlr, secondaryRequest.request()); + dcbService.createTransactions(ecsTlr, secondaryRequest); return requestsMapper.mapEntityToDto(save(ecsTlr)); } @@ -90,28 +109,28 @@ public boolean delete(UUID requestId) { return false; } - private String getBorrowingTenant(EcsTlrEntity ecsTlr) { - log.info("getBorrowingTenant:: getting borrowing tenant"); - final String borrowingTenantId = tenantService.getBorrowingTenant(ecsTlr); - log.info("getBorrowingTenant:: borrowing tenant: {}", borrowingTenantId); + private String getPrimaryRequestTenant(EcsTlrEntity ecsTlr) { + log.info("getPrimaryRequestTenant:: getting primary request tenant"); + final String primaryRequestTenantId = tenantService.getPrimaryRequestTenantId(ecsTlr); + log.info("getPrimaryRequestTenant:: primary request tenant: {}", primaryRequestTenantId); - if (borrowingTenantId == null) { - throw new TenantPickingException("Failed to get borrowing tenant"); + if (primaryRequestTenantId == null) { + throw new TenantPickingException("Failed to get primary request tenant"); } - return borrowingTenantId; + return primaryRequestTenantId; } - private Collection getLendingTenants(EcsTlrEntity ecsTlr) { + private Collection getSecondaryRequestTenants(EcsTlrEntity ecsTlr) { final String instanceId = ecsTlr.getInstanceId().toString(); - log.info("getLendingTenants:: looking for lending tenants for instance {}", instanceId); - List tenantIds = tenantService.getLendingTenants(ecsTlr); + log.info("getSecondaryRequestTenants:: looking for secondary request tenants for instance {}", instanceId); + List tenantIds = tenantService.getSecondaryRequestTenants(ecsTlr); if (tenantIds.isEmpty()) { - log.error("getLendingTenants:: failed to find lending tenants for instance: {}", instanceId); - throw new TenantPickingException("Failed to find lending tenants for instance " + instanceId); + log.error("getSecondaryRequestTenants:: failed to find lending tenants for instance: {}", instanceId); + throw new TenantPickingException("Failed to find secondary request tenants for instance " + instanceId); } - log.info("getLendingTenants:: lending tenants found: {}", tenantIds); + log.info("getSecondaryRequestTenants:: secondary request tenants found: {}", tenantIds); return tenantIds; } @@ -125,6 +144,14 @@ private EcsTlrEntity save(EcsTlrEntity ecsTlr) { } private static Request buildPrimaryRequest(Request secondaryRequest) { + return buildRequest(secondaryRequest, PRIMARY); + } + + private static Request buildIntermediateRequest(Request secondaryRequest) { + return buildRequest(secondaryRequest, INTERMEDIATE); + } + + private static Request buildRequest(Request secondaryRequest, EcsRequestPhaseEnum ecsRequestPhase) { return new Request() .id(secondaryRequest.getId()) .instanceId(secondaryRequest.getInstanceId()) @@ -134,14 +161,14 @@ private static Request buildPrimaryRequest(Request secondaryRequest) { .requestDate(secondaryRequest.getRequestDate()) .requestLevel(secondaryRequest.getRequestLevel()) .requestType(secondaryRequest.getRequestType()) - .ecsRequestPhase(Request.EcsRequestPhaseEnum.PRIMARY) + .ecsRequestPhase(ecsRequestPhase) .fulfillmentPreference(secondaryRequest.getFulfillmentPreference()) .pickupServicePointId(secondaryRequest.getPickupServicePointId()); } private Request buildSecondaryRequest(EcsTlrEntity ecsTlr) { return requestsMapper.mapEntityToRequest(ecsTlr) - .ecsRequestPhase(Request.EcsRequestPhaseEnum.SECONDARY); + .ecsRequestPhase(EcsRequestPhaseEnum.SECONDARY); } private static void updateEcsTlr(EcsTlrEntity ecsTlr, RequestWrapper primaryRequest, @@ -153,7 +180,7 @@ private static void updateEcsTlr(EcsTlrEntity ecsTlr, RequestWrapper primaryRequ ecsTlr.setPrimaryRequestId(UUID.fromString(primaryRequest.request().getId())); ecsTlr.setSecondaryRequestId(UUID.fromString(secondaryRequest.request().getId())); - Optional.of(secondaryRequest.request()) + of(secondaryRequest.request()) .map(Request::getItemId) .map(UUID::fromString) .ifPresent(ecsTlr::setItemId); @@ -162,13 +189,15 @@ private static void updateEcsTlr(EcsTlrEntity ecsTlr, RequestWrapper primaryRequ log.debug("updateEcsTlr:: ECS TLR: {}", () -> ecsTlr); } - private void createDcbTransactions(EcsTlrEntity ecsTlr, Request secondaryRequest) { - if (secondaryRequest.getItemId() == null) { - log.info("createDcbTransactions:: secondary request has no item ID"); - return; - } - dcbService.createBorrowingTransaction(ecsTlr, secondaryRequest); - dcbService.createLendingTransaction(ecsTlr); + private static void updateEcsTlrWithIntermediateRequest(EcsTlrEntity ecsTlr, + RequestWrapper intermediateRequest) { + + log.info("updateEcsTlrWithIntermediateRequest:: updating ECS TLR in memory"); + ecsTlr.setIntermediateRequestTenantId(intermediateRequest.tenantId()); + ecsTlr.setIntermediateRequestId(UUID.fromString(intermediateRequest.request().getId())); + + log.info("updateEcsTlrWithIntermediateRequest:: ECS TLR updated in memory"); + log.debug("updateEcsTlrWithIntermediateRequest:: ECS TLR: {}", () -> ecsTlr); } } diff --git a/src/main/java/org/folio/service/impl/InventoryServiceImpl.java b/src/main/java/org/folio/service/impl/InventoryServiceImpl.java index b2018d63..d32319f1 100644 --- a/src/main/java/org/folio/service/impl/InventoryServiceImpl.java +++ b/src/main/java/org/folio/service/impl/InventoryServiceImpl.java @@ -2,6 +2,8 @@ import java.util.Collection; +import org.folio.client.feign.HoldingClient; +import org.folio.client.feign.InstanceClient; import org.folio.client.feign.ItemClient; import org.folio.client.feign.LoanTypeClient; import org.folio.client.feign.LocationCampusClient; @@ -10,6 +12,10 @@ import org.folio.client.feign.MaterialTypeClient; import org.folio.domain.dto.Campus; import org.folio.domain.dto.Campuses; +import org.folio.domain.dto.HoldingsRecord; +import org.folio.domain.dto.HoldingsRecords; +import org.folio.domain.dto.Instance; +import org.folio.domain.dto.Instances; import org.folio.domain.dto.Institution; import org.folio.domain.dto.Institutions; import org.folio.domain.dto.Item; @@ -34,6 +40,8 @@ public class InventoryServiceImpl implements InventoryService { private final ItemClient itemClient; + private final InstanceClient instanceClient; + private final HoldingClient holdingClient; private final MaterialTypeClient materialTypeClient; private final LoanTypeClient loanTypeClient; private final LocationLibraryClient libraryClient; @@ -45,65 +53,64 @@ public Collection findItems(CqlQuery query, String idIndex, Collection items = BulkFetcher.fetch(itemClient, query, idIndex, ids, Items::getItems); - log.info("findItems:: found {} items", items::size); - return items; + return BulkFetcher.fetch(itemClient, query, idIndex, ids, Items::getItems); } @Override - public Collection findItems(Collection ids) { - log.info("findItems:: searching items by {} IDs", ids::size); - log.debug("findItems:: ids={}", ids); - Collection items = BulkFetcher.fetch(itemClient, ids, Items::getItems); - log.info("findItems:: found {} items", items::size); - return items; + public Collection findHoldings(CqlQuery query, String idIndex, Collection ids) { + log.info("findHoldings:: searching holdings by query and index: query={}, index={}, ids={}", + query, idIndex, ids.size()); + log.debug("findHoldings:: ids={}", ids); + return BulkFetcher.fetch(holdingClient, query, idIndex, ids, HoldingsRecords::getHoldingsRecords); + } + + @Override + public Collection findHoldings(Collection ids) { + log.info("findHoldings:: searching holdings by {} IDs", ids::size); + return BulkFetcher.fetch(holdingClient, ids, HoldingsRecords::getHoldingsRecords); + } + + + @Override + public Collection findInstances(Collection ids) { + log.info("findInstances:: searching instances by {} IDs", ids::size); + log.debug("findInstances:: ids={}", ids); + return BulkFetcher.fetch(instanceClient, ids, Instances::getInstances); } @Override public Collection findMaterialTypes(Collection ids) { log.info("findMaterialTypes:: searching material types by {} IDs", ids::size); log.debug("findMaterialTypes:: ids={}", ids); - Collection materialTypes = BulkFetcher.fetch(materialTypeClient, ids, - MaterialTypes::getMtypes); - log.info("findMaterialTypes:: found {} material types", materialTypes::size); - return materialTypes; + return BulkFetcher.fetch(materialTypeClient, ids, MaterialTypes::getMtypes); } @Override public Collection findLoanTypes(Collection ids) { log.info("findLoanTypes:: searching loan types by {} IDs", ids::size); log.debug("findLoanTypes:: ids={}", ids); - Collection loanTypes = BulkFetcher.fetch(loanTypeClient, ids, LoanTypes::getLoantypes); - log.info("findLoanTypes:: found {} loan types", loanTypes::size); - return loanTypes; + return BulkFetcher.fetch(loanTypeClient, ids, LoanTypes::getLoantypes); } @Override public Collection findLibraries(Collection ids) { log.info("findLibraries:: searching libraries by {} IDs", ids::size); log.debug("findLibraries:: ids={}", ids); - Collection libraries = BulkFetcher.fetch(libraryClient, ids, Libraries::getLoclibs); - log.info("findLibraries:: found {} libraries", libraries::size); - return libraries; + return BulkFetcher.fetch(libraryClient, ids, Libraries::getLoclibs); } @Override public Collection findCampuses(Collection ids) { log.info("findCampuses:: searching campuses by {} IDs", ids::size); log.debug("findCampuses:: ids={}", ids); - Collection campuses = BulkFetcher.fetch(campusClient, ids, Campuses::getLoccamps); - log.info("findCampuses:: found {} campuses", campuses::size); - return campuses; + return BulkFetcher.fetch(campusClient, ids, Campuses::getLoccamps); } @Override public Collection findInstitutions(Collection ids) { log.info("findInstitutions:: searching institutions by {} IDs", ids::size); log.debug("findInstitutions:: ids={}", ids); - Collection institutions = BulkFetcher.fetch(institutionClient, ids, - Institutions::getLocinsts); - log.info("findInstitutions:: found {} institutions", institutions::size); - return institutions; + return BulkFetcher.fetch(institutionClient, ids, Institutions::getLocinsts); } } diff --git a/src/main/java/org/folio/service/impl/PickSlipsService.java b/src/main/java/org/folio/service/impl/PickSlipsService.java index 17e41bc9..7dc19a07 100644 --- a/src/main/java/org/folio/service/impl/PickSlipsService.java +++ b/src/main/java/org/folio/service/impl/PickSlipsService.java @@ -12,7 +12,6 @@ import org.folio.service.InventoryService; import org.folio.service.LocationService; import org.folio.service.RequestService; -import org.folio.service.SearchService; import org.folio.service.ServicePointService; import org.folio.service.UserGroupService; import org.folio.service.UserService; @@ -31,11 +30,10 @@ public PickSlipsService(LocationService locationService, InventoryService invent RequestService requestService, ConsortiaService consortiaService, SystemUserScopedExecutionService executionService, UserService userService, UserGroupService userGroupService, DepartmentService departmentService, - AddressTypeService addressTypeService, SearchService searchService, - ServicePointService servicePointService) { + AddressTypeService addressTypeService, ServicePointService servicePointService) { super(EnumSet.of(PAGED), EnumSet.of(OPEN_NOT_YET_FILLED), EnumSet.of(PAGE), locationService, inventoryService, requestService, consortiaService, executionService, userService, - userGroupService, departmentService, addressTypeService, searchService, servicePointService); + userGroupService, departmentService, addressTypeService, servicePointService); } } diff --git a/src/main/java/org/folio/service/impl/RequestBatchUpdateEventHandler.java b/src/main/java/org/folio/service/impl/RequestBatchUpdateEventHandler.java index a6f2f2d3..2ce021b5 100644 --- a/src/main/java/org/folio/service/impl/RequestBatchUpdateEventHandler.java +++ b/src/main/java/org/folio/service/impl/RequestBatchUpdateEventHandler.java @@ -61,14 +61,14 @@ private void updateQueuePositionsForItemLevel(String itemId) { } private void updateQueuePositions(List unifiedQueue, boolean isTlrRequestQueue) { - log.info("updateQueuePositions:: parameters unifiedQueue: {}", unifiedQueue); + log.debug("updateQueuePositions:: parameters unifiedQueue: {}", unifiedQueue); List sortedPrimaryRequestIds = unifiedQueue.stream() .filter(request -> PRIMARY == request.getEcsRequestPhase()) .filter(request -> request.getPosition() != null) .sorted(Comparator.comparing(Request::getPosition)) .map(request -> UUID.fromString(request.getId())) .toList(); - log.info("updateQueuePositions:: sortedPrimaryRequestIds: {}", sortedPrimaryRequestIds); + log.debug("updateQueuePositions:: sortedPrimaryRequestIds: {}", sortedPrimaryRequestIds); List ecsTlrByPrimaryRequests = ecsTlrRepository.findByPrimaryRequestIdIn( sortedPrimaryRequestIds); @@ -101,7 +101,7 @@ private Map> groupSecondaryRequestsByTenantId( private List sortEcsTlrEntities(List sortedPrimaryRequestIds, List ecsTlrQueue) { - log.info("sortEcsTlrEntities:: parameters sortedPrimaryRequestIds: {}, ecsTlrQueue: {}", + log.debug("sortEcsTlrEntities:: parameters sortedPrimaryRequestIds: {}, ecsTlrQueue: {}", sortedPrimaryRequestIds, ecsTlrQueue); Map ecsTlrByPrimaryRequestId = ecsTlrQueue.stream() .collect(toMap(EcsTlrEntity::getPrimaryRequestId, Function.identity())); @@ -109,7 +109,7 @@ private List sortEcsTlrEntities(List sortedPrimaryRequestIds .stream() .map(ecsTlrByPrimaryRequestId::get) .toList(); - log.info("sortEcsTlrEntities:: result: {}", sortedEcsTlrQueue); + log.debug("sortEcsTlrEntities:: result: {}", sortedEcsTlrQueue); return sortedEcsTlrQueue; } @@ -118,7 +118,7 @@ private void reorderSecondaryRequestsQueue( Map> groupedSecondaryRequestsByTenantId, List sortedEcsTlrQueue, boolean isTlrRequestQueue) { - log.info("reorderSecondaryRequestsQueue:: parameters groupedSecondaryRequestsByTenantId: {}, " + + log.debug("reorderSecondaryRequestsQueue:: parameters groupedSecondaryRequestsByTenantId: {}, " + "sortedEcsTlrQueue: {}", groupedSecondaryRequestsByTenantId, sortedEcsTlrQueue); Map correctOrder = IntStream.range(0, sortedEcsTlrQueue.size()) @@ -198,12 +198,12 @@ private void updateReorderedRequests(List requestsWithUpdatedPositions, new ReorderQueueReorderedQueueInner() .id(request.getId()) .newPosition(request.getPosition()))); - log.info("updateReorderedRequests:: reorderQueue: {}", reorderQueue); + log.debug("updateReorderedRequests:: reorderQueue: {}", reorderQueue); List requests = isTlrRequestQueue ? requestService.reorderRequestsQueueForInstance(id, tenantId, reorderQueue) : requestService.reorderRequestsQueueForItem(id, tenantId, reorderQueue); - log.info("updateReorderedRequests:: result: {}", requests); + log.debug("updateReorderedRequests:: result: {}", requests); } } diff --git a/src/main/java/org/folio/service/impl/RequestEventHandler.java b/src/main/java/org/folio/service/impl/RequestEventHandler.java index 770c28a7..f8087ace 100644 --- a/src/main/java/org/folio/service/impl/RequestEventHandler.java +++ b/src/main/java/org/folio/service/impl/RequestEventHandler.java @@ -118,29 +118,12 @@ private static boolean requestMatchesEcsTlr(EcsTlrEntity ecsTlr, Request updated private void handlePrimaryRequestUpdate(EcsTlrEntity ecsTlr, KafkaEvent event) { propagateChangesFromPrimaryToSecondaryRequest(ecsTlr, event); - determineNewTransactionStatus(event).ifPresent(newTransactionStatus -> { - if (newTransactionStatus == CANCELLED) { - log.info("handlePrimaryRequestUpdate:: cancelling secondary DCB transaction"); - updateTransactionStatus(ecsTlr.getSecondaryRequestDcbTransactionId(), newTransactionStatus, - ecsTlr.getSecondaryRequestTenantId()); - } else { - updateTransactionStatus(ecsTlr.getPrimaryRequestDcbTransactionId(), newTransactionStatus, - ecsTlr.getPrimaryRequestTenantId()); - } - }); + updateTransactionStatuses(event, ecsTlr); } private void handleSecondaryRequestUpdate(EcsTlrEntity ecsTlr, KafkaEvent event) { processItemIdUpdate(ecsTlr, event.getData().getNewVersion()); - determineNewTransactionStatus(event).ifPresent(newTransactionStatus -> { - updateTransactionStatus(ecsTlr.getSecondaryRequestDcbTransactionId(), newTransactionStatus, - ecsTlr.getSecondaryRequestTenantId()); - if (newTransactionStatus == OPEN) { - log.info("handleSecondaryRequestUpdate:: open primary DCB transaction"); - updateTransactionStatus(ecsTlr.getPrimaryRequestDcbTransactionId(), newTransactionStatus, - ecsTlr.getPrimaryRequestTenantId()); - } - }); + updateTransactionStatuses(event, ecsTlr); } private void processItemIdUpdate(EcsTlrEntity ecsTlr, Request updatedRequest) { @@ -151,8 +134,8 @@ private void processItemIdUpdate(EcsTlrEntity ecsTlr, Request updatedRequest) { log.info("processItemIdUpdate:: updating ECS TLR {} with itemId {}", ecsTlr::getId, updatedRequest::getItemId); ecsTlr.setItemId(UUID.fromString(updatedRequest.getItemId())); - dcbService.createLendingTransaction(ecsTlr); - dcbService.createBorrowingTransaction(ecsTlr, updatedRequest); + // TODO: change this if Page request works + dcbService.createTransactions(ecsTlr, updatedRequest); ecsTlrRepository.save(ecsTlr); log.info("processItemIdUpdate: ECS TLR {} is updated", ecsTlr::getId); } @@ -186,19 +169,52 @@ private static Optional determineNewTransactionSta return newTransactionStatus; } + private void updateTransactionStatuses(KafkaEvent event, EcsTlrEntity ecsTlr) { + determineNewTransactionStatus(event) + .ifPresent(newStatus -> updateTransactionStatuses(newStatus, ecsTlr)); + } + + private void updateTransactionStatuses(TransactionStatus.StatusEnum newStatus, EcsTlrEntity ecsTlr) { + log.info("updateTransactionStatuses:: updating primary transaction status to {}", newStatus::getValue); + updateTransactionStatus(ecsTlr.getPrimaryRequestDcbTransactionId(), newStatus, + ecsTlr.getPrimaryRequestTenantId()); + + log.info("updateTransactionStatuses:: updating intermediate transaction status to {}", newStatus::getValue); + updateTransactionStatus(ecsTlr.getIntermediateRequestDcbTransactionId(), newStatus, + ecsTlr.getIntermediateRequestTenantId()); + + log.info("updateTransactionStatuses:: updating secondary transaction status to {}", newStatus::getValue); + updateTransactionStatus(ecsTlr.getSecondaryRequestDcbTransactionId(), newStatus, + ecsTlr.getSecondaryRequestTenantId()); + } + private void updateTransactionStatus(UUID transactionId, - TransactionStatus.StatusEnum newTransactionStatus, String tenant) { + TransactionStatus.StatusEnum newStatus, String tenantId) { + + if (transactionId == null) { + log.info("updateTransactionStatus:: transaction ID is null, doing nothing"); + return; + } + if (tenantId == null) { + log.info("updateTransactionStatus:: tenant ID is null, doing nothing"); + return; + } try { - var currentStatus = dcbService.getTransactionStatus(transactionId, tenant).getStatus(); + var currentStatus = dcbService.getTransactionStatus(transactionId, tenantId).getStatus(); log.info("updateTransactionStatus:: current transaction status: {}", currentStatus); - if (newTransactionStatus.getValue().equals(currentStatus.getValue())) { + if (newStatus.getValue().equals(currentStatus.getValue())) { log.info("updateTransactionStatus:: transaction status did not change, doing nothing"); return; } - dcbService.updateTransactionStatus(transactionId, newTransactionStatus, tenant); + log.info("updateTransactionStatus: changing status of transaction {} in tenant {} from {} to {}", + transactionId, tenantId, currentStatus.getValue(), newStatus.getValue()); + dcbService.updateTransactionStatus(transactionId, newStatus, tenantId); } catch (FeignException.NotFound e) { log.error("updateTransactionStatus:: transaction {} not found: {}", transactionId, e.getMessage()); + } catch (Exception e) { + log.error("updateTransactionStatus:: failed to update transaction status: {}", e::getMessage); + log.debug("updateTransactionStatus:: ", e); } } diff --git a/src/main/java/org/folio/service/impl/RequestServiceImpl.java b/src/main/java/org/folio/service/impl/RequestServiceImpl.java index 47f527c7..313d4b8e 100644 --- a/src/main/java/org/folio/service/impl/RequestServiceImpl.java +++ b/src/main/java/org/folio/service/impl/RequestServiceImpl.java @@ -4,6 +4,7 @@ import java.util.Collection; import java.util.List; +import java.util.Optional; import java.util.UUID; import org.folio.client.feign.CirculationClient; @@ -23,7 +24,6 @@ import org.folio.domain.dto.Requests; import org.folio.domain.dto.ServicePoint; import org.folio.domain.dto.User; -import org.folio.domain.entity.EcsTlrEntity; import org.folio.exception.RequestCreatingException; import org.folio.service.CloningService; import org.folio.service.RequestService; @@ -58,95 +58,159 @@ public class RequestServiceImpl implements RequestService { public static final String HOLDINGS_RECORD_ID = "10cd3a5a-d36f-4c7a-bc4f-e1ae3cf820c9"; @Override - public RequestWrapper createPrimaryRequest(Request request, String borrowingTenantId) { - final String requestId = request.getId(); - log.info("createPrimaryRequest:: creating primary request {} in borrowing tenant ({})", - requestId, borrowingTenantId); - Request primaryRequest = executionService.executeSystemUserScoped(borrowingTenantId, - () -> circulationClient.createRequest(request)); - log.info("createPrimaryRequest:: primary request {} created in borrowing tenant ({})", - requestId, borrowingTenantId); - log.debug("createPrimaryRequest:: primary request: {}", () -> primaryRequest); - - return new RequestWrapper(primaryRequest, borrowingTenantId); + public RequestWrapper createPrimaryRequest(Request primaryRequest, + String primaryRequestTenantId, String secondaryRequestTenantId) { + + final String requestId = primaryRequest.getId(); + log.info("createPrimaryRequest:: creating primary request {} in tenant {}", requestId, + primaryRequestTenantId); + + return executionService.executeSystemUserScoped(primaryRequestTenantId, () -> { + CirculationItem circItem = createCirculationItem(primaryRequest, secondaryRequestTenantId); + Request request = circulationClient.createRequest(primaryRequest); + log.info("createPrimaryRequest:: primary request {} created in tenant {}", + requestId, primaryRequestTenantId); + log.debug("createPrimaryRequest:: primary request: {}", () -> request); + updateCirculationItemOnRequestCreation(circItem, request); + return new RequestWrapper(request, primaryRequestTenantId); + }); } @Override - public RequestWrapper createSecondaryRequest(Request request, String borrowingTenantId, - Collection lendingTenantIds) { + public RequestWrapper createSecondaryRequest(Request request, String primaryRequestTenantId, + Collection secondaryRequestTenantIds) { final String requestId = request.getId(); final String requesterId = request.getRequesterId(); final String pickupServicePointId = request.getPickupServicePointId(); log.info("createSecondaryRequest:: creating secondary request {} in one of potential " + - "lending tenants: {}", requestId, lendingTenantIds); + "tenants: {}", requestId, secondaryRequestTenantIds); - User primaryRequestRequester = executionService.executeSystemUserScoped(borrowingTenantId, + User primaryRequestRequester = executionService.executeSystemUserScoped(primaryRequestTenantId, () -> userService.find(requesterId)); ServicePoint primaryRequestPickupServicePoint = executionService.executeSystemUserScoped( - borrowingTenantId, () -> servicePointService.find(pickupServicePointId)); + primaryRequestTenantId, () -> servicePointService.find(pickupServicePointId)); - for (String lendingTenantId : lendingTenantIds) { + for (String secondaryRequestTenantId : secondaryRequestTenantIds) { try { - return executionService.executeSystemUserScoped(lendingTenantId, () -> { - log.info("createSecondaryRequest:: creating requester {} in lending tenant ({})", - requesterId, lendingTenantId); + return executionService.executeSystemUserScoped(secondaryRequestTenantId, () -> { + log.info("createSecondaryRequest:: creating requester {} in tenant {}", + requesterId, secondaryRequestTenantId); cloneRequester(primaryRequestRequester); - log.info("createSecondaryRequest:: creating pickup service point {} in lending tenant ({})", - pickupServicePointId, lendingTenantId); + log.info("createSecondaryRequest:: creating pickup service point {} in tenant {}", + pickupServicePointId, secondaryRequestTenantId); servicePointCloningService.clone(primaryRequestPickupServicePoint); - log.info("createSecondaryRequest:: creating secondary request {} in lending tenant ({})", - requestId, lendingTenantId); + log.info("createSecondaryRequest:: creating secondary request {} in tenant {}", + requestId, secondaryRequestTenantId); Request secondaryRequest = circulationClient.createRequest(request); - log.info("createSecondaryRequest:: secondary request {} created in lending tenant ({})", - requestId, lendingTenantId); + log.info("createSecondaryRequest:: secondary request {} created in tenant {}", + secondaryRequest.getId(), secondaryRequestTenantId); log.debug("createSecondaryRequest:: secondary request: {}", () -> secondaryRequest); - return new RequestWrapper(secondaryRequest, lendingTenantId); + return new RequestWrapper(secondaryRequest, secondaryRequestTenantId); }); } catch (Exception e) { - log.error("createSecondaryRequest:: failed to create secondary request in lending tenant ({}): {}", - lendingTenantId, e.getMessage()); + log.error("createSecondaryRequest:: failed to create secondary request in tenant {}: {}", + secondaryRequestTenantId, e.getMessage()); log.debug("createSecondaryRequest:: ", e); } } String errorMessage = format( - "Failed to create secondary request for instance %s in all potential lending tenants: %s", - request.getInstanceId(), lendingTenantIds); + "Failed to create secondary request for instance %s in all potential tenants: %s", + request.getInstanceId(), secondaryRequestTenantIds); log.error("createSecondaryRequest:: {}", errorMessage); throw new RequestCreatingException(errorMessage); } @Override - public CirculationItem createCirculationItem(EcsTlrEntity ecsTlr, Request secondaryRequest, - String borrowingTenantId, String lendingTenantId) { + public RequestWrapper createIntermediateRequest(Request intermediateRequest, + String primaryRequestTenantId, String intermediateRequestTenantId, + String secondaryRequestTenantId) { + + log.info("createIntermediateRequest:: creating intermediate request in tenant {}, instance {}," + + " item {}, requester {}", intermediateRequestTenantId, intermediateRequest.getInstanceId(), + intermediateRequest.getItemId(), intermediateRequest.getRequesterId()); + + try { + final String requesterId = intermediateRequest.getRequesterId(); + final String pickupServicePointId = intermediateRequest.getPickupServicePointId(); + + User primaryRequestRequester = executionService.executeSystemUserScoped(primaryRequestTenantId, + () -> userService.find(requesterId)); + ServicePoint primaryRequestPickupServicePoint = executionService.executeSystemUserScoped( + primaryRequestTenantId, () -> servicePointService.find(pickupServicePointId)); + + log.info("createIntermediateRequest:: creating requester {} in tenant {}", + requesterId, intermediateRequestTenantId); + cloneRequester(primaryRequestRequester); + + log.info("createIntermediateRequest:: creating pickup service point {} in tenant {}", + pickupServicePointId, intermediateRequestTenantId); + servicePointCloningService.clone(primaryRequestPickupServicePoint); + + CirculationItem circItem = createCirculationItem(intermediateRequest, secondaryRequestTenantId); + + log.info("createIntermediateRequest:: creating intermediate request in tenant {}", + intermediateRequestTenantId); + Request request = circulationClient.createRequest(intermediateRequest); + log.info("createIntermediateRequest:: intermediate request {} created in tenant {}", + request.getId(), intermediateRequestTenantId); + + updateCirculationItemOnRequestCreation(circItem, request); + + return new RequestWrapper(request, intermediateRequestTenantId); + } catch (Exception e) { + log.error("createIntermediateRequest:: failed to create intermediate request in tenant {}: {}", + intermediateRequestTenantId, e.getMessage()); + log.debug("createIntermediateRequest:: ", e); + } + + String errorMessage = format( + "Failed to create intermediate request for instance %s, item %s, requester %s in tenant %s", + intermediateRequest.getInstanceId(), intermediateRequest.getItemId(), intermediateRequest.getRequesterId(), intermediateRequestTenantId); + log.error("createIntermediateRequest:: {}", errorMessage); + throw new RequestCreatingException(errorMessage); + } - if (ecsTlr == null || secondaryRequest == null) { - log.info("createCirculationItem:: ECS TLR or secondary request is null, skipping"); + public CirculationItem createCirculationItem(Request request, String inventoryTenantId) { + if (request == null) { + log.warn("createCirculationItem:: request is null, skipping"); + return null; + } + if (inventoryTenantId == null) { + log.warn("createCirculationItem:: inventory tenant ID is null, skipping"); return null; } - var itemId = secondaryRequest.getItemId(); - var instanceId = secondaryRequest.getInstanceId(); + String itemId = request.getItemId(); + String instanceId = request.getInstanceId(); + String pickupLocation = request.getPickupServicePointId(); + + log.info("createCirculationItem:: creating circulation item, params: itemId={}, instanceId={}, " + + "pickupLocation={}, inventoryTenantId={}", itemId, instanceId, pickupLocation, inventoryTenantId); if (itemId == null || instanceId == null) { log.info("createCirculationItem:: item ID is {}, instance ID is {}, skipping", itemId, instanceId); return null; } - // check if circulation item already exists + // Check if circulation item already exists in the tenant we want to create it in CirculationItem existingCirculationItem = circulationItemClient.getCirculationItem(itemId); if (existingCirculationItem != null) { - log.info("createCirculationItem:: circulation item already exists"); + log.info("createCirculationItem:: circulation item already exists in status '{}'", + Optional.ofNullable(existingCirculationItem.getStatus()) + .map(CirculationItemStatus::getName) + .map(CirculationItemStatus.NameEnum::getValue) + .orElse(null)); return existingCirculationItem; } - InventoryItem item = getItemFromStorage(itemId, lendingTenantId); - InventoryInstance instance = getInstanceFromStorage(instanceId, lendingTenantId); + InventoryItem item = getItemFromStorage(itemId, inventoryTenantId); + InventoryInstance instance = getInstanceFromStorage(instanceId, inventoryTenantId); var itemStatus = item.getStatus().getName(); var circulationItemStatus = CirculationItemStatus.NameEnum.fromValue(itemStatus.getValue()); @@ -166,32 +230,28 @@ public CirculationItem createCirculationItem(EcsTlrEntity ecsTlr, Request second .permanentLoanTypeId(item.getPermanentLoanTypeId()) .instanceTitle(instance.getTitle()) .barcode(item.getBarcode()) - .pickupLocation(secondaryRequest.getPickupServicePointId()) + .pickupLocation(pickupLocation) .effectiveLocationId(item.getEffectiveLocationId()) .lendingLibraryCode("TEST_CODE"); - log.info("createCirculationItem:: Creating circulation item {}", circulationItem.toString()); - + log.info("createCirculationItem:: creating circulation item {}", circulationItem.toString()); return circulationItemClient.createCirculationItem(itemId, circulationItem); } @Override public CirculationItem updateCirculationItemOnRequestCreation(CirculationItem circulationItem, - Request secondaryRequest) { + Request request) { if (circulationItem == null) { log.info("updateCirculationItemOnRequestCreation:: circulation item is null, skipping"); return null; } - log.info("updateCirculationItemOnRequestCreation:: updating circulation item {}", circulationItem.getId()); - if (secondaryRequest.getRequestType() == Request.RequestTypeEnum.PAGE) { - log.info("updateCirculationItemOnRequestCreation:: secondary request {} type is " + - "Page, updating circulation item {} with status Paged", secondaryRequest.getId(), - circulationItem.getId()); - + if (request.getRequestType() == Request.RequestTypeEnum.PAGE) { + log.info("updateCirculationItemOnRequestCreation:: request {} type is 'Page', " + + "updating circulation item {} with status 'Paged'", request.getId(), circulationItem.getId()); circulationItem.getStatus().setName(CirculationItemStatus.NameEnum.PAGED); circulationItemClient.updateCirculationItem(circulationItem.getId().toString(), circulationItem); @@ -232,10 +292,13 @@ public Collection getRequestsFromStorage(CqlQuery query, String idIndex log.info("getRequestsFromStorage:: searching requests by query and index: query={}, index={}, ids={}", query, idIndex, ids.size()); log.debug("getRequestsFromStorage:: ids={}", ids); + return BulkFetcher.fetch(requestStorageClient, query, idIndex, ids, Requests::getRequests); + } - Collection requests = BulkFetcher.fetch(requestStorageClient, query, idIndex, ids, - Requests::getRequests); - + @Override + public Collection getRequestsFromStorage(CqlQuery query) { + log.info("getRequestsFromStorage:: searching requests by query: {}", query); + Collection requests = requestStorageClient.getByQuery(query).getRequests(); log.info("getRequestsFromStorage:: found {} requests", requests::size); return requests; } diff --git a/src/main/java/org/folio/service/impl/SearchSlipsService.java b/src/main/java/org/folio/service/impl/SearchSlipsService.java new file mode 100644 index 00000000..a8156dfd --- /dev/null +++ b/src/main/java/org/folio/service/impl/SearchSlipsService.java @@ -0,0 +1,50 @@ +package org.folio.service.impl; + +import static org.folio.domain.dto.ItemStatus.NameEnum.AWAITING_DELIVERY; +import static org.folio.domain.dto.ItemStatus.NameEnum.CHECKED_OUT; +import static org.folio.domain.dto.ItemStatus.NameEnum.IN_PROCESS; +import static org.folio.domain.dto.ItemStatus.NameEnum.IN_TRANSIT; +import static org.folio.domain.dto.ItemStatus.NameEnum.MISSING; +import static org.folio.domain.dto.ItemStatus.NameEnum.ON_ORDER; +import static org.folio.domain.dto.ItemStatus.NameEnum.PAGED; +import static org.folio.domain.dto.ItemStatus.NameEnum.RESTRICTED; +import static org.folio.domain.dto.Request.RequestTypeEnum.HOLD; +import static org.folio.domain.dto.Request.StatusEnum.OPEN_NOT_YET_FILLED; + +import java.util.EnumSet; + +import org.folio.domain.dto.ItemStatus; +import org.folio.service.AddressTypeService; +import org.folio.service.ConsortiaService; +import org.folio.service.DepartmentService; +import org.folio.service.InventoryService; +import org.folio.service.LocationService; +import org.folio.service.RequestService; +import org.folio.service.ServicePointService; +import org.folio.service.UserGroupService; +import org.folio.service.UserService; +import org.folio.spring.service.SystemUserScopedExecutionService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import lombok.extern.log4j.Log4j2; + +@Service +@Log4j2 +public class SearchSlipsService extends StaffSlipsServiceImpl { + + private static final EnumSet ITEM_STATUSES = EnumSet.of( + CHECKED_OUT, AWAITING_DELIVERY, IN_TRANSIT, MISSING, PAGED, ON_ORDER, IN_PROCESS, RESTRICTED); + + @Autowired + public SearchSlipsService(LocationService locationService, InventoryService inventoryService, + RequestService requestService, ConsortiaService consortiaService, + SystemUserScopedExecutionService executionService, UserService userService, + UserGroupService userGroupService, DepartmentService departmentService, + AddressTypeService addressTypeService, ServicePointService servicePointService) { + + super(ITEM_STATUSES, EnumSet.of(OPEN_NOT_YET_FILLED), EnumSet.of(HOLD), locationService, + inventoryService, requestService, consortiaService, executionService, userService, + userGroupService, departmentService, addressTypeService, servicePointService); + } +} diff --git a/src/main/java/org/folio/service/impl/StaffSlipsServiceImpl.java b/src/main/java/org/folio/service/impl/StaffSlipsServiceImpl.java index 2ec7fc89..f43b8323 100644 --- a/src/main/java/org/folio/service/impl/StaffSlipsServiceImpl.java +++ b/src/main/java/org/folio/service/impl/StaffSlipsServiceImpl.java @@ -1,23 +1,27 @@ package org.folio.service.impl; +import static java.lang.Boolean.TRUE; import static java.util.Collections.emptyList; +import static java.util.Collections.emptyMap; import static java.util.Locale.getISOCountries; import static java.util.function.Function.identity; import static java.util.stream.Collectors.groupingBy; import static java.util.stream.Collectors.joining; -import static java.util.stream.Collectors.mapping; +import static java.util.stream.Collectors.toList; import static java.util.stream.Collectors.toMap; import static java.util.stream.Collectors.toSet; import static org.apache.commons.lang3.StringUtils.firstNonBlank; import static org.apache.commons.lang3.StringUtils.isBlank; +import static org.folio.domain.dto.Request.RequestLevelEnum.TITLE; +import static org.folio.domain.dto.Request.RequestTypeEnum.HOLD; -import java.util.AbstractMap; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Date; import java.util.EnumSet; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Locale; import java.util.Map; @@ -30,8 +34,10 @@ import org.folio.domain.dto.AddressType; import org.folio.domain.dto.Campus; -import org.folio.domain.dto.Contributor; import org.folio.domain.dto.Department; +import org.folio.domain.dto.HoldingsRecord; +import org.folio.domain.dto.Instance; +import org.folio.domain.dto.InstanceContributorsInner; import org.folio.domain.dto.Institution; import org.folio.domain.dto.Item; import org.folio.domain.dto.ItemEffectiveCallNumberComponents; @@ -41,9 +47,6 @@ import org.folio.domain.dto.Location; import org.folio.domain.dto.MaterialType; import org.folio.domain.dto.Request; -import org.folio.domain.dto.SearchHolding; -import org.folio.domain.dto.SearchInstance; -import org.folio.domain.dto.SearchItem; import org.folio.domain.dto.ServicePoint; import org.folio.domain.dto.StaffSlip; import org.folio.domain.dto.StaffSlipItem; @@ -60,7 +63,6 @@ import org.folio.service.InventoryService; import org.folio.service.LocationService; import org.folio.service.RequestService; -import org.folio.service.SearchService; import org.folio.service.ServicePointService; import org.folio.service.StaffSlipsService; import org.folio.service.UserGroupService; @@ -68,7 +70,9 @@ import org.folio.spring.service.SystemUserScopedExecutionService; import org.folio.support.CqlQuery; +import lombok.Getter; import lombok.RequiredArgsConstructor; +import lombok.Setter; import lombok.extern.log4j.Log4j2; @RequiredArgsConstructor @@ -88,39 +92,82 @@ public class StaffSlipsServiceImpl implements StaffSlipsService { private final UserGroupService userGroupService; private final DepartmentService departmentService; private final AddressTypeService addressTypeService; - private final SearchService searchService; private final ServicePointService servicePointService; @Override public Collection getStaffSlips(String servicePointId) { log.info("getStaffSlips:: building staff slips for service point {}", servicePointId); + StaffSlipsContext context = new StaffSlipsContext(); + findLocationsAndItems(servicePointId, context); + if (context.getLocationsByTenant().isEmpty()) { + log.info("getStaffSlips:: found no location for service point {}, doing nothing", servicePointId); + return emptyList(); + } + findRequests(context); + if (context.getRequests().isEmpty()) { + log.info("getStaffSlips:: found no requests to build staff slips for, doing nothing"); + return emptyList(); + } + discardNonRequestedItems(context); + findInstances(context); + findRequesters(context); + findUserGroups(context); + findDepartments(context); + findAddressTypes(context); + findPickupServicePoints(context); + fetchDataFromLendingTenants(context); + + Collection staffSlips = buildStaffSlips(context); + log.info("getStaffSlips:: successfully built {} staff slips", staffSlips::size); + return staffSlips; + } + + private void findHoldRequestsWithoutItems(StaffSlipsContext context) { + if (!relevantRequestTypes.contains(HOLD)) { + log.info("findHoldRequestsWithoutItems:: 'Hold' is not a relevant request type, doing nothing"); + return; + } + + Collection holdRequestsWithoutItems = findTitleLevelHoldsWithoutItems(); + Collection instances = findInstancesForRequests(holdRequestsWithoutItems); + Map> holdings = findHoldingsForHolds(instances, context); - Map> locationsByTenant = findLocations(servicePointId); - Collection locationIds = locationsByTenant.values() + Set relevantInstanceIds = holdings.values() .stream() .flatMap(Collection::stream) - .map(Location::getId) + .map(HoldingsRecord::getInstanceId) .collect(toSet()); - Collection instances = findInstances(locationIds); - Collection itemsInRelevantLocations = getItemsForLocations(instances, locationIds); - Collection requests = findRequests(itemsInRelevantLocations); - Collection requestedItems = filterRequestedItems(itemsInRelevantLocations, requests); - Collection staffSlipContexts = buildStaffSlipContexts(requests, requestedItems, - instances, locationsByTenant); - Collection staffSlips = buildStaffSlips(staffSlipContexts); + List requestsForRelevantInstances = holdRequestsWithoutItems.stream() + .filter(request -> relevantInstanceIds.contains(request.getInstanceId())) + .toList(); - log.info("getStaffSlips:: successfully built {} staff slips", staffSlips::size); - return staffSlips; + log.info("getStaffSlips:: {} of {} hold requests are placed on relevant instances", + requestsForRelevantInstances::size, holdRequestsWithoutItems::size); + + context.getRequests().addAll(requestsForRelevantInstances); + context.getInstanceCache().addAll(instances); } - private Map> findLocations(String servicePointId) { - log.info("findLocations:: searching for locations in all consortium tenants"); - CqlQuery query = CqlQuery.exactMatch("primaryServicePoint", servicePointId); + private void findLocationsAndItems(String servicePointId, StaffSlipsContext staffSlipsContext) { + CqlQuery locationsQuery = CqlQuery.exactMatch("primaryServicePoint", servicePointId); - return getAllConsortiumTenants() - .stream() - .collect(toMap(identity(), tenantId -> findLocations(query, tenantId))); + getAllConsortiumTenants() + .forEach(tenantId -> executionService.executeSystemUserScoped(tenantId, () -> { + log.info("getStaffSlips:: searching for relevant locations and items in tenant {}", tenantId); + Collection locations = locationService.findLocations(locationsQuery); + Map locationsById = toMapById(locations, Location::getId); + + Collection items = findItems(locations); + Collection itemContexts = items.stream() + .map(item -> new ItemContext(item.getId(), item, + locationsById.get(item.getEffectiveLocationId()))) + .collect(toList()); + + staffSlipsContext.getLocationsByTenant().put(tenantId, locations); + staffSlipsContext.getItemContextsByTenant().put(tenantId, itemContexts); + return null; + })); } private Collection getAllConsortiumTenants() { @@ -130,317 +177,279 @@ private Collection getAllConsortiumTenants() { .collect(toSet()); } - private Collection findLocations(CqlQuery query, String tenantId) { - log.info("findLocations:: searching for locations in tenant {} by query: {}", tenantId, query); - return executionService.executeSystemUserScoped(tenantId, () -> locationService.findLocations(query)); - } - - private Collection findInstances(Collection locationIds) { - log.info("findInstances:: searching for instances"); - if (locationIds.isEmpty()) { - log.info("findItems:: no locations to search instances for, doing nothing"); + private Collection findItems(Collection locations) { + if (locations.isEmpty()) { + log.info("findItems:: no locations to search items for, doing nothing"); return emptyList(); } - List itemStatusStrings = relevantItemStatuses.stream() + Set locationIds = locations.stream() + .map(Location::getId) + .collect(toSet()); + + Set itemStatuses = relevantItemStatuses.stream() .map(ItemStatus.NameEnum::getValue) - .toList(); + .collect(toSet()); - CqlQuery query = CqlQuery.exactMatchAny("item.status.name", itemStatusStrings); + CqlQuery query = CqlQuery.exactMatchAny("status.name", itemStatuses); - return searchService.searchInstances(query, "item.effectiveLocationId", locationIds); + return inventoryService.findItems(query, "effectiveLocationId", locationIds); } - private static Collection getItemsForLocations(Collection instances, - Collection locationIds) { + private void findRequests(StaffSlipsContext context) { + log.info("findRequestsForItems:: searching for requests for relevant items"); - log.info("getItemsForLocations:: searching for items in relevant locations"); - List items = instances.stream() - .map(SearchInstance::getItems) + List itemIds = context.getItemContextsByTenant() + .values() + .stream() .flatMap(Collection::stream) - .filter(item -> locationIds.contains(item.getEffectiveLocationId())) + .map(ItemContext::getItem) + .map(Item::getId) .toList(); - log.info("getItemsForLocations:: found {} items in relevant locations", items::size); - return items; - } - - private Collection findRequests(Collection items) { - log.info("findRequests:: searching for requests for relevant items"); - if (items.isEmpty()) { - log.info("findRequests:: no items to search requests for, doing nothing"); - return emptyList(); + if (itemIds.isEmpty()) { + log.info("findRequestsForItems:: no items to search requests for, doing nothing"); + return; } - Set itemIds = items.stream() - .map(SearchItem::getId) - .collect(toSet()); - List requestTypes = relevantRequestTypes.stream() .map(Request.RequestTypeEnum::getValue) - .toList(); + .collect(toList()); List requestStatuses = relevantRequestStatuses.stream() .map(Request.StatusEnum::getValue) - .toList(); + .collect(toList()); CqlQuery query = CqlQuery.exactMatchAny("requestType", requestTypes) .and(CqlQuery.exactMatchAny("status", requestStatuses)); - return requestService.getRequestsFromStorage(query, "itemId", itemIds); + Collection requests = requestService.getRequestsFromStorage(query, "itemId", itemIds); + context.getRequests().addAll(requests); + findHoldRequestsWithoutItems(context); } - private static Collection filterRequestedItems(Collection items, - Collection requests) { - - log.info("filterItemsByRequests:: filtering out non-requested items"); - Set requestedItemIds = requests.stream() - .map(Request::getItemId) - .filter(Objects::nonNull) - .collect(toSet()); + private Collection findTitleLevelHoldsWithoutItems() { + log.info("findHoldRequestsWithoutItem:: searching for open hold requests without itemId"); + List requestStatuses = relevantRequestStatuses.stream() + .map(Request.StatusEnum::getValue) + .collect(toList()); - List requestedItems = items.stream() - .filter(item -> requestedItemIds.contains(item.getId())) - .toList(); + CqlQuery query = CqlQuery.exactMatch("requestType", HOLD.getValue()) + .and(CqlQuery.exactMatch("requestLevel", TITLE.getValue())) + .and(CqlQuery.exactMatchAny("status", requestStatuses)) + .not(CqlQuery.match("itemId", "")); - log.info("filterItemsByRequests:: {} of {} relevant items are requested", requestedItems::size, - items::size); - return requestedItems; + return requestService.getRequestsFromStorage(query); } - private Collection buildStaffSlipContexts(Collection requests, - Collection requestedItems, Collection instances, - Map> locationsByTenant) { + private Map> findHoldingsForHolds(Collection instances, + StaffSlipsContext context) { - if (requests.isEmpty()) { - log.info("buildStaffSlipContexts:: no requests to build contexts for, doing nothing"); - return emptyList(); + log.info("findHoldingsForHolds:: searching holdings for instances"); + + if (instances.isEmpty()) { + log.info("findHoldingsForHolds:: no instances to search holdings for, doing nothing"); + return emptyMap(); } - log.info("buildStaffSlipContexts:: building contexts for {} requests", requests::size); - Map itemContextsByItemId = buildItemContexts(requestedItems, instances, - locationsByTenant); - Map requesterContextsByRequestId = buildRequesterContexts(requests); - Map requestContextsByRequestId = buildRequestContexts(requests); - - Collection staffSlipContexts = requests.stream() - .map(request -> new StaffSlipContext( - itemContextsByItemId.get(request.getItemId()), - requesterContextsByRequestId.get(request.getId()), - requestContextsByRequestId.get(request.getId()))) - .toList(); + Set instanceIds = instances.stream() + .map(Instance::getId) + .collect(toSet()); - log.info("getStaffSlips:: successfully built contexts for {} requests", requests::size); - return staffSlipContexts; + return context.getLocationsByTenant() + .keySet() + .stream() + .collect(toMap(identity(), tenantId -> executionService.executeSystemUserScoped(tenantId, + () -> findHoldingsForHolds(instanceIds, context, tenantId)))); } - private Map buildItemContexts(Collection requestedItems, - Collection instances, Map> locationsByTenant) { + private Collection findHoldingsForHolds(Collection instanceIds, + StaffSlipsContext context, String tenantId) { - log.info("buildItemContexts:: building contexts for {} items", requestedItems::size); + log.info("findHoldings:: searching holdings for relevant locations and instances"); - Map> requestedItemIdsByTenant = requestedItems.stream() - .collect(groupingBy(SearchItem::getTenantId, mapping(SearchItem::getId, toSet()))); + Set relevantLocationIds = context.getLocationsByTenant() + .get(tenantId) + .stream() + .map(Location::getId) + .collect(toSet()); - Map itemIdToInstance = instances.stream() - .flatMap(searchInstance -> searchInstance.getItems().stream() - .map(item -> new AbstractMap.SimpleEntry<>(item.getId(), searchInstance))) - .collect(toMap(Map.Entry::getKey, Map.Entry::getValue, (a, b) -> a)); + if (relevantLocationIds.isEmpty()) { + log.info("findHoldings:: no location to search holdings for, doing nothing"); + return emptyList(); + } - return requestedItemIdsByTenant.entrySet() - .stream() - .map(entry -> buildItemContexts(entry.getKey(), entry.getValue(), locationsByTenant, itemIdToInstance)) - .flatMap(Collection::stream) - .collect(toMap(context -> context.item().getId(), identity())); - } + if (instanceIds.isEmpty()) { + log.info("findHoldings:: no instances to search holdings for, doing nothing"); + return emptyList(); + } + + Collection holdingsForInstances = inventoryService.findHoldings(CqlQuery.empty(), + "instanceId", instanceIds); - private Collection buildItemContexts(String tenantId, Collection itemIds, - Map> locationsByTenant, Map itemIdToInstance) { + log.info("findHoldingsForHolds:: caching {} holdings", holdingsForInstances::size); + context.getHoldingsByIdCache().put(tenantId, holdingsForInstances); - log.info("buildItemContexts:: building item contexts for {} items in tenant {}", itemIds.size(), tenantId); - return executionService.executeSystemUserScoped(tenantId, - () -> buildItemContexts(itemIds, itemIdToInstance, locationsByTenant.get(tenantId))); + List holdingsInRelevantLocations = holdingsForInstances.stream() + .filter(holding -> relevantLocationIds.contains(holding.getEffectiveLocationId())) + .collect(toList()); + + log.info("findHoldings:: {} of {} holdings are in relevant locations", + holdingsInRelevantLocations::size, holdingsForInstances::size); + + return holdingsInRelevantLocations; } - private Collection buildItemContexts(Collection itemIds, - Map itemIdToInstance, Collection locations) { + private void findHoldings(StaffSlipsContext context, String tenantId) { + log.info("findHoldings:: searching holdings"); - Collection items = inventoryService.findItems(itemIds); + Collection itemContexts = context.getItemContextsByTenant().get(tenantId); + Set requestedHoldingIds = itemContexts.stream() + .map(ItemContext::getItem) + .map(Item::getHoldingsRecordId) + .collect(toSet()); - Map materialTypesById = findMaterialTypes(items) + Map cachedHoldingsById = context.getHoldingsByIdCache() + .getOrDefault(tenantId, new ArrayList<>()) .stream() - .collect(mapById(MaterialType::getId)); + .collect(mapById(HoldingsRecord::getId)); + + Set missingHoldingIds = new HashSet<>(requestedHoldingIds); + missingHoldingIds.removeAll(cachedHoldingsById.keySet()); + + log.info("findHoldings:: cache hit for {} of {} requested holdings", + requestedHoldingIds.size() - missingHoldingIds.size(), requestedHoldingIds.size()); - Map loanTypesById = findLoanTypes(items) + Map fetchedHoldingsById = inventoryService.findHoldings(missingHoldingIds) .stream() - .collect(mapById(LoanType::getId)); + .collect(mapById(HoldingsRecord::getId)); - Set locationIdsOfRequestedItems = items.stream() - .map(Item::getEffectiveLocationId) - .collect(toSet()); + itemContexts.forEach(itemContext -> { + String holdingsRecordId = itemContext.getItem().getHoldingsRecordId(); + Optional.ofNullable(cachedHoldingsById.get(holdingsRecordId)) + .or(() -> Optional.ofNullable(fetchedHoldingsById.get(holdingsRecordId))) + .ifPresent(itemContext::setHolding); + }); - Map locationsById = locations.stream() - .filter(location -> locationIdsOfRequestedItems.contains(location.getId())) - .toList().stream() - .collect(mapById(Location::getId)); + context.getInstanceCache().clear(); + } - Collection locationsOfRequestedItems = locationsById.values(); + private Collection findInstancesForRequests(Collection requests) { + log.info("findInstances:: searching instances for requests"); + if (requests.isEmpty()) { + log.info("findInstances:: no requests to search instances for, doing nothing"); + return emptyList(); + } - Map librariesById = findLibraries(locationsOfRequestedItems) - .stream() - .collect(mapById(Library::getId)); + Set instanceIds = requests.stream() + .map(Request::getInstanceId) + .collect(toSet()); - Map campusesById = findCampuses(locationsOfRequestedItems) - .stream() - .collect(mapById(Campus::getId)); + return inventoryService.findInstances(instanceIds); + } - Map institutionsById = findInstitutions(locationsOfRequestedItems) + private void findInstances(StaffSlipsContext context) { + log.info("findInstances:: searching instances"); + Set requestedInstanceIds = context.getRequests() .stream() - .collect(mapById(Institution::getId)); + .map(Request::getInstanceId) + .collect(toSet()); - Map servicePointsById = findServicePointsForLocations(locationsOfRequestedItems) + Map cachedRequestedInstancesById = context.getInstanceCache() .stream() - .collect(mapById(ServicePoint::getId)); - - List itemContexts = new ArrayList<>(items.size()); - for (Item item : items) { - SearchInstance instance = itemIdToInstance.get(item.getId()); - Location location = locationsById.get(item.getEffectiveLocationId()); - ServicePoint primaryServicePoint = Optional.ofNullable(location.getPrimaryServicePoint()) - .map(UUID::toString) - .map(servicePointsById::get) - .orElse(null); - SearchHolding holding = instance.getHoldings() - .stream() - .filter(h -> item.getHoldingsRecordId().equals(h.getId())) - .findFirst() - .orElse(null); - - ItemContext itemContext = new ItemContext(item, instance, holding, location, - materialTypesById.get(item.getMaterialTypeId()), - loanTypesById.get(getEffectiveLoanTypeId(item)), - institutionsById.get(location.getInstitutionId()), - campusesById.get(location.getCampusId()), - librariesById.get(location.getLibraryId()), - primaryServicePoint); - - itemContexts.add(itemContext); - } + .filter(instance -> requestedInstanceIds.contains(instance.getId())) + .collect(mapById(Instance::getId)); - return itemContexts; - } - - private Map buildRequesterContexts(Collection requests) { - log.info("buildRequesterContexts:: building requester contexts for {} requests", requests::size); - Collection requesters = findRequesters(requests); - Collection userGroups = findUserGroups(requesters); - Collection departments = findDepartments(requesters); - Collection addressTypes = findAddressTypes(requesters); - - Map requestersById = requesters.stream() - .collect(mapById(User::getId)); - Map userGroupsById = userGroups.stream() - .collect(mapById(UserGroup::getId)); - Map departmentsById = departments.stream() - .collect(mapById(Department::getId)); - Map addressTypesById = addressTypes.stream() - .collect(mapById(AddressType::getId)); - - Map requesterContexts = new HashMap<>(requests.size()); - for (Request request : requests) { - User requester = requestersById.get(request.getRequesterId()); - UserGroup userGroup = userGroupsById.get(requester.getPatronGroup()); - - Collection requesterDepartments = requester.getDepartments() - .stream() - .filter(Objects::nonNull) - .map(departmentsById::get) - .toList(); - - AddressType primaryRequesterAddressType = Optional.ofNullable(requester.getPersonal()) - .map(UserPersonal::getAddresses) - .flatMap(addresses -> addresses.stream() - .filter(UserPersonalAddressesInner::getPrimaryAddress) - .findFirst() - .map(UserPersonalAddressesInner::getAddressTypeId) - .map(addressTypesById::get)) - .orElse(null); + Set missingInstanceIds = new HashSet<>(requestedInstanceIds); + missingInstanceIds.removeAll(cachedRequestedInstancesById.keySet()); - AddressType deliveryAddressType = addressTypesById.get(request.getDeliveryAddressTypeId()); + log.info("findInstances:: cache hit for {} of {} requested instances", + requestedInstanceIds.size() - missingInstanceIds.size(), requestedInstanceIds.size()); - RequesterContext requesterContext = new RequesterContext(requester, userGroup, - requesterDepartments, primaryRequesterAddressType, deliveryAddressType); - requesterContexts.put(request.getId(), requesterContext); - } + Map fetchedInstancesById = inventoryService.findInstances(missingInstanceIds) + .stream() + .collect(mapById(Instance::getId)); - return requesterContexts; + context.getInstancesById().putAll(fetchedInstancesById); + context.getInstancesById().putAll(cachedRequestedInstancesById); + context.getInstanceCache().clear(); } - private Map buildRequestContexts(Collection requests) { - log.info("buildRequesterContexts:: building request contexts for {} requests", requests::size); - Collection servicePoints = findServicePointsForRequests(requests); - Map servicePointsById = servicePoints.stream() - .collect(mapById(ServicePoint::getId)); - - Map requestContexts = new HashMap<>(requests.size()); - for (Request request : requests) { - ServicePoint pickupServicePoint = servicePointsById.get(request.getPickupServicePointId()); - RequestContext requestContext = new RequestContext(request, pickupServicePoint); - requestContexts.put(request.getId(), requestContext); - } + private void fetchDataFromLendingTenants(StaffSlipsContext context) { + context.getItemContextsByTenant() + .keySet() + .forEach(tenantId -> executionService.executeSystemUserScoped(tenantId, + () -> fetchDataFromLendingTenant(context, tenantId))); + } - return requestContexts; + private StaffSlipsContext fetchDataFromLendingTenant(StaffSlipsContext context, String tenantId) { + log.info("fetchDataFromLendingTenant:: fetching item-related data from tenant {}", tenantId); + Collection itemContexts = context.getItemContextsByTenant().get(tenantId); + findHoldings(context, tenantId); + findMaterialTypes(itemContexts); + findLoanTypes(itemContexts); + findLibraries(itemContexts); + findCampuses(itemContexts); + findInstitutions(itemContexts); + findPrimaryServicePoints(itemContexts); + return context; } - private Collection findRequesters(Collection requests) { - if (requests.isEmpty()) { + private void findRequesters(StaffSlipsContext context) { + if (context.getRequests().isEmpty()) { log.info("findRequesters:: no requests to search requesters for, doing nothing"); - return emptyList(); + return; } - Set requesterIds = requests.stream() + Set requesterIds = context.getRequests().stream() .map(Request::getRequesterId) .collect(toSet()); - return userService.find(requesterIds); + Collection users = userService.find(requesterIds); + context.getRequestersById().putAll(toMapById(users, User::getId)); } - private Collection findUserGroups(Collection requesters) { - if (requesters.isEmpty()) { + private void findUserGroups(StaffSlipsContext context) { + if (context.getRequestersById().isEmpty()) { log.info("findUserGroups:: no requesters to search user groups for, doing nothing"); - return emptyList(); + return; } - Set userGroupIds = requesters.stream() + Set userGroupIds = context.getRequestersById().values() + .stream() .map(User::getPatronGroup) .filter(Objects::nonNull) .collect(toSet()); - return userGroupService.find(userGroupIds); + Collection userGroups = userGroupService.find(userGroupIds); + context.getUserGroupsById().putAll(toMapById(userGroups, UserGroup::getId)); } - private Collection findDepartments(Collection requesters) { - if (requesters.isEmpty()) { + private void findDepartments(StaffSlipsContext context) { + if (context.getRequestersById().isEmpty()) { log.info("findDepartments:: no requesters to search departments for, doing nothing"); - return emptyList(); + return; } - Set departmentIds = requesters.stream() + Set departmentIds = context.getRequestersById().values() + .stream() .map(User::getDepartments) .filter(Objects::nonNull) .flatMap(Collection::stream) .collect(toSet()); - return departmentService.findDepartments(departmentIds); + Collection departments = departmentService.findDepartments(departmentIds); + context.getDepartmentsById().putAll(toMapById(departments, Department::getId)); } - private Collection findAddressTypes(Collection requesters) { - if (requesters.isEmpty()) { + private void findAddressTypes(StaffSlipsContext context) { + if (context.getRequestersById().isEmpty()) { log.info("findAddressTypes:: no requesters to search address types for, doing nothing"); - return emptyList(); + return; } - Set addressTypeIds = requesters.stream() + Set addressTypeIds = context.getRequestersById().values() + .stream() .map(User::getPersonal) .filter(Objects::nonNull) .map(UserPersonal::getAddresses) @@ -449,26 +458,24 @@ private Collection findAddressTypes(Collection requesters) { .map(UserPersonalAddressesInner::getAddressTypeId) .collect(toSet()); - return addressTypeService.findAddressTypes(addressTypeIds); + Collection addressTypes = addressTypeService.findAddressTypes(addressTypeIds); + context.getAddressTypesById().putAll(toMapById(addressTypes, AddressType::getId)); } - private Collection findServicePointsForLocations(Collection locations) { - return findServicePoints( - locations.stream() - .map(Location::getPrimaryServicePoint) - .filter(Objects::nonNull) - .map(UUID::toString) - .collect(toSet()) - ); - } + private void findPickupServicePoints(StaffSlipsContext context) { + if ( context.getRequests().isEmpty()) { + log.info("findPickupServicePoints:: no requests to search service points for, doing nothing"); + return; + } - private Collection findServicePointsForRequests(Collection requests) { - return findServicePoints( - requests.stream() - .map(Request::getPickupServicePointId) - .filter(Objects::nonNull) - .collect(toSet()) - ); + Set pickupServicePointIds = context.getRequests() + .stream() + .map(Request::getPickupServicePointId) + .filter(Objects::nonNull) + .collect(toSet()); + + Collection pickupServicePoints = findServicePoints(pickupServicePointIds); + context.getPickupServicePointsById().putAll(toMapById(pickupServicePoints, ServicePoint::getId)); } private Collection findServicePoints(Collection servicePointIds) { @@ -480,111 +487,146 @@ private Collection findServicePoints(Collection servicePoi return servicePointService.find(servicePointIds); } - private Collection findMaterialTypes(Collection items) { - if (items.isEmpty()) { + private void findMaterialTypes(Collection itemContexts) { + if (itemContexts.isEmpty()) { log.info("findMaterialTypes:: no items to search material types for, doing nothing"); - return emptyList(); + return; } - Set materialTypeIds = items.stream() - .map(Item::getMaterialTypeId) - .collect(toSet()); + Map> contextsByMaterialTypeId = itemContexts.stream() + .collect(groupingBy(context -> context.getItem().getMaterialTypeId())); - return inventoryService.findMaterialTypes(materialTypeIds); + inventoryService.findMaterialTypes(contextsByMaterialTypeId.keySet()) + .forEach(materialType -> contextsByMaterialTypeId.get(materialType.getId()) + .forEach(context -> context.setMaterialType(materialType))); } - private Collection findLoanTypes(Collection items) { - if (items.isEmpty()) { + private void findLoanTypes(Collection itemContexts) { + if (itemContexts.isEmpty()) { log.info("findLoanTypes:: no items to search loan types for, doing nothing"); - return emptyList(); + return; } - Set loanTypeIds = items.stream() - .map(StaffSlipsServiceImpl::getEffectiveLoanTypeId) - .collect(toSet()); + Map> contextsByLoanTypeId = itemContexts.stream() + .collect(groupingBy(context -> getEffectiveLoanTypeId(context.getItem()))); - return inventoryService.findLoanTypes(loanTypeIds); + inventoryService.findLoanTypes(contextsByLoanTypeId.keySet()) + .forEach(loanType -> contextsByLoanTypeId.get(loanType.getId()) + .forEach(context -> context.setLoanType(loanType))); } - private Collection findLibraries(Collection locations) { - if (locations.isEmpty()) { - log.info("findLibraries:: no locations to search libraries for, doing nothing"); - return emptyList(); + private void findLibraries(Collection itemContexts) { + if (itemContexts.isEmpty()) { + log.info("findLibraries:: no items to search libraries for, doing nothing"); + return; } - Set libraryIds = locations.stream() - .map(Location::getLibraryId) - .collect(toSet()); + Map> contextsByLibraryId = itemContexts.stream() + .collect(groupingBy(context -> context.getLocation().getLibraryId())); - return inventoryService.findLibraries(libraryIds); + inventoryService.findLibraries(contextsByLibraryId.keySet()) + .forEach(library -> contextsByLibraryId.get(library.getId()) + .forEach(context -> context.setLibrary(library))); } - private Collection findCampuses(Collection locations) { - if (locations.isEmpty()) { - log.info("findCampuses:: no locations to search campuses for, doing nothing"); - return emptyList(); + private void findCampuses(Collection itemContexts) { + if (itemContexts.isEmpty()) { + log.info("findCampuses:: no items to search campuses for, doing nothing"); + return; } - Set campusIds = locations.stream() - .map(Location::getCampusId) - .collect(toSet()); + Map> contextsByCampusId = itemContexts.stream() + .collect(groupingBy(context -> context.getLocation().getCampusId())); - return inventoryService.findCampuses(campusIds); + inventoryService.findCampuses(contextsByCampusId.keySet()) + .forEach(campus -> contextsByCampusId.get(campus.getId()) + .forEach(context -> context.setCampus(campus))); } - private Collection findInstitutions(Collection locations) { - if (locations.isEmpty()) { - log.info("findCampuses:: no locations to search institutions for, doing nothing"); - return emptyList(); + private void findInstitutions(Collection itemContexts) { + if (itemContexts.isEmpty()) { + log.info("findInstitutions:: no items to search institutions for, doing nothing"); + return; } - Set institutionIds = locations.stream() - .map(Location::getInstitutionId) - .collect(toSet()); + Map> contextsByInstitutionId = itemContexts.stream() + .collect(groupingBy(context -> context.getLocation().getInstitutionId())); - return inventoryService.findInstitutions(institutionIds); + inventoryService.findInstitutions(contextsByInstitutionId.keySet()) + .forEach(institution -> contextsByInstitutionId.get(institution.getId()) + .forEach(context -> context.setInstitution(institution))); } - private static Collection buildStaffSlips(Collection contexts) { - log.info("buildStaffSlips:: building staff slips for {} contexts", contexts::size); - return contexts.stream() - .map(StaffSlipsServiceImpl::buildStaffSlip) + private void findPrimaryServicePoints(Collection itemContexts) { + if (itemContexts.isEmpty()) { + log.info("findPrimaryServicePoints:: no items to search institutions for, doing nothing"); + return; + } + + Map> contextsByPrimaryServicePointId = itemContexts.stream() + .collect(groupingBy(context -> context.getLocation().getPrimaryServicePoint().toString())); + + findServicePoints(contextsByPrimaryServicePointId.keySet()) + .forEach(servicePoint -> contextsByPrimaryServicePointId.get(servicePoint.getId()) + .forEach(context -> context.setPrimaryServicePoint(servicePoint))); + } + + + private static Collection buildStaffSlips(StaffSlipsContext context) { + return context.getRequests() + .stream() + .map(request -> buildStaffSlip(request, context)) .toList(); } - private static StaffSlip buildStaffSlip(StaffSlipContext context) { - log.info("buildStaffSlip:: building staff slip for request {}", - context.requestContext.request().getId()); + private static StaffSlip buildStaffSlip(Request request, StaffSlipsContext context) { + log.info("buildStaffSlip:: building staff slip for request {}", request.getId()); return new StaffSlip() .currentDateTime(new Date()) - .item(buildStaffSlipItem(context)) - .request(buildStaffSlipRequest(context)) - .requester(buildStaffSlipRequester(context)); + .item(buildStaffSlipItem(request, context)) + .request(buildStaffSlipRequest(request, context)) + .requester(buildStaffSlipRequester(request, context)); } - private static StaffSlipItem buildStaffSlipItem(StaffSlipContext context) { + private static StaffSlipItem buildStaffSlipItem(Request request, StaffSlipsContext context) { log.debug("buildStaffSlipItem:: building staff slip item"); - ItemContext itemContext = context.itemContext(); - Item item = itemContext.item(); - if (item == null) { - log.warn("buildStaffSlipItem:: item is null, doing nothing"); + String itemId = request.getItemId(); + if (itemId == null) { + log.info("buildStaffSlipItem:: request is not linked to an item, doing nothing"); + return null; + } + + ItemContext itemContext = context.getItemContextsByTenant() + .values() + .stream() + .flatMap(Collection::stream) + .filter(ctx -> itemId.equals(ctx.getItemId())) + .findFirst() + .orElse(null); + + if (itemContext == null) { + log.warn("buildStaffSlipItem:: item context for request {} was not found, doing nothing", + request.getId()); return null; } + Item item = itemContext.getItem(); + String yearCaptions = Optional.ofNullable(item.getYearCaption()) .map(captions -> String.join("; ", captions)) .orElse(null); String copyNumber = Optional.ofNullable(item.getCopyNumber()) - .or(() -> Optional.ofNullable(itemContext.holding().getCopyNumber())) + .or(() -> Optional.ofNullable(itemContext.getHolding()) + .map(HoldingsRecord::getCopyNumber)) .orElse(""); - String materialType = Optional.ofNullable(itemContext.materialType) + String materialType = Optional.ofNullable(itemContext.getMaterialType()) .map(MaterialType::getName) .orElse(null); - String loanType = Optional.ofNullable(itemContext.loanType()) + String loanType = Optional.ofNullable(itemContext.getLoanType()) .map(LoanType::getName) .orElse(null); @@ -602,20 +644,19 @@ private static StaffSlipItem buildStaffSlipItem(StaffSlipContext context) { .displaySummary(item.getDisplaySummary()) .descriptionOfPieces(item.getDescriptionOfPieces()); - SearchInstance instance = itemContext.instance(); + Instance instance = context.getInstancesById().get(request.getInstanceId()); if (instance != null) { staffSlipItem.title(instance.getTitle()); - - List contributors = instance.getContributors(); + List contributors = instance.getContributors(); if (contributors != null && !contributors.isEmpty()) { String primaryContributor = contributors.stream() - .filter(Contributor::getPrimary) + .filter(InstanceContributorsInner::getPrimary) .findFirst() - .map(Contributor::getName) + .map(InstanceContributorsInner::getName) .orElse(null); String allContributors = contributors.stream() - .map(Contributor::getName) + .map(InstanceContributorsInner::getName) .collect(joining("; ")); staffSlipItem @@ -625,25 +666,25 @@ private static StaffSlipItem buildStaffSlipItem(StaffSlipContext context) { } } - Location location = itemContext.location(); + Location location = itemContext.getLocation(); if (location != null) { staffSlipItem .effectiveLocationSpecific(location.getName()) .effectiveLocationDiscoveryDisplayName(location.getDiscoveryDisplayName()); - Optional.ofNullable(itemContext.library()) + Optional.ofNullable(itemContext.getLibrary()) .map(Library::getName) .ifPresent(staffSlipItem::effectiveLocationLibrary); - Optional.ofNullable(itemContext.campus()) + Optional.ofNullable(itemContext.getCampus()) .map(Campus::getName) .ifPresent(staffSlipItem::effectiveLocationCampus); - Optional.ofNullable(itemContext.institution()) + Optional.ofNullable(itemContext.getInstitution()) .map(Institution::getName) .ifPresent(staffSlipItem::effectiveLocationInstitution); - Optional.ofNullable(itemContext.primaryServicePoint()) + Optional.ofNullable(itemContext.getPrimaryServicePoint()) .map(ServicePoint::getName) .ifPresent(staffSlipItem::effectiveLocationPrimaryServicePointName); } @@ -658,25 +699,25 @@ private static StaffSlipItem buildStaffSlipItem(StaffSlipContext context) { return staffSlipItem; } - private static StaffSlipRequest buildStaffSlipRequest(StaffSlipContext context) { + private static StaffSlipRequest buildStaffSlipRequest(Request request, StaffSlipsContext context) { log.debug("buildStaffSlipItem:: building staff slip request"); - RequestContext requestContext = context.requestContext(); - Request request = requestContext.request(); if (request == null) { log.warn("buildStaffSlipRequest:: request is null, doing nothing"); return null; } - String deliveryAddressType = Optional.ofNullable(context.requesterContext.deliveryAddressType()) + String deliveryAddressType = Optional.ofNullable(request.getDeliveryAddressTypeId()) + .map(context.getAddressTypesById()::get) .map(AddressType::getAddressType) .orElse(null); - String pickupServicePoint = Optional.ofNullable(requestContext.pickupServicePoint()) + String pickupServicePoint = Optional.ofNullable(request.getPickupServicePointId()) + .map(context.getPickupServicePointsById()::get) .map(ServicePoint::getName) .orElse(null); return new StaffSlipRequest() - .requestId(UUID.fromString(request.getId())) + .requestID(UUID.fromString(request.getId())) .servicePointPickup(pickupServicePoint) .requestDate(request.getRequestDate()) .requestExpirationDate(request.getRequestExpirationDate()) @@ -686,21 +727,23 @@ private static StaffSlipRequest buildStaffSlipRequest(StaffSlipContext context) .patronComments(request.getPatronComments()); } - private static StaffSlipRequester buildStaffSlipRequester(StaffSlipContext context) { + private static StaffSlipRequester buildStaffSlipRequester(Request request, StaffSlipsContext context) { log.debug("buildStaffSlipItem:: building staff slip requester"); - RequesterContext requesterContext = context.requesterContext(); - User requester = requesterContext.requester(); + User requester = context.getRequestersById().get(request.getRequesterId()); if (requester == null) { log.warn("buildStaffSlipRequester:: requester is null, doing nothing"); return null; } - String departments = requesterContext.departments() + String departments = requester.getDepartments() .stream() + .filter(Objects::nonNull) + .map(context.getDepartmentsById()::get) + .filter(Objects::nonNull) .map(Department::getName) .collect(joining("; ")); - String patronGroup = Optional.ofNullable(requesterContext.userGroup()) + String patronGroup = Optional.ofNullable(context.getUserGroupsById().get(requester.getPatronGroup())) .map(UserGroup::getGroup) .orElse(""); @@ -714,14 +757,6 @@ private static StaffSlipRequester buildStaffSlipRequester(StaffSlipContext conte String preferredFirstName = Optional.ofNullable(personal.getPreferredFirstName()) .orElseGet(personal::getFirstName); - String primaryAddressType = Optional.ofNullable(requesterContext.primaryAddressType()) - .map(AddressType::getAddressType) - .orElse(null); - - String deliveryAddressType = Optional.ofNullable(requesterContext.deliveryAddressType()) - .map(AddressType::getAddressType) - .orElse(null); - staffSlipRequester .firstName(personal.getFirstName()) .preferredFirstName(preferredFirstName) @@ -730,10 +765,25 @@ private static StaffSlipRequester buildStaffSlipRequester(StaffSlipContext conte List addresses = personal.getAddresses(); if (addresses != null) { - String deliveryAddressTypeId = context.requestContext().request().getDeliveryAddressTypeId(); + addresses.stream() + .filter(address -> TRUE.equals(address.getPrimaryAddress())) + .findFirst() + .ifPresent(primaryAddress -> staffSlipRequester + .primaryAddressLine1(primaryAddress.getAddressLine1()) + .primaryAddressLine2(primaryAddress.getAddressLine2()) + .primaryCity(primaryAddress.getCity()) + .primaryStateProvRegion(primaryAddress.getRegion()) + .primaryZipPostalCode(primaryAddress.getPostalCode()) + .primaryCountry(getCountryName(primaryAddress.getCountryId())) + .primaryDeliveryAddressType( + Optional.ofNullable(context.getAddressTypesById().get(primaryAddress.getAddressTypeId())) + .map(AddressType::getAddressType) + .orElse(null) + )); + + String deliveryAddressTypeId = request.getDeliveryAddressTypeId(); if (deliveryAddressTypeId != null) { - personal.getAddresses() - .stream() + addresses.stream() .filter(address -> deliveryAddressTypeId.equals(address.getAddressTypeId())) .findFirst() .ifPresent(deliveryAddress -> staffSlipRequester @@ -743,31 +793,25 @@ private static StaffSlipRequester buildStaffSlipRequester(StaffSlipContext conte .region(deliveryAddress.getRegion()) .postalCode(deliveryAddress.getPostalCode()) .countryId(deliveryAddress.getCountryId()) - .addressType(deliveryAddressType) - ); + .addressType( + Optional.ofNullable(context.getAddressTypesById().get(deliveryAddressTypeId)) + .map(AddressType::getAddressType) + .orElse(null) + )); } - - personal.getAddresses() - .stream() - .filter(UserPersonalAddressesInner::getPrimaryAddress) - .findFirst() - .ifPresent(primaryAddress -> staffSlipRequester - .primaryAddressLine1(primaryAddress.getAddressLine1()) - .primaryAddressLine2(primaryAddress.getAddressLine2()) - .primaryCity(primaryAddress.getCity()) - .primaryStateProvRegion(primaryAddress.getRegion()) - .primaryZipPostalCode(primaryAddress.getPostalCode()) - .primaryCountry(getCountryName(primaryAddress.getCountryId())) - .primaryDeliveryAddressType(primaryAddressType) - ); } } return staffSlipRequester; } - private static Collector> mapById(Function keyMapper) { - return toMap(keyMapper, identity()); + private static Map toMapById(Collection collection, Function idExtractor) { + return collection.stream() + .collect(mapById(idExtractor)); + } + + private static Collector> mapById(Function idExtractor) { + return toMap(idExtractor, identity()); } private static String getCountryName(String countryCode) { @@ -783,17 +827,54 @@ private static String getEffectiveLoanTypeId(Item item) { return firstNonBlank(item.getTemporaryLoanTypeId(), item.getPermanentLoanTypeId()); } - private record ItemContext(Item item, SearchInstance instance, SearchHolding holding, - Location location, MaterialType materialType, LoanType loanType, Institution institution, - Campus campus, Library library, ServicePoint primaryServicePoint) {} + private static void discardNonRequestedItems(StaffSlipsContext context) { + log.info("discardNonRequestedItems:: discarding non-requested items"); - private record RequesterContext(User requester, UserGroup userGroup, - Collection departments, AddressType primaryAddressType, - AddressType deliveryAddressType) {} - - private record RequestContext(Request request, ServicePoint pickupServicePoint) { } + Set requestedItemIds = context.getRequests() + .stream() + .map(Request::getItemId) + .filter(Objects::nonNull) + .collect(toSet()); - private record StaffSlipContext(ItemContext itemContext, RequesterContext requesterContext, - RequestContext requestContext) {} + context.getItemContextsByTenant() + .values() + .forEach(itemContexts -> itemContexts.removeIf( + itemContext -> !requestedItemIds.contains(itemContext.getItemId()))); + + context.getItemContextsByTenant() + .entrySet() + .removeIf(entry -> entry.getValue().isEmpty()); + } + + @Getter + private static class StaffSlipsContext { + private final Collection requests = new ArrayList<>(); + private final Map instancesById = new HashMap<>(); + private final Map requestersById = new HashMap<>(); + private final Map userGroupsById = new HashMap<>(); + private final Map departmentsById = new HashMap<>(); + private final Map addressTypesById = new HashMap<>(); + private final Map pickupServicePointsById = new HashMap<>(); + private final Map> itemContextsByTenant = new HashMap<>(); + private final Map> locationsByTenant = new HashMap<>(); + private final Map> holdingsByIdCache = new HashMap<>(); + private final Collection instanceCache = new ArrayList<>(); + } + + @RequiredArgsConstructor + @Getter + @Setter + private static class ItemContext { + private final String itemId; + private final Item item; + private final Location location; + private HoldingsRecord holding; + private MaterialType materialType; + private LoanType loanType; + private Library library; + private Campus campus; + private Institution institution; + private ServicePoint primaryServicePoint; + } } diff --git a/src/main/java/org/folio/service/impl/TenantServiceImpl.java b/src/main/java/org/folio/service/impl/TenantServiceImpl.java index d814cf81..e1e67ca3 100644 --- a/src/main/java/org/folio/service/impl/TenantServiceImpl.java +++ b/src/main/java/org/folio/service/impl/TenantServiceImpl.java @@ -3,6 +3,7 @@ import static com.google.common.base.Predicates.alwaysTrue; import static com.google.common.base.Predicates.notNull; import static java.util.Comparator.comparingLong; +import static java.util.Optional.ofNullable; import static java.util.function.Function.identity; import static java.util.stream.Collectors.collectingAndThen; import static java.util.stream.Collectors.counting; @@ -45,25 +46,25 @@ public class TenantServiceImpl implements TenantService { private final UserTenantsService userTenantsService; @Override - public String getBorrowingTenant(EcsTlrEntity ecsTlr) { - log.info("getBorrowingTenant:: getting borrowing tenant"); + public String getPrimaryRequestTenantId(EcsTlrEntity ecsTlr) { + log.info("getPrimaryRequestTenantId:: getting borrowing tenant"); if (ecsTlr == null || ecsTlr.getPrimaryRequestTenantId() == null) { - log.info("getBorrowingTenant:: central tenant by default"); + log.info("getPrimaryRequestTenantId:: central tenant by default"); return userTenantsService.getCentralTenantId(); } - log.info("getBorrowingTenant:: returning primaryRequestTenantId"); + log.info("getPrimaryRequestTenantId:: returning primaryRequestTenantId"); return ecsTlr.getPrimaryRequestTenantId(); } @Override - public List getLendingTenants(EcsTlrEntity ecsTlr) { + public List getSecondaryRequestTenants(EcsTlrEntity ecsTlr) { final String instanceId = ecsTlr.getInstanceId().toString(); - log.info("getLendingTenants:: looking for potential lending tenants for instance {}", instanceId); + log.info("getSecondaryRequestTenants:: looking for potential secondary request tenants for instance {}", instanceId); var itemStatusOccurrencesByTenant = getItemStatusOccurrencesByTenant(instanceId); - log.info("getLendingTenants:: item status occurrences by tenant: {}", itemStatusOccurrencesByTenant); + log.info("getSecondaryRequestTenants:: item status occurrences by tenant: {}", itemStatusOccurrencesByTenant); - List lendingTenantIds = itemStatusOccurrencesByTenant.entrySet() + List tenantIds = itemStatusOccurrencesByTenant.entrySet() .stream() .sorted(compareByItemCount(AVAILABLE) .thenComparing(compareByItemCount(CHECKED_OUT, IN_TRANSIT)) @@ -71,13 +72,13 @@ public List getLendingTenants(EcsTlrEntity ecsTlr) { .map(Entry::getKey) .toList(); - if (lendingTenantIds.isEmpty()) { - log.warn("getLendingTenants:: failed to find lending tenants for instance {}", instanceId); + if (tenantIds.isEmpty()) { + log.warn("getSecondaryRequestTenants:: failed to find secondary request tenants for instance {}", instanceId); } else { - log.info("getLendingTenants:: found tenants for instance {}: {}", instanceId, lendingTenantIds); + log.info("getSecondaryRequestTenants:: found tenants for instance {}: {}", instanceId, tenantIds); } - return lendingTenantIds; + return tenantIds; } private Map> getItemStatusOccurrencesByTenant(String instanceId) { diff --git a/src/main/java/org/folio/service/impl/UserCloningServiceImpl.java b/src/main/java/org/folio/service/impl/UserCloningServiceImpl.java index 8a8ce135..d8f2ad33 100644 --- a/src/main/java/org/folio/service/impl/UserCloningServiceImpl.java +++ b/src/main/java/org/folio/service/impl/UserCloningServiceImpl.java @@ -1,6 +1,7 @@ package org.folio.service.impl; import org.folio.domain.dto.User; +import org.folio.domain.dto.UserPersonal; import org.folio.domain.dto.UserType; import org.folio.service.UserService; import org.springframework.beans.factory.annotation.Autowired; @@ -38,6 +39,17 @@ protected User buildClone(User original) { .type(UserType.SHADOW.getValue()) .barcode(original.getBarcode()) .active(true); + + // TODO: Remove hardcoded Secure Patron name. mod-tlr shouldn't know about secure requests, + // but there should be a mechanism to let it know that the user's name should also be copied + String firstName = original.getPersonal().getFirstName(); + String lastName = original.getPersonal().getLastName(); + if ("Secure".equals(firstName) && "Patron".equals(lastName)) { + clone.personal(new UserPersonal() + .firstName(firstName) + .lastName(lastName)); + } + log.debug("buildClone:: result: {}", () -> clone); return clone; } diff --git a/src/main/java/org/folio/service/impl/UserTenantsServiceImpl.java b/src/main/java/org/folio/service/impl/UserTenantsServiceImpl.java index bb89d4d9..cce03333 100644 --- a/src/main/java/org/folio/service/impl/UserTenantsServiceImpl.java +++ b/src/main/java/org/folio/service/impl/UserTenantsServiceImpl.java @@ -40,6 +40,5 @@ public UserTenant findFirstUserTenant() { public String getCentralTenantId() { return findFirstUserTenant().getCentralTenantId(); } - } diff --git a/src/main/java/org/folio/support/BulkFetcher.java b/src/main/java/org/folio/support/BulkFetcher.java index 4da7f9ff..195e544e 100644 --- a/src/main/java/org/folio/support/BulkFetcher.java +++ b/src/main/java/org/folio/support/BulkFetcher.java @@ -2,6 +2,7 @@ import static java.util.Collections.emptyList; import static java.util.function.UnaryOperator.identity; +import static java.util.stream.Collectors.toList; import static java.util.stream.Collectors.toMap; import java.util.Collection; @@ -63,9 +64,9 @@ public static List fetch(Collection queries, GetByQueryClien .map(client::getByQuery) .map(collectionExtractor) .flatMap(Collection::stream) - .toList(); + .collect(toList()); - log.info("fetch:: fetched {} objects", result::size); + log.info("fetch:: fetched {} object(s)", result::size); return result; } diff --git a/src/main/java/org/folio/support/CqlQuery.java b/src/main/java/org/folio/support/CqlQuery.java index a1cc20b3..9d7c41e4 100644 --- a/src/main/java/org/folio/support/CqlQuery.java +++ b/src/main/java/org/folio/support/CqlQuery.java @@ -11,6 +11,7 @@ public record CqlQuery(String query) { public static final String MULTIPLE_VALUES_DELIMITER = " or "; public static final String EXACT_MATCH_QUERY_TEMPLATE = "%s==\"%s\""; + public static final String MATCH_QUERY_TEMPLATE = "%s=\"%s\""; public static final String EXACT_MATCH_ANY_QUERY_TEMPLATE = "%s==(%s)"; public static CqlQuery empty() { @@ -21,6 +22,10 @@ public static CqlQuery exactMatch(String index, String value) { return new CqlQuery(format(EXACT_MATCH_QUERY_TEMPLATE, index, value)); } + public static CqlQuery match(String index, String value) { + return new CqlQuery(format(MATCH_QUERY_TEMPLATE, index, value)); + } + public static CqlQuery exactMatchAnyId(Collection values) { return exactMatchAny("id", values); } @@ -51,6 +56,17 @@ public CqlQuery and(CqlQuery other) { return new CqlQuery(format("%s and (%s)", query, other.query())); } + public CqlQuery not(CqlQuery other) { + if (other == null || isBlank(other.query())) { + return this; + } + if (isBlank(query)) { + return other; + } + + return new CqlQuery(format("%s not (%s)", query, other.query())); + } + @Override public String toString() { return query; diff --git a/src/main/resources/db/changelog/changelog-master.xml b/src/main/resources/db/changelog/changelog-master.xml index 23e9c561..e2754435 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/2024-09-05-add-holdings-record-id-column.xml b/src/main/resources/db/changelog/changes/2024-09-05-add-holdings-record-id-column.xml index 93cf8955..af73fb5b 100644 --- a/src/main/resources/db/changelog/changes/2024-09-05-add-holdings-record-id-column.xml +++ b/src/main/resources/db/changelog/changes/2024-09-05-add-holdings-record-id-column.xml @@ -5,6 +5,7 @@ http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-2.0.xsd"> + ANY diff --git a/src/main/resources/db/changelog/changes/2024-10-03-add-intermediate-phase-columns.xml b/src/main/resources/db/changelog/changes/2024-10-03-add-intermediate-phase-columns.xml new file mode 100644 index 00000000..04a65c6c --- /dev/null +++ b/src/main/resources/db/changelog/changes/2024-10-03-add-intermediate-phase-columns.xml @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/resources/db/changelog/changes/initial_schema.xml b/src/main/resources/db/changelog/changes/initial_schema.xml index 02f13d2a..aa54ce8c 100644 --- a/src/main/resources/db/changelog/changes/initial_schema.xml +++ b/src/main/resources/db/changelog/changes/initial_schema.xml @@ -6,6 +6,7 @@ + ANY Create ecs_tlr table @@ -36,6 +37,7 @@ + ANY diff --git a/src/main/resources/swagger.api/schemas/EcsTlr.yaml b/src/main/resources/swagger.api/schemas/EcsTlr.yaml index f9763b8b..a8424fef 100644 --- a/src/main/resources/swagger.api/schemas/EcsTlr.yaml +++ b/src/main/resources/swagger.api/schemas/EcsTlr.yaml @@ -61,6 +61,15 @@ EcsTlr: secondaryRequestTenantId: description: "ID of the tenant secondary request was created in" type: string + intermediateRequestId: + description: "Intermediate request ID" + $ref: "uuid.yaml" + intermediateRequestDcbTransactionId: + description: "ID of DCB transaction created for intermediate request" + $ref: "uuid.yaml" + intermediateRequestTenantId: + description: "ID of the tenant intermediate request was created in" + type: string required: - instanceId diff --git a/src/main/resources/swagger.api/schemas/inventory/holdingsRecord.json b/src/main/resources/swagger.api/schemas/inventory/holdingsRecord.json index 747796ce..7d51e827 100644 --- a/src/main/resources/swagger.api/schemas/inventory/holdingsRecord.json +++ b/src/main/resources/swagger.api/schemas/inventory/holdingsRecord.json @@ -7,7 +7,7 @@ "id": { "type": "string", "description": "the unique ID of the holdings record; UUID", - "$ref": "../uuid.yaml" + "$ref": "../uuid.json" }, "_version": { "type": "integer", @@ -16,7 +16,7 @@ "sourceId": { "description": "(A reference to) the source of a holdings record", "type": "string", - "$ref": "../uuid.yaml" + "$ref": "../uuid.json" }, "hrid": { "type": "string", @@ -25,7 +25,7 @@ "holdingsTypeId": { "type": "string", "description": "unique ID for the type of this holdings record, a UUID", - "$ref": "../uuid.yaml" + "$ref": "../uuid.json" }, "formerIds": { "type": "array", @@ -38,22 +38,22 @@ "instanceId": { "description": "Inventory instances identifier", "type": "string", - "$ref": "../uuid.yaml" + "$ref": "../uuid.json" }, "permanentLocationId": { "type": "string", "description": "The permanent shelving location in which an item resides.", - "$ref" : "../uuid.yaml" + "$ref" : "../uuid.json" }, "temporaryLocationId": { "type": "string", "description": "Temporary location is the temporary location, shelving location, or holding which is a physical place where items are stored, or an Online location.", - "$ref": "../uuid.yaml" + "$ref": "../uuid.json" }, "effectiveLocationId": { "type": "string", "description": "Effective location is calculated by the system based on the values in the permanent and temporary locationId fields.", - "$ref": "../uuid.yaml" + "$ref": "../uuid.json" }, "electronicAccess": { "description": "List of electronic access items", @@ -66,7 +66,7 @@ "callNumberTypeId": { "type": "string", "description": "unique ID for the type of call number on a holdings record, a UUID", - "$ref": "../uuid.yaml" + "$ref": "../uuid.json" }, "callNumberPrefix": { "type": "string", @@ -115,7 +115,7 @@ "illPolicyId": { "type": "string", "description": "unique ID for an ILL policy, a UUID", - "$ref" : "../uuid.yaml" + "$ref" : "../uuid.json" }, "retentionPolicy": { "type": "string", @@ -170,7 +170,7 @@ "description": "List of statistical code IDs", "items": { "type": "string", - "$ref" : "../uuid.yaml" + "$ref" : "../uuid.json" }, "uniqueItems": true }, diff --git a/src/main/resources/swagger.api/schemas/inventory/holdingsRecords.json b/src/main/resources/swagger.api/schemas/inventory/holdingsRecords.json new file mode 100644 index 00000000..c94e83f3 --- /dev/null +++ b/src/main/resources/swagger.api/schemas/inventory/holdingsRecords.json @@ -0,0 +1,24 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "description": "A collection of holdings records", + "type": "object", + "properties": { + "holdingsRecords": { + "description": "List of holdings records", + "id": "holdingsRecord", + "type": "array", + "items": { + "type": "object", + "$ref": "holdingsRecord.json" + } + }, + "totalRecords": { + "description": "Estimated or exact total number of records", + "type": "integer" + }, + "resultInfo": { + "$ref": "../resultInfo.json", + "readonly": true + } + } +} \ No newline at end of file diff --git a/src/main/resources/swagger.api/schemas/inventory/instance.json b/src/main/resources/swagger.api/schemas/inventory/instance.json index 8082c3e3..2b2a1190 100644 --- a/src/main/resources/swagger.api/schemas/inventory/instance.json +++ b/src/main/resources/swagger.api/schemas/inventory/instance.json @@ -6,7 +6,7 @@ "id": { "type": "string", "description": "The unique ID of the instance record; a UUID", - "$ref": "../uuid.yaml" + "$ref": "../uuid.json" }, "_version": { "type": "integer", @@ -41,7 +41,7 @@ "alternativeTitleTypeId": { "type": "string", "description": "UUID for an alternative title qualifier", - "$ref": "../uuid.yaml" + "$ref": "../uuid.json" }, "alternativeTitle": { "type": "string", @@ -50,7 +50,7 @@ "authorityId": { "type": "string", "description": "UUID of authority record that controls an alternative title", - "$ref": "../uuid.yaml" + "$ref": "../uuid.json" } } }, @@ -77,7 +77,7 @@ "authorityId": { "type": "string", "description": "UUID of authority record that controls an series title", - "$ref": "../uuid.yaml" + "$ref": "../uuid.json" } }, "additionalProperties": true @@ -98,7 +98,7 @@ "identifierTypeId": { "type": "string", "description": "UUID of resource identifier type (e.g. ISBN, ISSN, LCCN, CODEN, Locally defined identifiers)", - "$ref": "../uuid.yaml" + "$ref": "../uuid.json" }, "identifierTypeObject": { "type": "object", @@ -129,7 +129,7 @@ "contributorTypeId": { "type": "string", "description": "UUID for the contributor type term defined in controlled vocabulary", - "$ref": "../uuid.yaml" + "$ref": "../uuid.json" }, "contributorTypeText": { "type": "string", @@ -138,12 +138,12 @@ "contributorNameTypeId": { "type": "string", "description": "UUID of contributor name type term defined by the MARC code list for relators", - "$ref": "../uuid.yaml" + "$ref": "../uuid.json" }, "authorityId": { "type": "string", "description": "UUID of authority record that controls the contributor", - "$ref": "../uuid.yaml" + "$ref": "../uuid.json" }, "contributorNameType": { "type": "object", @@ -178,7 +178,7 @@ "authorityId": { "type": "string", "description": "UUID of authority record that controls a subject heading", - "$ref": "../uuid.yaml" + "$ref": "../uuid.json" } }, "additionalProperties": true @@ -199,7 +199,7 @@ "classificationTypeId": { "type": "string", "description": "UUID of classification schema (e.g. LC, Canadian Classification, NLM, National Agricultural Library, UDC, and Dewey)", - "$ref": "../uuid.yaml" + "$ref": "../uuid.json" }, "classificationType": { "type": "object", @@ -298,7 +298,7 @@ "relationshipId": { "type": "string", "description": "UUID for the type of relationship between the electronic resource at the location identified and the item described in the record as a whole", - "$ref": "../uuid.yaml" + "$ref": "../uuid.json" } }, "additionalProperties": true @@ -307,14 +307,14 @@ "instanceTypeId": { "type": "string", "description": "UUID of the unique term for the resource type whether it's from the RDA content term list of locally defined", - "$ref": "../uuid.yaml" + "$ref": "../uuid.json" }, "instanceFormatIds": { "type": "array", "description": "UUIDs for the unique terms for the format whether it's from the RDA carrier term list of locally defined", "items": { "type": "string", - "$ref": "../uuid.yaml" + "$ref": "../uuid.json" } }, "instanceFormats": { @@ -356,7 +356,7 @@ "properties": { "instanceNoteTypeId": { "description": "ID of the type of note", - "$ref": "../uuid.yaml" + "$ref": "../uuid.json" }, "note": { "type": "string", @@ -381,7 +381,7 @@ "modeOfIssuanceId": { "type": "string", "description": "UUID of the RDA mode of issuance, a categorization reflecting whether a resource is issued in one or more parts, the way it is updated, and whether its termination is predetermined or not (e.g. monograph, sequential monograph, serial; integrating Resource, other)", - "$ref": "../uuid.yaml" + "$ref": "../uuid.json" }, "catalogedDate": { "type": "string", @@ -418,7 +418,7 @@ "statusId": { "type": "string", "description": "UUID for the Instance status term (e.g. cataloged, uncatalogued, batch loaded, temporary, other, not yet assigned)", - "$ref": "../uuid.yaml" + "$ref": "../uuid.json" }, "statusUpdatedDate": { "type": "string", @@ -456,7 +456,7 @@ "items": { "type": "string", "description": "Single UUID for the Instance nature of content", - "$ref": "../uuid.yaml" + "$ref": "../uuid.json" } } }, diff --git a/src/main/resources/swagger.api/schemas/request.json b/src/main/resources/swagger.api/schemas/request.json index 5287d652..40de3876 100644 --- a/src/main/resources/swagger.api/schemas/request.json +++ b/src/main/resources/swagger.api/schemas/request.json @@ -1,28 +1,27 @@ { "$schema": "http://json-schema.org/draft-04/schema#", - "title": "A request for an item", - "description": "Request for an item that might be at a different location or already checked out to another patron", + "description": "A request by a patron for a specific item", "type": "object", "properties": { "id": { "description": "UUID of the request", "type": "string", - "$ref": "uuid.json" - }, - "requestType": { - "description": "Whether the item should be held upon return, recalled or paged for", - "type": "string", - "enum": ["Hold", "Recall", "Page"] + "pattern": "^[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}$" }, "requestLevel": { "description": "Level of the request - Item or Title", "type": "string", "enum": ["Item", "Title"] }, + "requestType": { + "description": "Whether the item should be held upon return, recalled or paged for", + "type": "string", + "enum": ["Hold", "Recall", "Page"] + }, "ecsRequestPhase": { "description": "Stage in ECS request process, absence of this field means this is a single-tenant request", "type": "string", - "enum": ["Primary", "Secondary"] + "enum": ["Primary", "Secondary", "Intermediate"] }, "requestDate": { "description": "Date the request was made", @@ -34,14 +33,14 @@ "type": "string" }, "requesterId": { - "description": "ID of the user who made the request", + "description": "ID of the requesting patron (user)", "type": "string", - "$ref": "uuid.json" + "pattern": "^[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}$" }, "proxyUserId": { "description": "ID of the user representing a proxy for the patron", "type": "string", - "$ref": "uuid.json" + "pattern": "^[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}$" }, "instanceId": { "description": "ID of the instance being requested", @@ -56,7 +55,7 @@ "itemId": { "description": "ID of the item being requested", "type": "string", - "$ref": "uuid.json" + "pattern": "^[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}$" }, "status": { "description": "Status of the request", @@ -73,17 +72,15 @@ ] }, "cancellationReasonId": { - "description": "The id of the request reason", - "type": "string", - "$ref": "uuid.json" + "description": "The id of the relevant request reason", + "type": "string" }, "cancelledByUserId": { "description": "The id of the user that cancelled the request", - "type": "string", - "$ref": "uuid.json" + "type": "string" }, "cancellationAdditionalInformation": { - "description": "Additional information about a cancellation", + "description": "Potential relevant information regarding a cancellation", "type": "string" }, "cancelledDate": { @@ -92,9 +89,8 @@ "format": "date-time" }, "position": { - "description": "position of the request in a per-item request queue", - "type": "integer", - "minimum": 1 + "description": "Position of the request in the unified request queue", + "type": "integer" }, "instance": { "description": "Copy of some instance metadata (used for searching and sorting)", @@ -121,7 +117,7 @@ "$ref": "uuid.json" } }, - "additionalProperties": false, + "additionalProperties": true, "required": [ "value", "identifierTypeId" @@ -139,123 +135,61 @@ "type": "string" } }, - "additionalProperties": false + "additionalProperties": true }, "requester": { - "description": "Copy of some requesting patron metadata (used for searching and sorting), will be taken from the user referred to by the requesterId", - "readonly": true, + "description": "Copy of some requesting patron metadata (used for searching and sorting)", "type": "object", "properties": { "firstName": { - "description": "first name of the patron (read only, defined by the server)", - "type": "string", - "readonly": true + "description": "first name of the requesting patron", + "type": "string" }, "lastName": { - "description": "last name of the patron (read only, defined by the server)", - "type": "string", - "readonly": true + "description": "last name of the requesting patron", + "type": "string" }, "middleName": { - "description": "middle name of the patron (read only, defined by the server)", - "type": "string", - "readonly": true + "description": "middle name of the requesting patron", + "type": "string" }, "barcode": { - "description": "barcode of the patron (read only, defined by the server)", - "type": "string", - "readonly": true - }, - "patronGroupId": { - "description": "UUID for the patron group that this user belongs to", - "type": "string", - "readonly": true, - "$ref": "uuid.json" + "description": "barcode of the requesting patron", + "type": "string" }, - "patronGroup": { - "description": "record for the user's patron group", - "type": "object", - "additionalProperties": false, - "readonly": true, - "properties": { - "id": { - "description": "ID of the patron group", - "type": "string", - "readonly": true, - "$ref": "uuid.json" - }, - "group": { - "description": "The unique name of the patron group", - "type": "string", - "readonly": true - }, - "desc": { - "description": "A description of the patron group", - "type": "string", - "readonly": true - } - } + "patronGroup" : { + "description": "DEPRECATED, to be removed in subsequent major version", + "type": "string" } }, - "additionalProperties": false + "additionalProperties": true }, "proxy": { - "description": "Copy of some proxy patron metadata (used for searching and sorting), will be taken from the user referred to by the proxyUserId", - "readonly": true, + "description": "Copy of some proxy patron metadata (used for searching and sorting)", "type": "object", "properties": { "firstName": { - "description": "first name of the proxy patron (read only, defined by the server)", - "type": "string", - "readonly": true + "description": "first name of the proxy patron", + "type": "string" }, "lastName": { - "description": "last name of the proxy patron (read only, defined by the server)", - "type": "string", - "readonly": true + "description": "last name of the proxy patron", + "type": "string" }, "middleName": { - "description": "middle name of the proxy patron (read only, defined by the server)", - "type": "string", - "readonly": true + "description": "middle name of the proxy patron", + "type": "string" }, "barcode": { - "description": "barcode of the proxy patron (read only, defined by the server)", - "type": "string", - "readonly": true - }, - "patronGroupId": { - "description": "UUID for the patrongroup that this user belongs to", - "type": "string", - "readonly": true, - "$ref": "uuid.json" + "description": "barcode of the proxy patron", + "type": "string" }, "patronGroup": { - "description": "record for the user's patrongroup", - "type": "object", - "readonly": true, - "additionalProperties": false, - "properties": { - "id": { - "description": "ID of the patrongroup", - "type": "string", - "readonly": true, - "$ref": "uuid.json" - }, - "group": { - "description": "The unique name of the patrongroup", - "type": "string", - "readonly": true - }, - "desc": { - "description": "A description of the patrongroup", - "type": "string", - "readonly": true - } - } + "description": "DEPRECATED, to be removed in subsequent major version", + "type": "string" } }, - "additionalProperties": false + "additionalProperties": true }, "fulfillmentPreference": { "description": "How should the request be fulfilled (whether the item should be kept on the hold shelf for collection or delivered to the requester)", @@ -265,51 +199,7 @@ "deliveryAddressTypeId": { "description": "Deliver to the address of this type, for the requesting patron", "type": "string", - "$ref": "uuid.json" - }, - "deliveryAddress": { - "description": "Address the item is to be delivered to (derived from requester information)", - "type": "object", - "readonly": true, - "properties": { - "addressLine1": { - "description": "Address line 1", - "type": "string", - "readonly": true - }, - "addressLine2": { - "description": "Address line 2", - "type": "string", - "readonly": true - }, - "city": { - "description": "City name", - "type": "string", - "readonly": true - }, - "region": { - "description": "Region", - "type": "string", - "readonly": true - }, - "postalCode": { - "description": "Postal code", - "type": "string", - "readonly": true - }, - "countryId": { - "description": "Country code", - "type": "string", - "readonly": true - }, - "addressTypeId": { - "description": "Type of address (refers to address types)", - "type": "string", - "readonly": true, - "$ref": "uuid.json" - } - }, - "additionalProperties": false + "pattern": "^[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}$" }, "requestExpirationDate": { "description": "Date when the request expires", @@ -324,77 +214,57 @@ "pickupServicePointId": { "description": "The ID of the Service Point where this request can be picked up", "type": "string", - "$ref": "uuid.json" + "pattern": "^[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}$" }, - "pickupServicePoint": { - "description": "The full object of the Service Point record from pickupServicePointId", - "additionalProperties": false, - "readonly": true, - "properties": { - "name": { - "description": "Unique name for the service point", - "type": "string", - "readonly": true - }, - "code": { - "description": "Unique code for the service point", - "type": "string", - "readonly": true - }, - "discoveryDisplayName": { - "description": "Human-readable name for the service point", - "type": "string", - "readonly": true - }, - "description": { - "description": "Description of the service point", - "type": "string", - "readonly": true - }, - "shelvingLagTime": { - "description": "Shelving lag time", - "type": "integer", - "readonly": true - }, - "pickupLocation": { - "description": "Is this service point a pickup location?", - "type": "boolean", - "readonly": true - } - } + "metadata": { + "description": "Metadata about creation and changes to requests, provided by the server (client should not provide)", + "type": "object", + "$ref": "metadata.json" }, "tags": { "type": "object", "description": "Tags", "$ref": "tags.json" }, - "metadata": { - "description": "Metadata about creation and changes to requests, provided by the server (client should not provide)", - "type": "object", - "$ref": "metadata.json" - }, - "requestProcessingParameters": { + "printDetails": { "type": "object", - "description": "Additional parameters used for request processing and discarded afterwards. Not part of request record.", + "description": "PrintDetails", "properties": { - "overrideBlocks": { - "type": "object", - "description": "Blocks to override if user has corresponding permissions", - "$ref": "override-blocks.json" + "printCount": { + "type": "integer", + "description": "Number of times print slip generated." + }, + "requesterId": { + "type": "string", + "description": "UUID of print slip requester." + }, + "isPrinted": { + "type": "boolean", + "description": "Whether print slip was printed in past." + }, + "printEventDate": { + "type": "string", + "format": "date-time", + "description": "Date and time when print slip was generated last time." } - } + }, + "additionalProperties": true + }, + "awaitingPickupRequestClosedDate": { + "description": "A date when the request with awaiting pickup status was closed", + "type": "string", + "format": "date-time", + "readonly" : true }, "searchIndex": { "description": "Request fields used for search", "type": "object", "$ref": "request-search-index.json" + }, + "itemLocationCode": { + "description": "Allow specifying item location when creating title-level requests", + "type": "string" } }, - "additionalProperties": false, - "required": [ - "requesterId", - "requestType", - "requestDate", - "fulfillmentPreference" - ] -} + "additionalProperties": true +} \ No newline at end of file diff --git a/src/main/resources/swagger.api/schemas/staffSlips/searchSlipsResponse.yaml b/src/main/resources/swagger.api/schemas/staffSlips/searchSlipsResponse.yaml new file mode 100644 index 00000000..e24a1041 --- /dev/null +++ b/src/main/resources/swagger.api/schemas/staffSlips/searchSlipsResponse.yaml @@ -0,0 +1,11 @@ +description: "Search slips response" +type: "object" +properties: + totalRecords: + type: "integer" + description: "Total number of search slips" + searchSlips: + type: "array" + description: "Collection of search clips" + items: + $ref: "staffSlip.yaml" diff --git a/src/main/resources/swagger.api/schemas/staffSlips/staffSlip.yaml b/src/main/resources/swagger.api/schemas/staffSlips/staffSlip.yaml index 61d2c735..599f8f14 100644 --- a/src/main/resources/swagger.api/schemas/staffSlips/staffSlip.yaml +++ b/src/main/resources/swagger.api/schemas/staffSlips/staffSlip.yaml @@ -63,7 +63,7 @@ properties: request: type: object properties: - requestId: + requestID: type: string format: uuid servicePointPickup: diff --git a/src/main/resources/swagger.api/staff-slips.yaml b/src/main/resources/swagger.api/staff-slips.yaml index 5ba46c2b..9258e78f 100644 --- a/src/main/resources/swagger.api/staff-slips.yaml +++ b/src/main/resources/swagger.api/staff-slips.yaml @@ -4,13 +4,15 @@ info: version: v1 tags: - name: staffSlips +servers: + - url: /tlr/staff-slips paths: - /tlr/staff-slips/pick-slips/{servicePointId}: + /pick-slips/{servicePointId}: get: description: Get pick slips operationId: getPickSlips tags: - - pickSlips + - staffSlips parameters: - $ref: '#/components/parameters/servicePointId' responses: @@ -22,6 +24,23 @@ paths: $ref: '#/components/responses/notFoundResponse' '500': $ref: '#/components/responses/internalServerErrorResponse' + /search-slips/{servicePointId}: + get: + description: Get search slips + operationId: getSearchSlips + tags: + - staffSlips + parameters: + - $ref: '#/components/parameters/servicePointId' + responses: + '200': + $ref: '#/components/responses/search-slips' + '400': + $ref: '#/components/responses/badRequestResponse' + '404': + $ref: '#/components/responses/notFoundResponse' + '500': + $ref: '#/components/responses/internalServerErrorResponse' components: schemas: errorResponse: @@ -42,6 +61,10 @@ components: $ref: 'schemas/inventory/institutions.json' servicePoints: $ref: 'schemas/inventory/servicePoints.json' + holdingsRecords: + $ref: 'schemas/inventory/holdingsRecords.json' + instances: + $ref: 'schemas/inventory/instances.json' users: $ref: 'schemas/users/users.json' usersGroups: @@ -67,6 +90,12 @@ components: application/json: schema: $ref: 'schemas/staffSlips/pickSlipsResponse.yaml' + search-slips: + description: Search slips response + content: + application/json: + schema: + $ref: 'schemas/staffSlips/searchSlipsResponse.yaml' badRequestResponse: description: Validation errors content: diff --git a/src/test/java/org/folio/api/EcsTlrApiTest.java b/src/test/java/org/folio/api/EcsTlrApiTest.java index bd1aff49..29bc46c9 100644 --- a/src/test/java/org/folio/api/EcsTlrApiTest.java +++ b/src/test/java/org/folio/api/EcsTlrApiTest.java @@ -7,6 +7,7 @@ import static com.github.tomakehurst.wiremock.client.WireMock.get; import static com.github.tomakehurst.wiremock.client.WireMock.getRequestedFor; import static com.github.tomakehurst.wiremock.client.WireMock.jsonResponse; +import static com.github.tomakehurst.wiremock.client.WireMock.not; import static com.github.tomakehurst.wiremock.client.WireMock.notFound; import static com.github.tomakehurst.wiremock.client.WireMock.post; import static com.github.tomakehurst.wiremock.client.WireMock.postRequestedFor; @@ -15,6 +16,8 @@ import static com.github.tomakehurst.wiremock.client.WireMock.urlMatching; import static org.folio.domain.dto.EcsTlr.RequestTypeEnum.HOLD; import static org.folio.domain.dto.EcsTlr.RequestTypeEnum.PAGE; +import static org.folio.domain.dto.Request.EcsRequestPhaseEnum.INTERMEDIATE; +import static org.folio.domain.dto.Request.EcsRequestPhaseEnum.PRIMARY; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.springframework.http.HttpStatus.INTERNAL_SERVER_ERROR; import static org.springframework.http.HttpStatus.NOT_FOUND; @@ -50,6 +53,7 @@ import org.junit.jupiter.params.provider.EnumSource; import com.github.tomakehurst.wiremock.client.ResponseDefinitionBuilder; +import com.github.tomakehurst.wiremock.client.WireMock; class EcsTlrApiTest extends BaseIT { private static final String ITEM_ID = randomId(); @@ -62,6 +66,7 @@ class EcsTlrApiTest extends BaseIT { private static final String REQUESTER_BARCODE = randomId(); private static final String SECONDARY_REQUEST_ID = randomId(); private static final String PRIMARY_REQUEST_ID = SECONDARY_REQUEST_ID; + private static final String INTERMEDIATE_REQUEST_ID = SECONDARY_REQUEST_ID; private static final String UUID_PATTERN = "[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-5][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}"; @@ -113,10 +118,11 @@ public void beforeEach() { "RECALL, false, true, ITEM", "RECALL, false, false, ITEM" }) - void ecsTlrIsCreated(RequestTypeEnum requestType, boolean secondaryRequestRequesterExists, - boolean secondaryRequestPickupServicePointExists, EcsTlr.RequestLevelEnum requestLevel) { + void ecsTlrIsCreated(RequestTypeEnum requestType, boolean requesterClonesExist, + boolean pickupServicePointClonesExist, EcsTlr.RequestLevelEnum requestLevel) { - EcsTlr ecsTlr = buildEcsTlr(requestType, requestLevel); + EcsTlr ecsTlr = buildEcsTlr(requestType, requestLevel) + .primaryRequestTenantId(TENANT_ID_UNIVERSITY); // 1. Create stubs for other modules // 1.1 Mock search endpoint @@ -148,51 +154,54 @@ void ecsTlrIsCreated(RequestTypeEnum requestType, boolean secondaryRequestReques // 1.2 Mock user endpoints User primaryRequestRequester = buildPrimaryRequestRequester(REQUESTER_ID); - User secondaryRequestRequester = buildSecondaryRequestRequester(primaryRequestRequester, - secondaryRequestRequesterExists); + User requesterClone = buildRequesterClone(primaryRequestRequester, + requesterClonesExist); wireMockServer.stubFor(get(urlMatching(USERS_URL + "/" + REQUESTER_ID)) - .withHeader(HEADER_TENANT, equalTo(TENANT_ID_CONSORTIUM)) + .withHeader(HEADER_TENANT, equalTo(TENANT_ID_UNIVERSITY)) .willReturn(jsonResponse(primaryRequestRequester, HttpStatus.SC_OK))); - ResponseDefinitionBuilder mockGetSecondaryRequesterResponse = secondaryRequestRequesterExists - ? jsonResponse(secondaryRequestRequester, HttpStatus.SC_OK) + ResponseDefinitionBuilder mockGetClonedRequesterResponse = requesterClonesExist + ? jsonResponse(requesterClone, HttpStatus.SC_OK) : notFound(); wireMockServer.stubFor(get(urlMatching(USERS_URL + "/" + REQUESTER_ID)) - .withHeader(HEADER_TENANT, equalTo(TENANT_ID_COLLEGE)) - .willReturn(mockGetSecondaryRequesterResponse)); + .withHeader(HEADER_TENANT, WireMock.including(TENANT_ID_COLLEGE)) + .willReturn(mockGetClonedRequesterResponse)); + + wireMockServer.stubFor(get(urlMatching(USERS_URL + "/" + REQUESTER_ID)) + .withHeader(HEADER_TENANT, equalTo(TENANT_ID_CONSORTIUM)) + .willReturn(mockGetClonedRequesterResponse)); wireMockServer.stubFor(post(urlMatching(USERS_URL)) - .withHeader(HEADER_TENANT, equalTo(TENANT_ID_COLLEGE)) - .willReturn(jsonResponse(secondaryRequestRequester, HttpStatus.SC_CREATED))); + .withHeader(HEADER_TENANT, not(equalTo(TENANT_ID_UNIVERSITY))) + .willReturn(jsonResponse(requesterClone, HttpStatus.SC_CREATED))); wireMockServer.stubFor(put(urlMatching(USERS_URL + "/" + REQUESTER_ID)) - .withHeader(HEADER_TENANT, equalTo(TENANT_ID_COLLEGE)) + .withHeader(HEADER_TENANT, not(equalTo(TENANT_ID_UNIVERSITY))) .willReturn(jsonResponse(primaryRequestRequester, HttpStatus.SC_NO_CONTENT))); // 1.3 Mock service point endpoints ServicePoint primaryRequestPickupServicePoint = buildPrimaryRequestPickupServicePoint(PICKUP_SERVICE_POINT_ID); - ServicePoint secondaryRequestPickupServicePoint = - buildSecondaryRequestPickupServicePoint(primaryRequestPickupServicePoint); + ServicePoint servicePointClone = buildPickupServicePointClone(primaryRequestPickupServicePoint); wireMockServer.stubFor(get(urlMatching(SERVICE_POINTS_URL + "/" + PICKUP_SERVICE_POINT_ID)) - .withHeader(HEADER_TENANT, equalTo(TENANT_ID_CONSORTIUM)) + .withHeader(HEADER_TENANT, equalTo(TENANT_ID_UNIVERSITY)) .willReturn(jsonResponse(asJsonString(primaryRequestPickupServicePoint), HttpStatus.SC_OK))); - var mockGetSecondaryRequestPickupServicePointResponse = secondaryRequestPickupServicePointExists - ? jsonResponse(asJsonString(secondaryRequestPickupServicePoint), HttpStatus.SC_OK) + var mockGetClonedPickupServicePointResponse = pickupServicePointClonesExist + ? jsonResponse(asJsonString(servicePointClone), HttpStatus.SC_OK) : notFound(); wireMockServer.stubFor(get(urlMatching(SERVICE_POINTS_URL + "/" + PICKUP_SERVICE_POINT_ID)) - .withHeader(HEADER_TENANT, equalTo(TENANT_ID_COLLEGE)) - .willReturn(mockGetSecondaryRequestPickupServicePointResponse)); + .withHeader(HEADER_TENANT, not(equalTo(TENANT_ID_UNIVERSITY))) + .willReturn(mockGetClonedPickupServicePointResponse)); wireMockServer.stubFor(post(urlMatching(SERVICE_POINTS_URL)) - .withHeader(HEADER_TENANT, equalTo(TENANT_ID_COLLEGE)) - .willReturn(jsonResponse(asJsonString(secondaryRequestPickupServicePoint), HttpStatus.SC_CREATED))); + .withHeader(HEADER_TENANT, not(equalTo(TENANT_ID_UNIVERSITY))) + .willReturn(jsonResponse(asJsonString(servicePointClone), HttpStatus.SC_CREATED))); // 1.4 Mock request endpoints @@ -204,8 +213,11 @@ void ecsTlrIsCreated(RequestTypeEnum requestType, boolean secondaryRequestReques .item(new RequestItem().barcode(ITEM_BARCODE)) .instance(new RequestInstance().title(INSTANCE_TITLE)); - Request primaryRequestPostRequest = buildPrimaryRequest(secondaryRequestPostRequest); - Request mockPostPrimaryRequestResponse = buildPrimaryRequest(mockPostSecondaryRequestResponse); + Request primaryRequestPostRequest = buildRequest(secondaryRequestPostRequest, PRIMARY); + Request mockPostPrimaryRequestResponse = buildRequest(mockPostSecondaryRequestResponse, PRIMARY); + + Request intermediateRequestPostRequest = buildRequest(secondaryRequestPostRequest, INTERMEDIATE); + Request mockPostIntermediateRequestResponse = buildRequest(mockPostSecondaryRequestResponse, INTERMEDIATE); wireMockServer.stubFor(post(urlMatching(REQUESTS_URL)) .withHeader(HEADER_TENANT, equalTo(TENANT_ID_COLLEGE)) @@ -213,19 +225,32 @@ void ecsTlrIsCreated(RequestTypeEnum requestType, boolean secondaryRequestReques .willReturn(jsonResponse(asJsonString(mockPostSecondaryRequestResponse), HttpStatus.SC_CREATED))); wireMockServer.stubFor(post(urlMatching(REQUESTS_URL)) - .withHeader(HEADER_TENANT, equalTo(TENANT_ID_CONSORTIUM)) + .withHeader(HEADER_TENANT, equalTo(TENANT_ID_UNIVERSITY)) .withRequestBody(equalToJson(asJsonString(primaryRequestPostRequest))) .willReturn(jsonResponse(asJsonString(mockPostPrimaryRequestResponse), HttpStatus.SC_CREATED))); + wireMockServer.stubFor(post(urlMatching(REQUESTS_URL)) + .withHeader(HEADER_TENANT, equalTo(TENANT_ID_CONSORTIUM)) + .withRequestBody(equalToJson(asJsonString(intermediateRequestPostRequest))) + .willReturn(jsonResponse(asJsonString(mockPostIntermediateRequestResponse), HttpStatus.SC_CREATED))); + // 1.5 Mock DCB endpoints + DcbTransaction pickupTransactionPostRequest = new DcbTransaction() + .role(DcbTransaction.RoleEnum.PICKUP) + .item(new DcbItem() + .id(ITEM_ID) + .barcode(ITEM_BARCODE) + .title(INSTANCE_TITLE)) + .requestId(PRIMARY_REQUEST_ID); + DcbTransaction borrowerTransactionPostRequest = new DcbTransaction() .role(DcbTransaction.RoleEnum.BORROWER) .item(new DcbItem() .id(ITEM_ID) .barcode(ITEM_BARCODE) .title(INSTANCE_TITLE)) - .requestId(PRIMARY_REQUEST_ID); + .requestId(INTERMEDIATE_REQUEST_ID); DcbTransaction lenderTransactionPostRequest = new DcbTransaction() .role(DcbTransaction.RoleEnum.LENDER) @@ -234,6 +259,11 @@ void ecsTlrIsCreated(RequestTypeEnum requestType, boolean secondaryRequestReques TransactionStatusResponse mockPostEcsDcbTransactionResponse = new TransactionStatusResponse() .status(TransactionStatusResponse.StatusEnum.CREATED); + wireMockServer.stubFor(post(urlMatching(POST_ECS_REQUEST_TRANSACTION_URL_PATTERN)) + .withHeader(HEADER_TENANT, equalTo(TENANT_ID_UNIVERSITY)) + .withRequestBody(equalToJson(asJsonString(pickupTransactionPostRequest))) + .willReturn(jsonResponse(mockPostEcsDcbTransactionResponse, HttpStatus.SC_CREATED))); + wireMockServer.stubFor(post(urlMatching(POST_ECS_REQUEST_TRANSACTION_URL_PATTERN)) .withHeader(HEADER_TENANT, equalTo(TENANT_ID_CONSORTIUM)) .withRequestBody(equalToJson(asJsonString(borrowerTransactionPostRequest))) @@ -244,6 +274,8 @@ void ecsTlrIsCreated(RequestTypeEnum requestType, boolean secondaryRequestReques .withRequestBody(equalToJson(asJsonString(lenderTransactionPostRequest))) .willReturn(jsonResponse(mockPostEcsDcbTransactionResponse, HttpStatus.SC_CREATED))); + // 1.6 Mock circulation item endpoints + wireMockServer.stubFor(get(urlMatching("/circulation-item/" + ITEM_ID)) .willReturn(notFound())); @@ -279,9 +311,11 @@ void ecsTlrIsCreated(RequestTypeEnum requestType, boolean secondaryRequestReques EcsTlr expectedPostEcsTlrResponse = buildEcsTlr(requestType, requestLevel) .primaryRequestId(PRIMARY_REQUEST_ID) - .primaryRequestTenantId(TENANT_ID_CONSORTIUM) + .primaryRequestTenantId(TENANT_ID_UNIVERSITY) .secondaryRequestId(SECONDARY_REQUEST_ID) .secondaryRequestTenantId(TENANT_ID_COLLEGE) + .intermediateRequestId(INTERMEDIATE_REQUEST_ID) + .intermediateRequestTenantId(TENANT_ID_CONSORTIUM) .itemId(requestType == HOLD ? null : ITEM_ID); assertEquals(TENANT_ID_CONSORTIUM, getCurrentTenantId()); @@ -315,28 +349,35 @@ void ecsTlrIsCreated(RequestTypeEnum requestType, boolean secondaryRequestReques .withHeader(HEADER_TENANT, equalTo(TENANT_ID_COLLEGE))); wireMockServer.verify(postRequestedFor(urlMatching(REQUESTS_URL)) - .withHeader(HEADER_TENANT, equalTo(TENANT_ID_COLLEGE)) // because this tenant has available item + .withHeader(HEADER_TENANT, equalTo(TENANT_ID_COLLEGE)) .withRequestBody(equalToJson(asJsonString(secondaryRequestPostRequest)))); wireMockServer.verify(postRequestedFor(urlMatching(REQUESTS_URL)) .withHeader(HEADER_TENANT, equalTo(TENANT_ID_CONSORTIUM)) + .withRequestBody(equalToJson(asJsonString(intermediateRequestPostRequest)))); + + wireMockServer.verify(postRequestedFor(urlMatching(REQUESTS_URL)) + .withHeader(HEADER_TENANT, equalTo(TENANT_ID_UNIVERSITY)) .withRequestBody(equalToJson(asJsonString(primaryRequestPostRequest)))); - if (secondaryRequestRequesterExists) { + if (requesterClonesExist) { wireMockServer.verify(exactly(0), postRequestedFor(urlMatching(USERS_URL))); - wireMockServer.verify(exactly(1), putRequestedFor(urlMatching(USERS_URL + "/" + REQUESTER_ID))); + wireMockServer.verify(exactly(2), putRequestedFor(urlMatching(USERS_URL + "/" + REQUESTER_ID))); } else { wireMockServer.verify(postRequestedFor(urlMatching(USERS_URL)) .withHeader(HEADER_TENANT, equalTo(TENANT_ID_COLLEGE)) - .withRequestBody(equalToJson(asJsonString(secondaryRequestRequester)))); + .withRequestBody(equalToJson(asJsonString(requesterClone)))); } - if (secondaryRequestPickupServicePointExists) { + if (pickupServicePointClonesExist) { wireMockServer.verify(exactly(0), postRequestedFor(urlMatching(SERVICE_POINTS_URL))); } else { wireMockServer.verify(postRequestedFor(urlMatching(SERVICE_POINTS_URL)) .withHeader(HEADER_TENANT, equalTo(TENANT_ID_COLLEGE)) - .withRequestBody(equalToJson(asJsonString(secondaryRequestPickupServicePoint)))); + .withRequestBody(equalToJson(asJsonString(servicePointClone)))); + wireMockServer.verify(postRequestedFor(urlMatching(SERVICE_POINTS_URL)) + .withHeader(HEADER_TENANT, equalTo(TENANT_ID_CONSORTIUM)) + .withRequestBody(equalToJson(asJsonString(servicePointClone)))); } wireMockServer.verify(postRequestedFor(urlMatching(POST_ECS_REQUEST_TRANSACTION_URL_PATTERN)) @@ -346,6 +387,10 @@ void ecsTlrIsCreated(RequestTypeEnum requestType, boolean secondaryRequestReques wireMockServer.verify(postRequestedFor(urlMatching(POST_ECS_REQUEST_TRANSACTION_URL_PATTERN)) .withHeader(HEADER_TENANT, equalTo(TENANT_ID_COLLEGE)) .withRequestBody(equalToJson(asJsonString(lenderTransactionPostRequest)))); + + wireMockServer.verify(postRequestedFor(urlMatching(POST_ECS_REQUEST_TRANSACTION_URL_PATTERN)) + .withHeader(HEADER_TENANT, equalTo(TENANT_ID_UNIVERSITY)) + .withRequestBody(equalToJson(asJsonString(pickupTransactionPostRequest)))); } @Test @@ -481,7 +526,9 @@ private static Request buildSecondaryRequest(EcsTlr ecsTlr) { .patronComments(ecsTlr.getPatronComments()); } - private static Request buildPrimaryRequest(Request secondaryRequest) { + private static Request buildRequest(Request secondaryRequest, + Request.EcsRequestPhaseEnum ecsRequestPhase) { + return new Request() .id(PRIMARY_REQUEST_ID) .itemId(ITEM_ID) @@ -493,7 +540,7 @@ private static Request buildPrimaryRequest(Request secondaryRequest) { .requestDate(secondaryRequest.getRequestDate()) .requestLevel(secondaryRequest.getRequestLevel()) .requestType(secondaryRequest.getRequestType()) - .ecsRequestPhase(Request.EcsRequestPhaseEnum.PRIMARY) + .ecsRequestPhase(ecsRequestPhase) .fulfillmentPreference(secondaryRequest.getFulfillmentPreference()) .pickupServicePointId(secondaryRequest.getPickupServicePointId()); } @@ -520,7 +567,7 @@ private static User buildPrimaryRequestRequester(String userId) { .customFields(null); } - private static User buildSecondaryRequestRequester(User primaryRequestRequester, + private static User buildRequesterClone(User primaryRequestRequester, boolean secondaryRequestRequesterExists) { return new User() @@ -542,7 +589,7 @@ private static ServicePoint buildPrimaryRequestPickupServicePoint(String id) { .pickupLocation(true); } - private static ServicePoint buildSecondaryRequestPickupServicePoint( + private static ServicePoint buildPickupServicePointClone( ServicePoint primaryRequestPickupServicePoint) { return new ServicePoint() diff --git a/src/test/java/org/folio/api/StaffSlipsApiTest.java b/src/test/java/org/folio/api/StaffSlipsApiTest.java index 48e5b65b..37604280 100644 --- a/src/test/java/org/folio/api/StaffSlipsApiTest.java +++ b/src/test/java/org/folio/api/StaffSlipsApiTest.java @@ -4,28 +4,42 @@ import static com.github.tomakehurst.wiremock.client.WireMock.getRequestedFor; import static com.github.tomakehurst.wiremock.client.WireMock.matching; import static com.github.tomakehurst.wiremock.client.WireMock.okJson; +import static com.github.tomakehurst.wiremock.client.WireMock.requestedFor; import static com.github.tomakehurst.wiremock.client.WireMock.urlPathEqualTo; import static com.github.tomakehurst.wiremock.client.WireMock.urlPathMatching; import static java.util.Collections.emptyList; +import static java.util.stream.Collectors.joining; import static java.util.stream.Collectors.toSet; +import static org.folio.domain.dto.ItemStatus.NameEnum.AWAITING_DELIVERY; +import static org.folio.domain.dto.ItemStatus.NameEnum.CHECKED_OUT; +import static org.folio.domain.dto.ItemStatus.NameEnum.IN_PROCESS; +import static org.folio.domain.dto.ItemStatus.NameEnum.IN_TRANSIT; +import static org.folio.domain.dto.ItemStatus.NameEnum.MISSING; +import static org.folio.domain.dto.ItemStatus.NameEnum.ON_ORDER; import static org.folio.domain.dto.ItemStatus.NameEnum.PAGED; +import static org.folio.domain.dto.ItemStatus.NameEnum.RESTRICTED; +import static org.folio.domain.dto.Request.RequestTypeEnum.HOLD; import static org.folio.domain.dto.Request.RequestTypeEnum.PAGE; import static org.hamcrest.Matchers.hasSize; import static org.hamcrest.Matchers.is; import java.util.Collection; +import java.util.EnumSet; import java.util.List; import java.util.Set; import java.util.UUID; -import java.util.stream.Stream; import org.folio.domain.dto.AddressType; import org.folio.domain.dto.AddressTypes; import org.folio.domain.dto.Campus; import org.folio.domain.dto.Campuses; -import org.folio.domain.dto.Contributor; import org.folio.domain.dto.Department; import org.folio.domain.dto.Departments; +import org.folio.domain.dto.HoldingsRecord; +import org.folio.domain.dto.HoldingsRecords; +import org.folio.domain.dto.Instance; +import org.folio.domain.dto.InstanceContributorsInner; +import org.folio.domain.dto.Instances; import org.folio.domain.dto.Institution; import org.folio.domain.dto.Institutions; import org.folio.domain.dto.Item; @@ -41,11 +55,6 @@ import org.folio.domain.dto.MaterialTypes; import org.folio.domain.dto.Request; import org.folio.domain.dto.Requests; -import org.folio.domain.dto.SearchHolding; -import org.folio.domain.dto.SearchInstance; -import org.folio.domain.dto.SearchInstancesResponse; -import org.folio.domain.dto.SearchItem; -import org.folio.domain.dto.SearchItemStatus; import org.folio.domain.dto.ServicePoint; import org.folio.domain.dto.ServicePoints; import org.folio.domain.dto.User; @@ -55,10 +64,12 @@ import org.folio.domain.dto.UserPersonalAddressesInner; import org.folio.domain.dto.Users; import org.junit.jupiter.api.Test; +import org.springframework.http.HttpMethod; import org.springframework.test.web.reactive.server.WebTestClient; import com.github.tomakehurst.wiremock.client.WireMock; import com.github.tomakehurst.wiremock.matching.MultiValuePattern; +import com.github.tomakehurst.wiremock.matching.RequestPatternBuilder; import com.github.tomakehurst.wiremock.matching.StringValuePattern; import lombok.SneakyThrows; @@ -67,11 +78,16 @@ class StaffSlipsApiTest extends BaseIT { private static final String SERVICE_POINT_ID = "e0c50666-6144-47b1-9e87-8c1bf30cda34"; private static final String DEFAULT_LIMIT = "1000"; + private static final EnumSet PICK_SLIPS_ITEM_STATUSES = EnumSet.of(PAGED); + private static final EnumSet SEARCH_SLIPS_ITEM_STATUSES = EnumSet.of( + CHECKED_OUT, AWAITING_DELIVERY, IN_TRANSIT, MISSING, PAGED, ON_ORDER, IN_PROCESS, RESTRICTED); private static final String PICK_SLIPS_URL = "/tlr/staff-slips/pick-slips"; - private static final String INSTANCE_SEARCH_URL ="/search/instances"; + private static final String SEARCH_SLIPS_URL = "/tlr/staff-slips/search-slips"; private static final String LOCATIONS_URL = "/locations"; private static final String ITEMS_URL = "/item-storage/items"; + private static final String HOLDINGS_URL = "/holdings-storage/holdings"; + private static final String INSTANCES_URL = "/instance-storage/instances"; private static final String REQUESTS_URL = "/request-storage/requests"; private static final String MATERIAL_TYPES_URL = "/material-types"; private static final String LOAN_TYPES_URL = "/loan-types"; @@ -87,10 +103,21 @@ class StaffSlipsApiTest extends BaseIT { private static final String PICK_SLIPS_LOCATION_QUERY = "primaryServicePoint==\"" + SERVICE_POINT_ID + "\""; private static final String SEARCH_BY_ID_QUERY_PATTERN = "id==\\(.*\\)"; - private static final String PICK_SLIPS_REQUESTS_QUERY_PATTERN = "requestType==\\(\"Page\"\\) " + + private static final String REQUESTS_QUERY_PATTERN_TEMPLATE = "requestType==\\(\"%s\"\\) " + "and \\(status==\\(\"Open - Not yet filled\"\\)\\) and \\(itemId==\\(.*\\)\\)"; - private static final String PICK_SLIPS_INSTANCE_SEARCH_QUERY_PATTERN = - "item.status.name==\\(\"Paged\"\\) and \\(item.effectiveLocationId==\\(.*\\)\\)"; + private static final String PICK_SLIPS_REQUESTS_QUERY_PATTERN = + String.format(REQUESTS_QUERY_PATTERN_TEMPLATE, "Page"); + private static final String SEARCH_SLIPS_REQUESTS_QUERY_PATTERN = + String.format(REQUESTS_QUERY_PATTERN_TEMPLATE, "Page"); + private static final String REQUESTS_WITHOUT_ITEM_QUERY_PATTERN = + "requestType==\"Hold\"\\ and \\(requestLevel==\"Title\"\\) and " + + "\\(status==\\(\"Open - Not yet filled\"\\)\\) not \\(itemId=\"\"\\)"; + private static final String ITEMS_QUERY_PATTERN_TEMPLATE = + "status.name==\\(%s\\) and \\(effectiveLocationId==\\(.*\\)\\)"; + private static final String PICK_SLIPS_ITEMS_QUERY_PATTERN = + String.format(ITEMS_QUERY_PATTERN_TEMPLATE, joinForMatchAnyQuery(PICK_SLIPS_ITEM_STATUSES)); + private static final String SEARCH_SLIPS_ITEMS_QUERY_PATTERN = + String.format(ITEMS_QUERY_PATTERN_TEMPLATE, joinForMatchAnyQuery(SEARCH_SLIPS_ITEM_STATUSES)); private static final String INSTITUTION_ID = randomId(); private static final String CAMPUS_ID = randomId(); @@ -107,18 +134,24 @@ void pickSlipsAreBuiltSuccessfully() { createStubForLocations(emptyList(), TENANT_ID_UNIVERSITY); createStubForLocations(emptyList(), TENANT_ID_CONSORTIUM); - SearchItem searchItemCollege = buildSearchItem("item_barcode_college", PAGED, - locationCollege.getId(), TENANT_ID_COLLEGE); - SearchHolding searchHoldingCollege = buildSearchHolding(searchItemCollege); - SearchInstance searchInstanceCollege = buildSearchInstance("title_college", - List.of(searchHoldingCollege), List.of(searchItemCollege)); - createStubForInstanceSearch(List.of(locationCollege.getId()), List.of(searchInstanceCollege)); + Instance instance = buildInstance("Test title"); + createStubForInstances(List.of(instance)); - Request requestForCollegeItem = buildRequest(PAGE, searchItemCollege, randomId()); - createStubForRequests(List.of(searchItemCollege.getId()), List.of(requestForCollegeItem)); + HoldingsRecord holdingCollege = buildHolding(instance.getId(), randomId()); + createStubForHoldings(List.of(holdingCollege), TENANT_ID_COLLEGE); - Item itemCollege = buildItem(searchItemCollege); - createStubForItems(List.of(itemCollege), TENANT_ID_COLLEGE); + Item itemCollege = buildItem("item_barcode_college", PAGED, locationCollege.getId(), + holdingCollege.getId()); + createStubForItems(List.of(itemCollege), List.of(locationCollege), TENANT_ID_COLLEGE, + PICK_SLIPS_ITEMS_QUERY_PATTERN); + + User requester = buildUser("user_barcode"); + createStubForUsers(List.of(requester)); + + Request requestForCollegeItem = buildRequest(PAGE, itemCollege.getId(), holdingCollege.getId(), + instance.getId(), requester.getId()); + createStubForRequests(List.of(itemCollege.getId()), List.of(requestForCollegeItem), + PICK_SLIPS_REQUESTS_QUERY_PATTERN); MaterialType materialType = buildMaterialType(); createStubForMaterialTypes(List.of(materialType), TENANT_ID_COLLEGE); @@ -142,9 +175,6 @@ void pickSlipsAreBuiltSuccessfully() { createStubForServicePoints(List.of(primaryServicePoint), TENANT_ID_COLLEGE); createStubForServicePoints(List.of(pickupServicePoint), TENANT_ID_CONSORTIUM); - User requester = buildUser(requestForCollegeItem.getRequesterId(), "user_barcode"); - createStubForUsers(List.of(requester)); - UserGroup userGroup = buildUserGroup(requester.getPatronGroup(), "Test user group"); createStubForUserGroups(List.of(userGroup)); @@ -164,63 +194,145 @@ void pickSlipsAreBuiltSuccessfully() { .jsonPath("pickSlips[*].request").exists() .jsonPath("pickSlips[*].requester").exists(); - // verify that locations were searched in all tenants - Stream.of(TENANT_ID_CONSORTIUM, TENANT_ID_COLLEGE, TENANT_ID_UNIVERSITY) - .forEach(tenantId -> wireMockServer.verify(getRequestedFor(urlPathMatching(LOCATIONS_URL)) - .withHeader(HEADER_TENANT, equalTo(tenantId)))); - - // verify that service points were searched only in central tenant (pickup service point) - // and lending tenant (item's location primary service point) - wireMockServer.verify(getRequestedFor(urlPathMatching(SERVICE_POINTS_URL)) - .withHeader(HEADER_TENANT, equalTo(TENANT_ID_CONSORTIUM))); - wireMockServer.verify(getRequestedFor(urlPathMatching(SERVICE_POINTS_URL)) - .withHeader(HEADER_TENANT, equalTo(TENANT_ID_COLLEGE))); - wireMockServer.verify(0, getRequestedFor(urlPathMatching(SERVICE_POINTS_URL)) - .withHeader(HEADER_TENANT, equalTo(TENANT_ID_UNIVERSITY))); - - // verify that requesters were searched in central tenant only - wireMockServer.verify(getRequestedFor(urlPathMatching(USERS_URL)) - .withQueryParam("query", matching(SEARCH_BY_ID_QUERY_PATTERN)) // to ignore system user's internal calls - .withHeader(HEADER_TENANT, equalTo(TENANT_ID_CONSORTIUM))); - wireMockServer.verify(0, getRequestedFor(urlPathMatching(USERS_URL)) - .withQueryParam("query", matching(SEARCH_BY_ID_QUERY_PATTERN)) - .withHeader(HEADER_TENANT, equalTo(TENANT_ID_COLLEGE))); - wireMockServer.verify(0, getRequestedFor(urlPathMatching(USERS_URL)) - .withQueryParam("query", matching(SEARCH_BY_ID_QUERY_PATTERN)) - .withHeader(HEADER_TENANT, equalTo(TENANT_ID_UNIVERSITY))); - - // verify interactions with central tenant only - Stream.of(INSTANCE_SEARCH_URL, REQUESTS_URL, USER_GROUPS_URL, DEPARTMENTS_URL, ADDRESS_TYPES_URL) - .forEach(url -> { - wireMockServer.verify(getRequestedFor(urlPathMatching(url)) - .withHeader(HEADER_TENANT, equalTo(TENANT_ID_CONSORTIUM))); - wireMockServer.verify(0, getRequestedFor(urlPathMatching(url)) - .withHeader(HEADER_TENANT, equalTo(TENANT_ID_COLLEGE))); - wireMockServer.verify(0, getRequestedFor(urlPathMatching(url)) - .withHeader(HEADER_TENANT, equalTo(TENANT_ID_UNIVERSITY))); - }); - - // verify interactions with lending tenant only - Stream.of(ITEMS_URL, MATERIAL_TYPES_URL, LOAN_TYPES_URL, LIBRARIES_URL, CAMPUSES_URL, INSTITUTIONS_URL) - .forEach(url -> { - wireMockServer.verify(0, getRequestedFor(urlPathMatching(url)) - .withHeader(HEADER_TENANT, equalTo(TENANT_ID_CONSORTIUM))); - wireMockServer.verify(getRequestedFor(urlPathMatching(url)) - .withHeader(HEADER_TENANT, equalTo(TENANT_ID_COLLEGE))); - wireMockServer.verify(0, getRequestedFor(urlPathMatching(url)) - .withHeader(HEADER_TENANT, equalTo(TENANT_ID_UNIVERSITY))); - }); + verifyOutgoingGetRequests(LOCATIONS_URL, 1, 1, 1); + verifyOutgoingGetRequests(SERVICE_POINTS_URL, 1, 1, 0); + verifyOutgoingGetRequests(ITEMS_URL, 0, 1, 0); + verifyOutgoingGetRequests(HOLDINGS_URL, 0, 1, 0); + verifyOutgoingGetRequests(INSTANCES_URL, 1, 0, 0); + verifyOutgoingGetRequests(REQUESTS_URL, 1, 0, 0); + verifyOutgoingGetRequests(USER_GROUPS_URL, 1, 0, 0); + verifyOutgoingGetRequests(DEPARTMENTS_URL, 1, 0, 0); + verifyOutgoingGetRequests(ADDRESS_TYPES_URL, 1, 0, 0); + verifyOutgoingGetRequests(MATERIAL_TYPES_URL, 0, 1, 0); + verifyOutgoingGetRequests(LOAN_TYPES_URL, 0, 1, 0); + verifyOutgoingGetRequests(LIBRARIES_URL, 0, 1, 0); + verifyOutgoingGetRequests(CAMPUSES_URL, 0, 1, 0); + verifyOutgoingGetRequests(INSTITUTIONS_URL, 0, 1, 0); + + RequestPatternBuilder usersRequestPattern = getRequestedFor(urlPathMatching(USERS_URL)) + .withQueryParam("query", matching(SEARCH_BY_ID_QUERY_PATTERN)); // to ignore system user's internal calls + verifyOutgoingRequests(usersRequestPattern, 1, 0, 0); + } + + @Test + @SneakyThrows + void searchSlipsAreBuiltSuccessfully() { + Location locationCollege = buildLocation("Location college"); + Location locationUniversity = buildLocation("Location university"); + createStubForLocations(List.of(locationCollege), TENANT_ID_COLLEGE); + createStubForLocations(List.of(locationUniversity), TENANT_ID_UNIVERSITY); + createStubForLocations(emptyList(), TENANT_ID_CONSORTIUM); + + Instance instanceWithItem = buildInstance("Instance with item"); + Instance instanceWithoutItem = buildInstance("Instance without item"); + createStubForInstances(List.of(instanceWithItem)); + createStubForInstances(List.of(instanceWithoutItem)); + + HoldingsRecord holdingWithItem = buildHolding(instanceWithItem.getId(), randomId()); + HoldingsRecord holdingWithoutItem = buildHolding(instanceWithoutItem.getId(), + locationUniversity.getId()); + createStubForHoldings(emptyList(), TENANT_ID_COLLEGE, List.of(instanceWithoutItem.getId())); + createStubForHoldings(List.of(holdingWithoutItem), TENANT_ID_UNIVERSITY, + List.of(instanceWithoutItem.getId())); + createStubForHoldings(List.of(holdingWithItem), TENANT_ID_COLLEGE, List.of(holdingWithItem.getId())); + + Item itemCollege = buildItem("item_barcode_college", CHECKED_OUT, locationCollege.getId(), + holdingWithItem.getId()); + createStubForItems(List.of(itemCollege), List.of(locationCollege), TENANT_ID_COLLEGE, + SEARCH_SLIPS_ITEMS_QUERY_PATTERN); + createStubForItems(emptyList(), List.of(locationUniversity), TENANT_ID_UNIVERSITY, + SEARCH_SLIPS_ITEMS_QUERY_PATTERN); + + User requester = buildUser("user_barcode"); + createStubForUsers(List.of(requester)); + + Request requestWithItem = buildRequest(HOLD, itemCollege.getId(), holdingWithItem.getId(), + instanceWithItem.getId(), requester.getId()); + Request requestWithoutItemId = buildRequest(HOLD, null, null, instanceWithoutItem.getId(), + requester.getId()); + createStubForRequests(List.of(itemCollege.getId()), List.of(requestWithItem), + SEARCH_SLIPS_REQUESTS_QUERY_PATTERN); + createStubForRequests(List.of(requestWithoutItemId), REQUESTS_WITHOUT_ITEM_QUERY_PATTERN); + + MaterialType materialType = buildMaterialType(); + createStubForMaterialTypes(List.of(materialType), TENANT_ID_COLLEGE); + + LoanType loanType = buildLoanType(); + createStubForLoanTypes(List.of(loanType), TENANT_ID_COLLEGE); + + Library library = buildLibrary(); + createStubForLibraries(List.of(library), TENANT_ID_COLLEGE); + + Campus campus = buildCampus(); + createStubForCampuses(List.of(campus), TENANT_ID_COLLEGE); + + Institution institution = buildInstitution(); + createStubForInstitutions(List.of(institution), TENANT_ID_COLLEGE); + + ServicePoint primaryServicePoint = buildServicePoint(PRIMARY_SERVICE_POINT_ID, + "Primary service point"); + ServicePoint pickupServicePoint = buildServicePoint( + requestWithItem.getPickupServicePointId(), "Pickup service point"); + createStubForServicePoints(List.of(primaryServicePoint), TENANT_ID_COLLEGE); + createStubForServicePoints(List.of(pickupServicePoint), TENANT_ID_CONSORTIUM); + + UserGroup userGroup = buildUserGroup(requester.getPatronGroup(), "Test user group"); + createStubForUserGroups(List.of(userGroup)); + + List departments = buildDepartments(requester); + createStubForDepartments(departments); + + List addressTypes = buildAddressTypes(requester); + createStubForAddressTypes(addressTypes); + + getSearchSlips() + .expectStatus().isOk() + .expectBody() + .jsonPath("searchSlips").value(hasSize(2)) + .jsonPath("totalRecords").value(is(2)) + .jsonPath("searchSlips[*].currentDateTime").exists() + .jsonPath("searchSlips[*].item").exists() + .jsonPath("searchSlips[*].request").exists() + .jsonPath("searchSlips[*].requester").exists(); + + verifyOutgoingGetRequests(LOCATIONS_URL, 1, 1, 1); + verifyOutgoingGetRequests(SERVICE_POINTS_URL, 1, 1, 0); + verifyOutgoingGetRequests(ITEMS_URL, 0, 1, 1); + verifyOutgoingGetRequests(HOLDINGS_URL, 0, 2, 1); + verifyOutgoingGetRequests(INSTANCES_URL, 2, 0, 0); + verifyOutgoingGetRequests(REQUESTS_URL, 2, 0, 0); + verifyOutgoingGetRequests(USER_GROUPS_URL, 1, 0, 0); + verifyOutgoingGetRequests(DEPARTMENTS_URL, 1, 0, 0); + verifyOutgoingGetRequests(ADDRESS_TYPES_URL, 1, 0, 0); + verifyOutgoingGetRequests(MATERIAL_TYPES_URL, 0, 1, 0); + verifyOutgoingGetRequests(LOAN_TYPES_URL, 0, 1, 0); + verifyOutgoingGetRequests(LIBRARIES_URL, 0, 1, 0); + verifyOutgoingGetRequests(CAMPUSES_URL, 0, 1, 0); + verifyOutgoingGetRequests(INSTITUTIONS_URL, 0, 1, 0); + + RequestPatternBuilder usersRequestPattern = getRequestedFor(urlPathMatching(USERS_URL)) + .withQueryParam("query", matching(SEARCH_BY_ID_QUERY_PATTERN)); // to ignore system user's internal calls + verifyOutgoingRequests(usersRequestPattern, 1, 0, 0); } private WebTestClient.ResponseSpec getPickSlips() { return getPickSlips(SERVICE_POINT_ID); } + private WebTestClient.ResponseSpec getSearchSlips() { + return getSearchSlips(SERVICE_POINT_ID); + } + @SneakyThrows private WebTestClient.ResponseSpec getPickSlips(String servicePointId) { return doGet(PICK_SLIPS_URL + "/" + servicePointId); } + @SneakyThrows + private WebTestClient.ResponseSpec getSearchSlips(String servicePointId) { + return doGet(SEARCH_SLIPS_URL + "/" + servicePointId); + } + private static Location buildLocation(String name) { return new Location() .id(randomId()) @@ -232,58 +344,47 @@ private static Location buildLocation(String name) { .primaryServicePoint(UUID.fromString(PRIMARY_SERVICE_POINT_ID)); } - private static SearchItem buildSearchItem(String barcode, ItemStatus.NameEnum itemStatus, - String locationId, String tenant) { - - return new SearchItem() - .id(randomId()) - .tenantId(tenant) - .barcode(barcode) - .holdingsRecordId(randomId()) - .status(new SearchItemStatus().name(itemStatus.getValue())) - .effectiveLocationId(locationId) - .materialTypeId(MATERIAL_TYPE_ID); - } - - private static SearchInstance buildSearchInstance(String title, List holdings, - List items) { - - return new SearchInstance() + private static Instance buildInstance(String title) { + return new Instance() .id(randomId()) - .tenantId(TENANT_ID_CONSORTIUM) .title(title) - .holdings(holdings) - .items(items) .contributors(List.of( - new Contributor().name("First, Author").primary(true), - new Contributor().name("Second, Author"))); + new InstanceContributorsInner().name("First, Author").primary(true), + new InstanceContributorsInner().name("Second, Author").primary(null))); } - private static SearchHolding buildSearchHolding(SearchItem searchItem) { - return new SearchHolding() - .id(searchItem.getHoldingsRecordId()) - .tenantId(searchItem.getTenantId()); - } + private static Item buildItem(String barcode, ItemStatus.NameEnum status, String locationId, + String holdingId) { - private static Item buildItem(SearchItem searchItem) { return new Item() - .id(searchItem.getId()) - .barcode(searchItem.getBarcode()) - .holdingsRecordId(searchItem.getHoldingsRecordId()) - .status(new ItemStatus(ItemStatus.NameEnum.fromValue(searchItem.getStatus().getName()))) - .effectiveLocationId(searchItem.getEffectiveLocationId()) - .materialTypeId(searchItem.getMaterialTypeId()) + .id(randomId()) + .barcode(barcode) + .holdingsRecordId(holdingId) + .status(new ItemStatus(status)) + .effectiveLocationId(locationId) + .materialTypeId(MATERIAL_TYPE_ID) .permanentLoanTypeId(LOAN_TYPE_ID); } - private static Request buildRequest(Request.RequestTypeEnum requestTypeEnum, SearchItem item, - String requesterId) { + private static HoldingsRecord buildHolding(String instanceId, String locationId) { + return new HoldingsRecord() + .id(randomId()) + .instanceId(instanceId) + .copyNumber("Holding copy number") + .permanentLocationId(locationId) + .effectiveLocationId(locationId); + } + + private static Request buildRequest(Request.RequestTypeEnum requestTypeEnum, String itemId, + String holdingId, String instanceId, String requesterId) { return new Request() .id(randomId()) .requestType(requestTypeEnum) .requestLevel(Request.RequestLevelEnum.TITLE) - .itemId(item.getId()) + .itemId(itemId) + .holdingsRecordId(holdingId) + .instanceId(instanceId) .pickupServicePointId(randomId()) .requesterId(requesterId); } @@ -326,9 +427,9 @@ private static ServicePoint buildServicePoint(String id, String name) { .name(name); } - private static User buildUser(String id, String barcode) { + private static User buildUser(String barcode) { return new User() - .id(id) + .id(randomId()) .barcode(barcode) .departments(Set.of(randomId(), randomId())) .patronGroup(randomId()) @@ -395,47 +496,86 @@ private static void createStubForLocations(List locations, String tena .willReturn(okJson(asJsonString(mockResponse)))); } - private static void createStubForInstanceSearch(Collection locationIds, - List instances) { - SearchInstancesResponse mockResponse = new SearchInstancesResponse() - .instances(instances) - .totalRecords(instances.size()); - wireMockServer.stubFor(WireMock.get(urlPathEqualTo(INSTANCE_SEARCH_URL)) - .withQueryParam("expandAll", equalTo("true")) - .withQueryParam("limit", equalTo("500")) - .withQueryParam("query", matching(PICK_SLIPS_INSTANCE_SEARCH_QUERY_PATTERN)) - .withQueryParam("query", containsInAnyOrder(locationIds)) + private static void createStubForRequests(Collection itemIds, + List requests, String queryPattern) { + + Requests mockResponse = new Requests() + .requests(requests) + .totalRecords(requests.size()); + + wireMockServer.stubFor(WireMock.get(urlPathEqualTo(REQUESTS_URL)) + .withQueryParam("limit", equalTo(DEFAULT_LIMIT)) + .withQueryParam("query", matching(queryPattern)) + .withQueryParam("query", containsInAnyOrder(itemIds)) .withHeader(HEADER_TENANT, equalTo(TENANT_ID_CONSORTIUM)) .willReturn(okJson(asJsonString(mockResponse)))); } - private static void createStubForRequests(Collection itemIds, - List requests) { - + private static void createStubForRequests(List requests, String queryPattern) { Requests mockResponse = new Requests() .requests(requests) .totalRecords(requests.size()); wireMockServer.stubFor(WireMock.get(urlPathEqualTo(REQUESTS_URL)) .withQueryParam("limit", equalTo(DEFAULT_LIMIT)) - .withQueryParam("query", matching(PICK_SLIPS_REQUESTS_QUERY_PATTERN)) - .withQueryParam("query", containsInAnyOrder(itemIds)) + .withQueryParam("query", matching(queryPattern)) .withHeader(HEADER_TENANT, equalTo(TENANT_ID_CONSORTIUM)) .willReturn(okJson(asJsonString(mockResponse)))); } - private static void createStubForItems(List items, String tenantId) { + private static void createStubForItems(List items, Collection locations, + String tenantId, String queryPattern) { + Items mockResponse = new Items() .items(items) .totalRecords(items.size()); - Set ids = items.stream() - .map(Item::getId) + Set locationIds = locations.stream() + .map(Location::getId) .collect(toSet()); - createStubForGetByIds(ITEMS_URL, ids, mockResponse, tenantId); + wireMockServer.stubFor(WireMock.get(urlPathEqualTo(ITEMS_URL)) + .withQueryParam("limit", equalTo(DEFAULT_LIMIT)) + .withQueryParam("query", matching(queryPattern)) + .withQueryParam("query", containsInAnyOrder(locationIds)) + .withHeader(HEADER_TENANT, equalTo(tenantId)) + .willReturn(okJson(asJsonString(mockResponse)))); + } + + private static void createStubForHoldings(List holdings, String tenantId) { + HoldingsRecords mockResponse = new HoldingsRecords() + .holdingsRecords(holdings) + .totalRecords(holdings.size()); + + Set ids = holdings.stream() + .map(HoldingsRecord::getId) + .collect(toSet()); + + createStubForGetByIds(HOLDINGS_URL, ids, mockResponse, tenantId); + } + + private static void createStubForHoldings(List holdings, String tenantId, + Collection ids) { + + HoldingsRecords mockResponse = new HoldingsRecords() + .holdingsRecords(holdings) + .totalRecords(holdings.size()); + + createStubForGetByIds(HOLDINGS_URL, ids, mockResponse, tenantId); + } + + private static void createStubForInstances(List instances) { + Instances mockResponse = new Instances() + .instances(instances) + .totalRecords(instances.size()); + + Set ids = instances.stream() + .map(Instance::getId) + .collect(toSet()); + + createStubForGetByIds(INSTANCES_URL, ids, mockResponse, TENANT_ID_CONSORTIUM); } private static void createStubForMaterialTypes(List materialTypes, String tenantId) { @@ -574,4 +714,44 @@ private static MultiValuePattern containsInAnyOrder(Collection values) { .map(WireMock::containing) .toArray(StringValuePattern[]::new)); } + + private static String joinForMatchAnyQuery(EnumSet itemStatuses) { + return joinForMatchAnyQuery( + itemStatuses.stream() + .map(ItemStatus.NameEnum::getValue) + .collect(toSet()) + ); + } + + private static String joinForMatchAnyQuery(Collection values) { + return values.stream() + .map(value -> "\"" + value + "\"") + .collect(joining(" or ")); + } + + private static void verifyOutgoingGetRequests(String urlPattern, int requestsToConsortium, + int requestsToCollege, int requestsToUniversity) { + + verifyOutgoingRequests(HttpMethod.GET, urlPattern, requestsToConsortium, + requestsToCollege, requestsToUniversity); + } + + private static void verifyOutgoingRequests(HttpMethod method, String urlPattern, + int requestsToConsortium, int requestsToCollege, int requestsToUniversity) { + + RequestPatternBuilder requestPattern = requestedFor(method.name(), urlPathMatching(urlPattern)); + verifyOutgoingRequests(requestPattern, requestsToConsortium, requestsToCollege, requestsToUniversity); + } + + private static void verifyOutgoingRequests(RequestPatternBuilder requestPatternBuilder, + int requestsToConsortium, int requestsToCollege, int requestsToUniversity) { + + wireMockServer.verify(requestsToConsortium, requestPatternBuilder.withHeader(HEADER_TENANT, + equalTo(TENANT_ID_CONSORTIUM))); + wireMockServer.verify(requestsToCollege, requestPatternBuilder.withHeader(HEADER_TENANT, + equalTo(TENANT_ID_COLLEGE))); + wireMockServer.verify(requestsToUniversity, requestPatternBuilder.withHeader(HEADER_TENANT, + equalTo(TENANT_ID_UNIVERSITY))); + } + } diff --git a/src/test/java/org/folio/controller/KafkaEventListenerTest.java b/src/test/java/org/folio/controller/KafkaEventListenerTest.java index f3d9b31d..8e896c79 100644 --- a/src/test/java/org/folio/controller/KafkaEventListenerTest.java +++ b/src/test/java/org/folio/controller/KafkaEventListenerTest.java @@ -564,7 +564,7 @@ private static void verifyThatDcbTransactionsWereCreated(EcsTlrEntity ecsTlr) { assertNotNull(secondaryRequestDcbTransactionId); DcbTransaction expectedBorrowerTransaction = new DcbTransaction() - .role(DcbTransaction.RoleEnum.BORROWER) + .role(DcbTransaction.RoleEnum.BORROWING_PICKUP) .item(new DcbItem() .id(ecsTlr.getItemId().toString()) .barcode("test") diff --git a/src/test/java/org/folio/service/EcsTlrServiceTest.java b/src/test/java/org/folio/service/EcsTlrServiceTest.java index eab9a00e..34db8c46 100644 --- a/src/test/java/org/folio/service/EcsTlrServiceTest.java +++ b/src/test/java/org/folio/service/EcsTlrServiceTest.java @@ -46,6 +46,8 @@ class EcsTlrServiceTest { private TenantService tenantService; @Mock private DcbService dcbService; + @Mock + private UserTenantsService userTenantsService; @Spy private final EcsTlrMapper ecsTlrMapper = new EcsTlrMapperImpl(); @@ -99,12 +101,13 @@ void ecsTlrShouldBeCreatedThenUpdatedAndDeleted(EcsTlr.RequestLevelEnum requestL .id(UUID.randomUUID().toString()) .itemId(UUID.randomUUID().toString()); + when(userTenantsService.getCentralTenantId()).thenReturn(borrowingTenant); when(ecsTlrRepository.save(any(EcsTlrEntity.class))).thenReturn(mockEcsTlrEntity); - when(tenantService.getBorrowingTenant(any(EcsTlrEntity.class))) + when(tenantService.getPrimaryRequestTenantId(any(EcsTlrEntity.class))) .thenReturn(borrowingTenant); - when(tenantService.getLendingTenants(any(EcsTlrEntity.class))) + when(tenantService.getSecondaryRequestTenants(any(EcsTlrEntity.class))) .thenReturn(List.of(lendingTenant)); - when(requestService.createPrimaryRequest(any(Request.class), any(String.class))) + when(requestService.createPrimaryRequest(any(Request.class), any(String.class), any(String.class))) .thenReturn(new RequestWrapper(primaryRequest, borrowingTenant)); when(requestService.createSecondaryRequest(any(Request.class), any(String.class), any())) .thenReturn(new RequestWrapper(secondaryRequest, borrowingTenant)); @@ -134,27 +137,27 @@ void ecsTlrShouldBeCreatedThenUpdatedAndDeleted(EcsTlr.RequestLevelEnum requestL void canNotCreateEcsTlrWhenFailedToGetBorrowingTenantId() { String instanceId = UUID.randomUUID().toString(); EcsTlr ecsTlr = new EcsTlr().instanceId(instanceId); - when(tenantService.getBorrowingTenant(any(EcsTlrEntity.class))) + when(tenantService.getPrimaryRequestTenantId(any(EcsTlrEntity.class))) .thenReturn(null); TenantPickingException exception = assertThrows(TenantPickingException.class, () -> ecsTlrService.create(ecsTlr)); - assertEquals("Failed to get borrowing tenant", exception.getMessage()); + assertEquals("Failed to get primary request tenant", exception.getMessage()); } @Test void canNotCreateEcsTlrWhenFailedToGetLendingTenants() { String instanceId = UUID.randomUUID().toString(); EcsTlr ecsTlr = new EcsTlr().instanceId(instanceId); - when(tenantService.getBorrowingTenant(any(EcsTlrEntity.class))) + when(tenantService.getPrimaryRequestTenantId(any(EcsTlrEntity.class))) .thenReturn("borrowing_tenant"); - when(tenantService.getLendingTenants(any(EcsTlrEntity.class))) + when(tenantService.getSecondaryRequestTenants(any(EcsTlrEntity.class))) .thenReturn(emptyList()); TenantPickingException exception = assertThrows(TenantPickingException.class, () -> ecsTlrService.create(ecsTlr)); - assertEquals("Failed to find lending tenants for instance " + instanceId, exception.getMessage()); + assertEquals("Failed to find secondary request tenants for instance " + instanceId, exception.getMessage()); } } diff --git a/src/test/java/org/folio/service/RequestServiceTest.java b/src/test/java/org/folio/service/RequestServiceTest.java index db3c4d6f..b0261431 100644 --- a/src/test/java/org/folio/service/RequestServiceTest.java +++ b/src/test/java/org/folio/service/RequestServiceTest.java @@ -39,7 +39,6 @@ class RequestServiceTest { private Request secondaryRequest; private static final String ITEM_ID = UUID.randomUUID().toString(); private static final String INSTANCE_ID = UUID.randomUUID().toString(); - private static final String BORROWER_ID = UUID.randomUUID().toString(); private static final String LENDER_ID = UUID.randomUUID().toString(); private static final String HOLDINGS_RECORD_ID = "10cd3a5a-d36f-4c7a-bc4f-e1ae3cf820c9"; private static final String LENDING_LIBRARY_CODE = "TEST_CODE"; @@ -56,19 +55,18 @@ void setUp() { } @Test - void shouldReturnNullIfEcsTlrOrSecondaryRequestIsNull() { - assertNull(requestService.createCirculationItem(null, secondaryRequest, BORROWER_ID, LENDER_ID)); - assertNull(requestService.createCirculationItem(ecsTlrEntity, null, BORROWER_ID, LENDER_ID)); + void shouldReturnNullIfRequestIsNull() { + assertNull(requestService.createCirculationItem(null, LENDER_ID)); } @Test void shouldReturnNullIfItemIdOrInstanceIdIsNull() { secondaryRequest.setItemId(null); - assertNull(requestService.createCirculationItem(ecsTlrEntity, secondaryRequest, BORROWER_ID, LENDER_ID)); + assertNull(requestService.createCirculationItem(secondaryRequest, LENDER_ID)); secondaryRequest.setItemId(ITEM_ID); secondaryRequest.setInstanceId(null); - assertNull(requestService.createCirculationItem(ecsTlrEntity, secondaryRequest, BORROWER_ID, LENDER_ID)); + assertNull(requestService.createCirculationItem(secondaryRequest, LENDER_ID)); } @Test @@ -76,7 +74,7 @@ void shouldReturnExistingCirculationItemIfFound() { CirculationItem existingItem = new CirculationItem(); when(circulationItemClient.getCirculationItem(any())).thenReturn(existingItem); - assertEquals(existingItem, requestService.createCirculationItem(ecsTlrEntity, secondaryRequest, BORROWER_ID, LENDER_ID)); + assertEquals(existingItem, requestService.createCirculationItem(secondaryRequest, LENDER_ID)); } @Test @@ -101,7 +99,7 @@ void shouldCreateCirculationItem() { .dcbItem(true) .instanceTitle(instanceTitle) .lendingLibraryCode(LENDING_LIBRARY_CODE); - requestService.createCirculationItem(ecsTlrEntity, secondaryRequest, BORROWER_ID, LENDER_ID); + requestService.createCirculationItem(secondaryRequest, LENDER_ID); verify(circulationItemClient).createCirculationItem(ITEM_ID, expectedCirculationItem); } diff --git a/src/test/java/org/folio/service/StaffSlipsServiceTest.java b/src/test/java/org/folio/service/StaffSlipsServiceTest.java new file mode 100644 index 00000000..41b0bbfa --- /dev/null +++ b/src/test/java/org/folio/service/StaffSlipsServiceTest.java @@ -0,0 +1,646 @@ +package org.folio.service; + +import static java.util.Collections.emptyList; +import static java.util.UUID.randomUUID; +import static java.util.stream.Collectors.toSet; +import static org.folio.domain.dto.ItemStatus.NameEnum.CHECKED_OUT; +import static org.folio.domain.dto.ItemStatus.NameEnum.PAGED; +import static org.folio.domain.dto.Request.FulfillmentPreferenceEnum.DELIVERY; +import static org.folio.domain.dto.Request.FulfillmentPreferenceEnum.HOLD_SHELF; +import static org.folio.domain.dto.Request.RequestLevelEnum.ITEM; +import static org.folio.domain.dto.Request.RequestLevelEnum.TITLE; +import static org.folio.domain.dto.Request.RequestTypeEnum.HOLD; +import static org.folio.domain.dto.Request.RequestTypeEnum.PAGE; +import static org.folio.support.CqlQuery.exactMatch; +import static org.folio.support.CqlQuery.exactMatchAny; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.empty; +import static org.hamcrest.Matchers.hasSize; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.notNullValue; +import static org.hamcrest.Matchers.nullValue; +import static org.hamcrest.Matchers.oneOf; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anySet; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.verifyNoInteractions; +import static org.mockito.Mockito.when; + +import java.util.Collection; +import java.util.Date; +import java.util.List; +import java.util.Set; +import java.util.UUID; +import java.util.concurrent.Callable; +import java.util.stream.Stream; + +import org.folio.domain.dto.AddressType; +import org.folio.domain.dto.Campus; +import org.folio.domain.dto.Department; +import org.folio.domain.dto.HoldingsRecord; +import org.folio.domain.dto.Instance; +import org.folio.domain.dto.InstanceContributorsInner; +import org.folio.domain.dto.Institution; +import org.folio.domain.dto.Item; +import org.folio.domain.dto.ItemEffectiveCallNumberComponents; +import org.folio.domain.dto.ItemStatus; +import org.folio.domain.dto.Library; +import org.folio.domain.dto.LoanType; +import org.folio.domain.dto.Location; +import org.folio.domain.dto.MaterialType; +import org.folio.domain.dto.Request; +import org.folio.domain.dto.ServicePoint; +import org.folio.domain.dto.StaffSlip; +import org.folio.domain.dto.StaffSlipItem; +import org.folio.domain.dto.StaffSlipRequest; +import org.folio.domain.dto.StaffSlipRequester; +import org.folio.domain.dto.Tenant; +import org.folio.domain.dto.User; +import org.folio.domain.dto.UserGroup; +import org.folio.domain.dto.UserPersonal; +import org.folio.domain.dto.UserPersonalAddressesInner; +import org.folio.service.impl.PickSlipsService; +import org.folio.service.impl.SearchSlipsService; +import org.folio.spring.service.SystemUserScopedExecutionService; +import org.folio.support.CqlQuery; +import org.junit.jupiter.api.BeforeEach; +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 org.mockito.junit.jupiter.MockitoSettings; +import org.mockito.quality.Strictness; + +@ExtendWith(MockitoExtension.class) +@MockitoSettings(strictness = Strictness.LENIENT) +class StaffSlipsServiceTest { + + private static final String SERVICE_POINT_ID = randomId(); + private static final String ITEM_ID = randomId(); + private static final String HOLDING_ID = randomId(); + private static final String INSTANCE_ID = randomId(); + private static final String PICKUP_SERVICE_POINT_ID = randomId(); + private static final String REQUESTER_ID = randomId(); + private static final String DELIVERY_ADDRESS_TYPE_ID = randomId(); + private static final String PRIMARY_ADDRESS_TYPE_ID = randomId(); + private static final Date REQUEST_DATE = new Date(); + private static final Date REQUEST_EXPIRATION_DATE = new Date(); + private static final Date HOLD_SHELF_EXPIRATION_DATE = new Date(); + + @Mock + private LocationService locationService; + @Mock + private InventoryService inventoryService; + @Mock + private RequestService requestService; + @Mock + private ConsortiaService consortiaService; + @Mock + private SystemUserScopedExecutionService executionService; + @Mock + private UserService userService; + @Mock + private UserGroupService userGroupService; + @Mock + private DepartmentService departmentService; + @Mock + private AddressTypeService addressTypeService; + @Mock + private ServicePointService servicePointService; + + @InjectMocks + private PickSlipsService pickSlipsService; + + @InjectMocks + private SearchSlipsService searchSlipsService; + + @BeforeEach + public void setup() { + // Bypass the use of system user and return the result of Callable immediately + when(executionService.executeSystemUserScoped(any(String.class), any(Callable.class))) + .thenAnswer(invocation -> invocation.getArgument(1, Callable.class).call()); + } + + @Test + void pickSlipsAreBuiltSuccessfully() { + Request request = buildRequest(PAGE, ITEM); + Location location = buildLocation(); + Instance instance = buildInstance(request.getInstanceId()); + HoldingsRecord holding = buildHolding(request.getHoldingsRecordId(), request.getInstanceId(), location.getId()); + Item item = buildItem(PAGED, request.getItemId(), request.getHoldingsRecordId(), location.getId()); + MaterialType materialType = buildMaterialType(item.getMaterialTypeId()); + LoanType loanType = buildLoanType(item.getPermanentLoanTypeId()); + Library library = buildLibrary(location.getLibraryId()); + Campus campus = buildCampus(location.getCampusId()); + Institution institution = buildInstitution(location.getInstitutionId()); + ServicePoint primaryServicePoint = buildPrimaryServicePoint(location.getPrimaryServicePoint().toString()); + ServicePoint pickupServicePoint = buildPickupServicePoint(request.getPickupServicePointId()); + AddressType primaryAddressType = buildPrimaryAddressType(); + AddressType deliveryAddressType = buildDeliveryAddressType(); + Collection departments = buildDepartments(); + Set departmentIds = departments.stream().map(Department::getId).collect(toSet()); + User requester = buildRequester(request.getRequesterId(), departmentIds); + UserGroup userGroup = buildUserGroup(requester.getPatronGroup()); + + Set addressTypeIds = Stream.of(primaryAddressType, deliveryAddressType) + .map(AddressType::getId) + .collect(toSet()); + CqlQuery itemsCommonQuery = CqlQuery.exactMatchAny("status.name", List.of("Paged")); + CqlQuery requestsCommonQuery = exactMatchAny("requestType", List.of("Page")) + .and(exactMatchAny("status", List.of("Open - Not yet filled"))); + + when(consortiaService.getAllConsortiumTenants()) + .thenReturn(List.of(new Tenant().id("consortium"))); + when(locationService.findLocations(exactMatch("primaryServicePoint", SERVICE_POINT_ID))) + .thenReturn(List.of(location)); + when(inventoryService.findItems(itemsCommonQuery, "effectiveLocationId", Set.of(location.getId()))) + .thenReturn(List.of(item)); + when(requestService.getRequestsFromStorage(requestsCommonQuery, "itemId", List.of(item.getId()))) + .thenReturn(List.of(request)); + when(inventoryService.findInstances(Set.of(instance.getId()))) + .thenReturn(List.of(instance)); + when(inventoryService.findHoldings(Set.of(holding.getId()))) + .thenReturn(List.of(holding)); + when(inventoryService.findMaterialTypes(Set.of(materialType.getId()))) + .thenReturn(List.of(materialType)); + when(inventoryService.findLoanTypes(Set.of(loanType.getId()))) + .thenReturn(List.of(loanType)); + when(inventoryService.findLibraries(Set.of(library.getId()))) + .thenReturn(List.of(library)); + when(inventoryService.findCampuses(Set.of(campus.getId()))) + .thenReturn(List.of(campus)); + when(inventoryService.findInstitutions(Set.of(institution.getId()))) + .thenReturn(List.of(institution)); + when(servicePointService.find(Set.of(primaryServicePoint.getId()))) + .thenReturn(List.of(primaryServicePoint)); + when(servicePointService.find(Set.of(pickupServicePoint.getId()))) + .thenReturn(List.of(pickupServicePoint)); + when(userService.find(Set.of(requester.getId()))) + .thenReturn(List.of(requester)); + when(userGroupService.find(Set.of(userGroup.getId()))) + .thenReturn(List.of(userGroup)); + when(departmentService.findDepartments(departmentIds)) + .thenReturn(departments); + when(addressTypeService.findAddressTypes(addressTypeIds)) + .thenReturn(List.of(primaryAddressType, deliveryAddressType)); + + Collection staffSlips = pickSlipsService.getStaffSlips(SERVICE_POINT_ID); + assertThat(staffSlips, hasSize(1)); + + StaffSlip actualPickSlip = staffSlips.iterator().next(); + assertThat(actualPickSlip.getCurrentDateTime(), notNullValue()); + + StaffSlipItem pickSlipItem = actualPickSlip.getItem(); + assertThat(pickSlipItem.getBarcode(), is("item_barcode")); + assertThat(pickSlipItem.getStatus(), is("Paged")); + assertThat(pickSlipItem.getMaterialType(), is("Material type")); + assertThat(pickSlipItem.getLoanType(), is("Loan type")); + assertThat(pickSlipItem.getEnumeration(), is("enum")); + assertThat(pickSlipItem.getVolume(), is("vol")); + assertThat(pickSlipItem.getChronology(), is("chrono")); + assertThat(pickSlipItem.getYearCaption(), oneOf("2000; 2001", "2001; 2000")); + assertThat(pickSlipItem.getCopy(), is("copy")); + assertThat(pickSlipItem.getNumberOfPieces(), is("1")); + assertThat(pickSlipItem.getDisplaySummary(), is("summary")); + assertThat(pickSlipItem.getDescriptionOfPieces(), is("description")); + assertThat(pickSlipItem.getTitle(), is("Test title")); + assertThat(pickSlipItem.getPrimaryContributor(), is("First, Author")); + assertThat(pickSlipItem.getAllContributors(), is("First, Author; Second, Author")); + assertThat(pickSlipItem.getEffectiveLocationSpecific(), is("Test location")); + assertThat(pickSlipItem.getEffectiveLocationLibrary(), is("Library")); + assertThat(pickSlipItem.getEffectiveLocationCampus(), is("Campus")); + assertThat(pickSlipItem.getEffectiveLocationInstitution(), is("Institution")); + assertThat(pickSlipItem.getEffectiveLocationPrimaryServicePointName(), is("Primary service point")); + assertThat(pickSlipItem.getEffectiveLocationDiscoveryDisplayName(), is("Location display name")); + assertThat(pickSlipItem.getCallNumber(), is("CN")); + assertThat(pickSlipItem.getCallNumberPrefix(), is("PFX")); + assertThat(pickSlipItem.getCallNumberSuffix(), is("SFX")); + + StaffSlipRequest pickSlipRequest = actualPickSlip.getRequest(); + assertThat(pickSlipRequest.getRequestID(), is(UUID.fromString(request.getId()))); + assertThat(pickSlipRequest.getServicePointPickup(), is("Pickup service point")); + assertThat(pickSlipRequest.getRequestDate(), is(request.getRequestDate())); + assertThat(pickSlipRequest.getRequestExpirationDate(), is(request.getRequestExpirationDate())); + assertThat(pickSlipRequest.getHoldShelfExpirationDate(), is(request.getHoldShelfExpirationDate())); + assertThat(pickSlipRequest.getDeliveryAddressType(), is("Delivery address type")); + assertThat(pickSlipRequest.getPatronComments(), is("comment")); + + StaffSlipRequester pickSlipRequester = actualPickSlip.getRequester(); + assertThat(pickSlipRequester.getBarcode(), is("Requester barcode")); + assertThat(pickSlipRequester.getPatronGroup(), is("User group")); + assertThat(pickSlipRequester.getDepartments(), + oneOf("First department; Second department", "Second department; First department")); + assertThat(pickSlipRequester.getFirstName(), is("First name")); + assertThat(pickSlipRequester.getMiddleName(), is("Middle name")); + assertThat(pickSlipRequester.getLastName(), is("Last name")); + assertThat(pickSlipRequester.getPreferredFirstName(), is("Preferred first name")); + assertThat(pickSlipRequester.getAddressLine1(), is("Delivery address line 1")); + assertThat(pickSlipRequester.getAddressLine2(), is("Delivery address line 2")); + assertThat(pickSlipRequester.getCity(), is("Delivery address city")); + assertThat(pickSlipRequester.getRegion(), is("Delivery address region")); + assertThat(pickSlipRequester.getPostalCode(), is("Delivery address zip code")); + assertThat(pickSlipRequester.getCountryId(), is("US")); + assertThat(pickSlipRequester.getAddressType(), is("Delivery address type")); + assertThat(pickSlipRequester.getPrimaryAddressLine1(), is("Primary address line 1")); + assertThat(pickSlipRequester.getPrimaryAddressLine2(), is("Primary address line 2")); + assertThat(pickSlipRequester.getPrimaryCity(), is("Primary address city")); + assertThat(pickSlipRequester.getPrimaryStateProvRegion(), is("Primary address region")); + assertThat(pickSlipRequester.getPrimaryZipPostalCode(), is("Primary address zip code")); + assertThat(pickSlipRequester.getPrimaryCountry(), is("United States")); + assertThat(pickSlipRequester.getPrimaryDeliveryAddressType(), is("Primary address type")); + } + + @Test + void searchSlipsAreBuiltSuccessfully() { + Request requestWithItem = buildRequest(HOLD, ITEM).pickupServicePointId(null); + Request requestWithoutItem = buildRequest(HOLD, TITLE, null, null, requestWithItem.getInstanceId()) + .fulfillmentPreference(HOLD_SHELF) + .pickupServicePointId(PICKUP_SERVICE_POINT_ID) + .deliveryAddressTypeId(null); + Location location = buildLocation(); + Instance instance = buildInstance(requestWithItem.getInstanceId()); + HoldingsRecord holding = buildHolding(requestWithItem.getHoldingsRecordId(), + requestWithItem.getInstanceId(), location.getId()); + Item item = buildItem(CHECKED_OUT, requestWithItem.getItemId(), + requestWithItem.getHoldingsRecordId(), location.getId()); + MaterialType materialType = buildMaterialType(item.getMaterialTypeId()); + LoanType loanType = buildLoanType(item.getPermanentLoanTypeId()); + Library library = buildLibrary(location.getLibraryId()); + Campus campus = buildCampus(location.getCampusId()); + Institution institution = buildInstitution(location.getInstitutionId()); + ServicePoint primaryServicePoint = buildPrimaryServicePoint(location.getPrimaryServicePoint().toString()); + ServicePoint pickupServicePoint = buildPickupServicePoint(requestWithoutItem.getPickupServicePointId()); + AddressType primaryAddressType = buildPrimaryAddressType(); + AddressType deliveryAddressType = buildDeliveryAddressType(); + Collection departments = buildDepartments(); + Set departmentIds = departments.stream().map(Department::getId).collect(toSet()); + User requester = buildRequester(requestWithItem.getRequesterId(), departmentIds); + UserGroup userGroup = buildUserGroup(requester.getPatronGroup()); + + Set addressTypeIds = Stream.of(primaryAddressType, deliveryAddressType) + .map(AddressType::getId) + .collect(toSet()); + + CqlQuery requestsCommonQuery = exactMatchAny("requestType", List.of("Hold")) + .and(exactMatchAny("status", List.of("Open - Not yet filled"))); + + CqlQuery holdsWithoutItemQuery = CqlQuery.exactMatch("requestType", HOLD.getValue()) + .and(CqlQuery.exactMatch("requestLevel", TITLE.getValue())) + .and(CqlQuery.exactMatchAny("status", List.of("Open - Not yet filled"))) + .not(CqlQuery.match("itemId", "")); + + when(consortiaService.getAllConsortiumTenants()) + .thenReturn(List.of(new Tenant().id("consortium"))); + when(locationService.findLocations(exactMatch("primaryServicePoint", SERVICE_POINT_ID))) + .thenReturn(List.of(location)); + when(inventoryService.findItems(any(CqlQuery.class), anyString(), anySet())) + .thenReturn(List.of(item)); + when(requestService.getRequestsFromStorage(requestsCommonQuery, "itemId", List.of(item.getId()))) + .thenReturn(List.of(requestWithItem)); + when(requestService.getRequestsFromStorage(holdsWithoutItemQuery)) + .thenReturn(List.of(requestWithoutItem)); + when(inventoryService.findInstances(Set.of(instance.getId()))) + .thenReturn(List.of(instance)); + when(inventoryService.findHoldings(Set.of(holding.getId()))) + .thenReturn(List.of(holding)); + when(inventoryService.findHoldings(CqlQuery.empty(), "instanceId", Set.of(instance.getId()))) + .thenReturn(List.of(holding)); + when(inventoryService.findMaterialTypes(Set.of(materialType.getId()))) + .thenReturn(List.of(materialType)); + when(inventoryService.findLoanTypes(Set.of(loanType.getId()))) + .thenReturn(List.of(loanType)); + when(inventoryService.findLibraries(Set.of(library.getId()))) + .thenReturn(List.of(library)); + when(inventoryService.findCampuses(Set.of(campus.getId()))) + .thenReturn(List.of(campus)); + when(inventoryService.findInstitutions(Set.of(institution.getId()))) + .thenReturn(List.of(institution)); + when(servicePointService.find(Set.of(primaryServicePoint.getId()))) + .thenReturn(List.of(primaryServicePoint)); + when(servicePointService.find(Set.of(pickupServicePoint.getId()))) + .thenReturn(List.of(pickupServicePoint)); + when(userService.find(Set.of(requester.getId()))) + .thenReturn(List.of(requester)); + when(userGroupService.find(Set.of(userGroup.getId()))) + .thenReturn(List.of(userGroup)); + when(departmentService.findDepartments(departmentIds)) + .thenReturn(departments); + when(addressTypeService.findAddressTypes(addressTypeIds)) + .thenReturn(List.of(primaryAddressType, deliveryAddressType)); + + Collection staffSlips = searchSlipsService.getStaffSlips(SERVICE_POINT_ID); + assertThat(staffSlips, hasSize(2)); + + StaffSlip searchSlipForRequestWithItem = staffSlips.stream() + .filter(slip -> slip.getRequest().getRequestID().toString().equals(requestWithItem.getId())) + .findFirst() + .orElseThrow(); + + StaffSlipItem searchSlipItem = searchSlipForRequestWithItem.getItem(); + assertThat(searchSlipItem.getBarcode(), is("item_barcode")); + assertThat(searchSlipItem.getStatus(), is("Checked out")); + assertThat(searchSlipItem.getMaterialType(), is("Material type")); + assertThat(searchSlipItem.getLoanType(), is("Loan type")); + assertThat(searchSlipItem.getEnumeration(), is("enum")); + assertThat(searchSlipItem.getVolume(), is("vol")); + assertThat(searchSlipItem.getChronology(), is("chrono")); + assertThat(searchSlipItem.getYearCaption(), oneOf("2000; 2001", "2001; 2000")); + assertThat(searchSlipItem.getCopy(), is("copy")); + assertThat(searchSlipItem.getNumberOfPieces(), is("1")); + assertThat(searchSlipItem.getDisplaySummary(), is("summary")); + assertThat(searchSlipItem.getDescriptionOfPieces(), is("description")); + assertThat(searchSlipItem.getTitle(), is("Test title")); + assertThat(searchSlipItem.getPrimaryContributor(), is("First, Author")); + assertThat(searchSlipItem.getAllContributors(), is("First, Author; Second, Author")); + assertThat(searchSlipItem.getEffectiveLocationSpecific(), is("Test location")); + assertThat(searchSlipItem.getEffectiveLocationLibrary(), is("Library")); + assertThat(searchSlipItem.getEffectiveLocationCampus(), is("Campus")); + assertThat(searchSlipItem.getEffectiveLocationInstitution(), is("Institution")); + assertThat(searchSlipItem.getEffectiveLocationPrimaryServicePointName(), is("Primary service point")); + assertThat(searchSlipItem.getEffectiveLocationDiscoveryDisplayName(), is("Location display name")); + assertThat(searchSlipItem.getCallNumber(), is("CN")); + assertThat(searchSlipItem.getCallNumberPrefix(), is("PFX")); + assertThat(searchSlipItem.getCallNumberSuffix(), is("SFX")); + + StaffSlipRequester searchSlipWithItemRequester = searchSlipForRequestWithItem.getRequester(); + assertThat(searchSlipWithItemRequester.getAddressLine1(), is("Delivery address line 1")); + assertThat(searchSlipWithItemRequester.getAddressLine2(), is("Delivery address line 2")); + assertThat(searchSlipWithItemRequester.getCity(), is("Delivery address city")); + assertThat(searchSlipWithItemRequester.getRegion(), is("Delivery address region")); + assertThat(searchSlipWithItemRequester.getPostalCode(), is("Delivery address zip code")); + assertThat(searchSlipWithItemRequester.getCountryId(), is("US")); + assertThat(searchSlipWithItemRequester.getAddressType(), is("Delivery address type")); + + assertThat(searchSlipForRequestWithItem.getRequest().getServicePointPickup(), nullValue()); + assertThat(searchSlipForRequestWithItem.getRequest().getDeliveryAddressType(), + is("Delivery address type")); + + StaffSlip searchSlipForRequestWithoutItem = staffSlips.stream() + .filter(slip -> slip.getRequest().getRequestID().toString().equals(requestWithoutItem.getId())) + .findFirst() + .orElseThrow(); + + StaffSlipRequester searchSlipWithoutItemRequester = searchSlipForRequestWithoutItem.getRequester(); + assertThat(searchSlipWithoutItemRequester.getAddressLine1(), nullValue()); + assertThat(searchSlipWithoutItemRequester.getAddressLine2(), nullValue()); + assertThat(searchSlipWithoutItemRequester.getCity(), nullValue()); + assertThat(searchSlipWithoutItemRequester.getRegion(), nullValue()); + assertThat(searchSlipWithoutItemRequester.getPostalCode(), nullValue()); + assertThat(searchSlipWithoutItemRequester.getCountryId(), nullValue()); + assertThat(searchSlipWithoutItemRequester.getAddressType(), nullValue()); + + assertThat(searchSlipForRequestWithoutItem.getRequest().getDeliveryAddressType(), nullValue()); + assertThat(searchSlipForRequestWithoutItem.getRequest().getServicePointPickup(), + is("Pickup service point")); + assertThat(searchSlipForRequestWithoutItem.getItem(), nullValue()); + + Stream.of(searchSlipForRequestWithItem, searchSlipForRequestWithoutItem).forEach(searchSlip -> { + assertThat(searchSlip.getCurrentDateTime(), notNullValue()); + + StaffSlipRequest pickSlipRequest = searchSlip.getRequest(); + assertThat(pickSlipRequest.getRequestDate(), is(REQUEST_DATE)); + assertThat(pickSlipRequest.getRequestExpirationDate(), is(REQUEST_EXPIRATION_DATE)); + assertThat(pickSlipRequest.getHoldShelfExpirationDate(), is(HOLD_SHELF_EXPIRATION_DATE)); + + assertThat(pickSlipRequest.getPatronComments(), is("comment")); + + StaffSlipRequester pickSlipRequester = searchSlip.getRequester(); + assertThat(pickSlipRequester.getBarcode(), is("Requester barcode")); + assertThat(pickSlipRequester.getPatronGroup(), is("User group")); + assertThat(pickSlipRequester.getDepartments(), + oneOf("First department; Second department", "Second department; First department")); + assertThat(pickSlipRequester.getFirstName(), is("First name")); + assertThat(pickSlipRequester.getMiddleName(), is("Middle name")); + assertThat(pickSlipRequester.getLastName(), is("Last name")); + assertThat(pickSlipRequester.getPreferredFirstName(), is("Preferred first name")); + assertThat(pickSlipRequester.getPrimaryAddressLine1(), is("Primary address line 1")); + assertThat(pickSlipRequester.getPrimaryAddressLine2(), is("Primary address line 2")); + assertThat(pickSlipRequester.getPrimaryCity(), is("Primary address city")); + assertThat(pickSlipRequester.getPrimaryStateProvRegion(), is("Primary address region")); + assertThat(pickSlipRequester.getPrimaryZipPostalCode(), is("Primary address zip code")); + assertThat(pickSlipRequester.getPrimaryCountry(), is("United States")); + assertThat(pickSlipRequester.getPrimaryDeliveryAddressType(), is("Primary address type")); + }); + } + + @Test + void noConsortiumTenantsAreFound() { + when(consortiaService.getAllConsortiumTenants()) + .thenReturn(emptyList()); + + Collection staffSlips = pickSlipsService.getStaffSlips(SERVICE_POINT_ID); + + assertThat(staffSlips, empty()); + verifyNoInteractions(locationService, inventoryService, requestService, executionService); + } + + @Test + void noLocationsAreFound() { + when(consortiaService.getAllConsortiumTenants()) + .thenReturn(List.of(new Tenant().id("test_tenant"))); + when(locationService.findLocations(any(CqlQuery.class))) + .thenReturn(emptyList()); + + Collection staffSlips = pickSlipsService.getStaffSlips(SERVICE_POINT_ID); + + assertThat(staffSlips, empty()); + verifyNoInteractions(inventoryService, requestService); + } + + @Test + void noItemsAreFound() { + when(consortiaService.getAllConsortiumTenants()) + .thenReturn(List.of(new Tenant().id("test_tenant"))); + when(locationService.findLocations(any(CqlQuery.class))) + .thenReturn(List.of(new Location().id(randomId()))); + when(inventoryService.findItems(any(), any(), any())) + .thenReturn(emptyList()); + + Collection staffSlips = pickSlipsService.getStaffSlips(SERVICE_POINT_ID); + + assertThat(staffSlips, empty()); + verifyNoInteractions(requestService); + } + + @Test + void noRequestsAreFound() { + when(consortiaService.getAllConsortiumTenants()) + .thenReturn(List.of(new Tenant().id("test_tenant"))); + when(locationService.findLocations(any(CqlQuery.class))) + .thenReturn(List.of(new Location())); + when(inventoryService.findItems(any(), any(), any())) + .thenReturn(List.of(new Item())); + when(requestService.getRequestsFromStorage(any(), any(), any())) + .thenReturn(emptyList()); + + Collection staffSlips = pickSlipsService.getStaffSlips(SERVICE_POINT_ID); + + assertThat(staffSlips, empty()); + } + + private static User buildRequester(String id, Set departments) { + return new User() + .id(id) + .barcode("Requester barcode") + .patronGroup(randomId()) + .departments(departments) + .personal(new UserPersonal() + .firstName("First name") + .middleName("Middle name") + .lastName("Last name") + .preferredFirstName("Preferred first name") + .addresses(List.of( + new UserPersonalAddressesInner() + .id(randomId()) + .primaryAddress(true) + .addressTypeId(PRIMARY_ADDRESS_TYPE_ID) + .addressLine1("Primary address line 1") + .addressLine2("Primary address line 2") + .city("Primary address city") + .region("Primary address region") + .postalCode("Primary address zip code") + .countryId("US"), + new UserPersonalAddressesInner() + .id(randomId()) + .primaryAddress(false) + .addressTypeId(DELIVERY_ADDRESS_TYPE_ID) + .addressLine1("Delivery address line 1") + .addressLine2("Delivery address line 2") + .city("Delivery address city") + .region("Delivery address region") + .postalCode("Delivery address zip code") + .countryId("US") + ))); + } + + private static UserGroup buildUserGroup(String id) { + return new UserGroup() + .id(id) + .group("User group"); + } + + private static List buildDepartments() { + return List.of( + new Department().id(randomId()).name("First department"), + new Department().id(randomId()).name("Second department")); + } + + private static Request buildRequest(Request.RequestTypeEnum type, Request.RequestLevelEnum level) { + return buildRequest(type, level, ITEM_ID, HOLDING_ID, INSTANCE_ID); + } + + private static Request buildRequest(Request.RequestTypeEnum type, Request.RequestLevelEnum level, + String itemId, String holdingId, String instanceId) { + + return new Request() + .id(randomId()) + .itemId(itemId) + .holdingsRecordId(holdingId) + .instanceId(instanceId) + .requestLevel(level) + .requestType(type) + .status(Request.StatusEnum.OPEN_NOT_YET_FILLED) + .pickupServicePointId(PICKUP_SERVICE_POINT_ID) + .requesterId(REQUESTER_ID) + .requestDate(REQUEST_DATE) + .requestExpirationDate(REQUEST_EXPIRATION_DATE) + .holdShelfExpirationDate(REQUEST_EXPIRATION_DATE) + .fulfillmentPreference(DELIVERY) + .deliveryAddressTypeId(DELIVERY_ADDRESS_TYPE_ID) + .patronComments("comment"); + } + + private static AddressType buildDeliveryAddressType() { + return new AddressType().id(DELIVERY_ADDRESS_TYPE_ID).addressType("Delivery address type"); + } + + private static AddressType buildPrimaryAddressType() { + return new AddressType().id(PRIMARY_ADDRESS_TYPE_ID).addressType("Primary address type"); + } + + private static ServicePoint buildPickupServicePoint(String id) { + return new ServicePoint().id(id).name("Pickup service point"); + } + + private static ServicePoint buildPrimaryServicePoint(String id) { + return new ServicePoint().id(id).name("Primary service point"); + } + + private static Institution buildInstitution(String id) { + return new Institution().id(id).name("Institution"); + } + + private static Campus buildCampus(String id) { + return new Campus().id(id).name("Campus"); + } + + private static Library buildLibrary(String id) { + return new Library().id(id).name("Library"); + } + + private static LoanType buildLoanType(String id) { + return new LoanType().id(id).name("Loan type"); + } + + private static MaterialType buildMaterialType(String id) { + return new MaterialType().id(id).name("Material type"); + } + + private static Item buildItem(ItemStatus.NameEnum status, String id, String holdingId, String locationId) { + return new Item() + .id(id) + .holdingsRecordId(holdingId) + .barcode("item_barcode") + .status(new ItemStatus().name(status)) + .materialTypeId(randomId()) + .permanentLoanTypeId(randomId()) + .enumeration("enum") + .volume("vol") + .chronology("chrono") + .yearCaption(Set.of("2000", "2001")) + .copyNumber("copy") + .numberOfPieces("1") + .displaySummary("summary") + .descriptionOfPieces("description") + .effectiveLocationId(locationId) + .effectiveCallNumberComponents(new ItemEffectiveCallNumberComponents() + .callNumber("CN") + .prefix("PFX") + .suffix("SFX")); + } + + private static HoldingsRecord buildHolding(String id, String instanceId, String locationId) { + return new HoldingsRecord() + .id(id) + .instanceId(instanceId) + .permanentLocationId(locationId) + .effectiveLocationId(locationId); + } + + private static Instance buildInstance(String instanceId) { + return new Instance() + .id(instanceId) + .title("Test title") + .contributors(List.of( + new InstanceContributorsInner().name("First, Author").primary(true), + new InstanceContributorsInner().name("Second, Author").primary(null) + )); + } + + private static Location buildLocation() { + return new Location() + .id(randomId()) + .name("Test location") + .discoveryDisplayName("Location display name") + .libraryId(randomId()) + .campusId(randomId()) + .institutionId(randomId()) + .primaryServicePoint(randomUUID()); + } + + private static String randomId() { + return randomUUID().toString(); + } + +} \ No newline at end of file diff --git a/src/test/java/org/folio/service/TenantServiceTest.java b/src/test/java/org/folio/service/TenantServiceTest.java index 447a7578..b7bc2e40 100644 --- a/src/test/java/org/folio/service/TenantServiceTest.java +++ b/src/test/java/org/folio/service/TenantServiceTest.java @@ -40,7 +40,7 @@ class TenantServiceTest { void getBorrowingTenant() { EcsTlrEntity ecsTlr = new EcsTlrEntity(); ecsTlr.setPrimaryRequestTenantId(TENANT_ID); - assertEquals(TENANT_ID, tenantService.getBorrowingTenant(ecsTlr)); + assertEquals(TENANT_ID, tenantService.getPrimaryRequestTenantId(ecsTlr)); } @ParameterizedTest @@ -50,7 +50,7 @@ void getLendingTenants(List expectedTenantIds, SearchInstance instance) .thenReturn(new SearchInstancesResponse().instances(singletonList(instance))); EcsTlrEntity ecsTlr = new EcsTlrEntity(); ecsTlr.setInstanceId(INSTANCE_ID); - assertEquals(expectedTenantIds, tenantService.getLendingTenants(ecsTlr)); + assertEquals(expectedTenantIds, tenantService.getSecondaryRequestTenants(ecsTlr)); } private static Stream parametersForGetLendingTenants() { diff --git a/src/test/java/org/folio/service/UserCloningServiceTest.java b/src/test/java/org/folio/service/UserCloningServiceTest.java new file mode 100644 index 00000000..82d30e47 --- /dev/null +++ b/src/test/java/org/folio/service/UserCloningServiceTest.java @@ -0,0 +1,57 @@ +package org.folio.service; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.util.Map; +import java.util.UUID; + +import org.folio.domain.dto.User; +import org.folio.domain.dto.UserPersonal; +import org.folio.service.impl.UserCloningServiceImpl; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; +import org.mockito.Spy; +import org.mockito.junit.jupiter.MockitoExtension; + +import feign.FeignException; +import feign.Request; + +@ExtendWith(MockitoExtension.class) +class UserCloningServiceTest { + + @Spy + private UserService userService; + + private CloningService userCloningService; + + @Captor + ArgumentCaptor userCaptor; + + @Test + void securePatronNameShouldBeCopied() { + userCloningService = new UserCloningServiceImpl(userService); + + doThrow(new FeignException.NotFound(null, Request.create(Request.HttpMethod.GET, "", Map.of(), + Request.Body.create(""), null), null, null)) + .when(userService) + .find(any(String.class)); + when(userService.create(any(User.class))).thenReturn(null); + + userCloningService.clone(new User() + .id(UUID.randomUUID().toString()) + .personal(new UserPersonal() + .firstName("Secure") + .lastName("Patron"))); + + verify(userService).create(userCaptor.capture()); + + assertEquals("Secure", userCaptor.getValue().getPersonal().getFirstName()); + assertEquals("Patron", userCaptor.getValue().getPersonal().getLastName()); + } +} diff --git a/src/test/java/org/folio/service/impl/PickSlipsServiceTest.java b/src/test/java/org/folio/service/impl/PickSlipsServiceTest.java deleted file mode 100644 index 7ff26af2..00000000 --- a/src/test/java/org/folio/service/impl/PickSlipsServiceTest.java +++ /dev/null @@ -1,426 +0,0 @@ -package org.folio.service.impl; - -import static java.util.Collections.emptyList; -import static java.util.UUID.randomUUID; -import static java.util.stream.Collectors.toSet; -import static org.folio.domain.dto.ItemStatus.NameEnum.PAGED; -import static org.folio.domain.dto.Request.RequestTypeEnum.PAGE; -import static org.folio.support.CqlQuery.exactMatch; -import static org.folio.support.CqlQuery.exactMatchAny; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.empty; -import static org.hamcrest.Matchers.hasSize; -import static org.hamcrest.Matchers.is; -import static org.hamcrest.Matchers.notNullValue; -import static org.hamcrest.Matchers.oneOf; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.verifyNoInteractions; -import static org.mockito.Mockito.when; - -import java.util.Collection; -import java.util.Date; -import java.util.List; -import java.util.Set; -import java.util.UUID; -import java.util.concurrent.Callable; -import java.util.stream.Stream; - -import org.folio.domain.dto.AddressType; -import org.folio.domain.dto.Campus; -import org.folio.domain.dto.Contributor; -import org.folio.domain.dto.Department; -import org.folio.domain.dto.Institution; -import org.folio.domain.dto.Item; -import org.folio.domain.dto.ItemEffectiveCallNumberComponents; -import org.folio.domain.dto.ItemStatus; -import org.folio.domain.dto.Library; -import org.folio.domain.dto.LoanType; -import org.folio.domain.dto.Location; -import org.folio.domain.dto.MaterialType; -import org.folio.domain.dto.Request; -import org.folio.domain.dto.SearchHolding; -import org.folio.domain.dto.SearchInstance; -import org.folio.domain.dto.SearchItem; -import org.folio.domain.dto.ServicePoint; -import org.folio.domain.dto.StaffSlip; -import org.folio.domain.dto.StaffSlipItem; -import org.folio.domain.dto.StaffSlipRequest; -import org.folio.domain.dto.StaffSlipRequester; -import org.folio.domain.dto.Tenant; -import org.folio.domain.dto.User; -import org.folio.domain.dto.UserGroup; -import org.folio.domain.dto.UserPersonal; -import org.folio.domain.dto.UserPersonalAddressesInner; -import org.folio.service.AddressTypeService; -import org.folio.service.ConsortiaService; -import org.folio.service.DepartmentService; -import org.folio.service.InventoryService; -import org.folio.service.LocationService; -import org.folio.service.RequestService; -import org.folio.service.SearchService; -import org.folio.service.ServicePointService; -import org.folio.service.UserGroupService; -import org.folio.service.UserService; -import org.folio.spring.service.SystemUserScopedExecutionService; -import org.folio.support.CqlQuery; -import org.junit.jupiter.api.BeforeEach; -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 org.mockito.junit.jupiter.MockitoSettings; -import org.mockito.quality.Strictness; - -@ExtendWith(MockitoExtension.class) -@MockitoSettings(strictness = Strictness.LENIENT) -class PickSlipsServiceTest { - - private static final String SERVICE_POINT_ID = "6582fb37-9748-40a0-a0be-51efd151fa53"; - - @Mock - private LocationService locationService; - @Mock - private InventoryService inventoryService; - @Mock - private RequestService requestService; - @Mock - private ConsortiaService consortiaService; - @Mock - private SystemUserScopedExecutionService executionService; - @Mock - private UserService userService; - @Mock - private UserGroupService userGroupService; - @Mock - private DepartmentService departmentService; - @Mock - private AddressTypeService addressTypeService; - @Mock - private SearchService searchService; - @Mock - private ServicePointService servicePointService; - - @InjectMocks - private PickSlipsService pickSlipsService; - - @BeforeEach - public void setup() { - // Bypass the use of system user and return the result of Callable immediately - when(executionService.executeSystemUserScoped(any(), any())) - .thenAnswer(invocation -> invocation.getArgument(1, Callable.class).call()); - } - - @Test - void pickSlipsAreBuiltSuccessfully() { - Location mockLocation = new Location() - .id(randomId()) - .name("Test location") - .discoveryDisplayName("Location display name") - .libraryId(randomId()) - .campusId(randomId()) - .institutionId(randomId()) - .primaryServicePoint(randomUUID()); - - SearchItem mockSearchItem = new SearchItem() - .id(randomId()) - .effectiveLocationId(mockLocation.getId()) - .tenantId("consortium"); - - SearchHolding mockSearchHolding = new SearchHolding() - .id(randomId()) - .tenantId("consortium"); - - SearchInstance mockSearchInstance = new SearchInstance() - .id(randomId()) - .title("Test title") - .items(List.of(mockSearchItem)) - .holdings(List.of(mockSearchHolding)) - .contributors(List.of( - new Contributor().name("First, Author").primary(true), - new Contributor().name("Second, Author").primary(false) - )); - - Item mockItem = new Item() - .id(mockSearchItem.getId()) - .holdingsRecordId(mockSearchHolding.getId()) - .barcode("item_barcode") - .status(new ItemStatus().name(PAGED)) - .materialTypeId(randomId()) - .permanentLoanTypeId(randomId()) - .enumeration("enum") - .volume("vol") - .chronology("chrono") - .yearCaption(Set.of("2000", "2001")) - .copyNumber("copy") - .numberOfPieces("1") - .displaySummary("summary") - .descriptionOfPieces("description") - .effectiveLocationId(mockLocation.getId()) - .effectiveCallNumberComponents(new ItemEffectiveCallNumberComponents() - .callNumber("CN") - .prefix("PFX") - .suffix("SFX")); - - MaterialType mockMaterialType = new MaterialType() - .id(mockItem.getMaterialTypeId()) - .name("Material type"); - - LoanType mockLoanType = new LoanType() - .id(mockItem.getPermanentLoanTypeId()) - .name("Loan type"); - - Library mockLibrary = new Library() - .id(mockLocation.getLibraryId()) - .name("Library"); - - Campus mockCampus = new Campus() - .id(mockLocation.getCampusId()) - .name("Campus"); - - Institution mockInstitution = new Institution() - .id(mockLocation.getInstitutionId()) - .name("Institution"); - - ServicePoint mockPrimaryServicePoint = new ServicePoint() - .id(mockLocation.getPrimaryServicePoint().toString()) - .name("Primary service point"); - ServicePoint mockPickupServicePoint = new ServicePoint() - .id(randomId()) - .name("Pickup service point"); - - AddressType mockPrimaryAddressType = new AddressType() - .id(randomId()) - .addressType("Primary address type"); - AddressType mockDeliveryAddressType = new AddressType() - .id(randomId()) - .addressType("Delivery address type"); - - Request mockRequest = new Request() - .id(randomId()) - .itemId(mockItem.getId()) - .requestLevel(Request.RequestLevelEnum.ITEM) - .requestType(PAGE) - .pickupServicePointId(mockPickupServicePoint.getId()) - .requesterId(randomId()) - .requestDate(new Date()) - .requestExpirationDate(new Date()) - .holdShelfExpirationDate(new Date()) - .deliveryAddressTypeId(mockDeliveryAddressType.getId()) - .patronComments("comment"); - - Collection mockDepartments = List.of( - new Department().id(randomId()).name("First department"), - new Department().id(randomId()).name("Second department")); - Set mockDepartmentIds = mockDepartments.stream() - .map(Department::getId) - .collect(toSet()); - - UserGroup mockUserGroup = new UserGroup() - .id(randomId()) - .group("User group"); - - User mockRequester = new User() - .id(mockRequest.getRequesterId()) - .barcode("Requester barcode") - .patronGroup(mockUserGroup.getId()) - .departments(mockDepartmentIds) - .personal(new UserPersonal() - .firstName("First name") - .middleName("Middle name") - .lastName("Last name") - .preferredFirstName("Preferred first name") - .addresses(List.of( - new UserPersonalAddressesInner() - .id(randomId()) - .primaryAddress(true) - .addressTypeId(mockPrimaryAddressType.getId()) - .addressLine1("Primary address line 1") - .addressLine2("Primary address line 2") - .city("Primary address city") - .region("Primary address region") - .postalCode("Primary address zip code") - .countryId("US"), - new UserPersonalAddressesInner() - .id(randomId()) - .primaryAddress(false) - .addressTypeId(mockRequest.getDeliveryAddressTypeId()) - .addressLine1("Delivery address line 1") - .addressLine2("Delivery address line 2") - .city("Delivery address city") - .region("Delivery address region") - .postalCode("Delivery address zip code") - .countryId("US") - ))); - - Set departmentIds = mockDepartments.stream() - .map(Department::getId) - .collect(toSet()); - - Set addressTypeIds = Stream.of(mockPrimaryAddressType, mockDeliveryAddressType) - .map(AddressType::getId) - .collect(toSet()); - - CqlQuery searchInstancesCommonQuery = CqlQuery.exactMatchAny("item.status.name", List.of("Paged")); - CqlQuery requestCommonQuery = exactMatchAny("requestType", List.of("Page")) - .and(exactMatchAny("status", List.of("Open - Not yet filled"))); - - when(consortiaService.getAllConsortiumTenants()) - .thenReturn(List.of(new Tenant().id("consortium"))); - when(locationService.findLocations(exactMatch("primaryServicePoint", SERVICE_POINT_ID))) - .thenReturn(List.of(mockLocation)); - when(searchService.searchInstances(searchInstancesCommonQuery, "item.effectiveLocationId", - Set.of(mockLocation.getId()))) - .thenReturn(List.of(mockSearchInstance)); - when(requestService.getRequestsFromStorage(requestCommonQuery, "itemId", Set.of(mockItem.getId()))) - .thenReturn(List.of(mockRequest)); - when(inventoryService.findItems(Set.of(mockItem.getId()))) - .thenReturn(List.of(mockItem)); - when(inventoryService.findMaterialTypes(Set.of(mockMaterialType.getId()))) - .thenReturn(List.of(mockMaterialType)); - when(inventoryService.findLoanTypes(Set.of(mockLoanType.getId()))) - .thenReturn(List.of(mockLoanType)); - when(inventoryService.findLibraries(Set.of(mockLibrary.getId()))) - .thenReturn(List.of(mockLibrary)); - when(inventoryService.findCampuses(Set.of(mockCampus.getId()))) - .thenReturn(List.of(mockCampus)); - when(inventoryService.findInstitutions(Set.of(mockInstitution.getId()))) - .thenReturn(List.of(mockInstitution)); - when(servicePointService.find(Set.of(mockPrimaryServicePoint.getId()))) - .thenReturn(List.of(mockPrimaryServicePoint)); - when(servicePointService.find(Set.of(mockPickupServicePoint.getId()))) - .thenReturn(List.of(mockPickupServicePoint)); - when(userService.find(Set.of(mockRequester.getId()))) - .thenReturn(List.of(mockRequester)); - when(userGroupService.find(Set.of(mockUserGroup.getId()))) - .thenReturn(List.of(mockUserGroup)); - when(departmentService.findDepartments(departmentIds)) - .thenReturn(mockDepartments); - when(addressTypeService.findAddressTypes(addressTypeIds)) - .thenReturn(List.of(mockPrimaryAddressType, mockDeliveryAddressType)); - - Collection staffSlips = pickSlipsService.getStaffSlips(SERVICE_POINT_ID); - assertThat(staffSlips, hasSize(1)); - - StaffSlip actualPickSlip = staffSlips.iterator().next(); - assertThat(actualPickSlip.getCurrentDateTime(), notNullValue()); - - StaffSlipItem pickSlipItem = actualPickSlip.getItem(); - assertThat(pickSlipItem.getBarcode(), is("item_barcode")); - assertThat(pickSlipItem.getStatus(), is("Paged")); - assertThat(pickSlipItem.getMaterialType(), is("Material type")); - assertThat(pickSlipItem.getLoanType(), is("Loan type")); - assertThat(pickSlipItem.getEnumeration(), is("enum")); - assertThat(pickSlipItem.getVolume(), is("vol")); - assertThat(pickSlipItem.getChronology(), is("chrono")); - assertThat(pickSlipItem.getYearCaption(), oneOf("2000; 2001", "2001; 2000")); - assertThat(pickSlipItem.getCopy(), is("copy")); - assertThat(pickSlipItem.getNumberOfPieces(), is("1")); - assertThat(pickSlipItem.getDisplaySummary(), is("summary")); - assertThat(pickSlipItem.getDescriptionOfPieces(), is("description")); - assertThat(pickSlipItem.getTitle(), is("Test title")); - assertThat(pickSlipItem.getPrimaryContributor(), is("First, Author")); - assertThat(pickSlipItem.getAllContributors(), is("First, Author; Second, Author")); - assertThat(pickSlipItem.getEffectiveLocationSpecific(), is("Test location")); - assertThat(pickSlipItem.getEffectiveLocationLibrary(), is("Library")); - assertThat(pickSlipItem.getEffectiveLocationCampus(), is("Campus")); - assertThat(pickSlipItem.getEffectiveLocationInstitution(), is("Institution")); - assertThat(pickSlipItem.getEffectiveLocationPrimaryServicePointName(), is("Primary service point")); - assertThat(pickSlipItem.getEffectiveLocationDiscoveryDisplayName(), is("Location display name")); - assertThat(pickSlipItem.getCallNumber(), is("CN")); - assertThat(pickSlipItem.getCallNumberPrefix(), is("PFX")); - assertThat(pickSlipItem.getCallNumberSuffix(), is("SFX")); - - StaffSlipRequest pickSlipRequest = actualPickSlip.getRequest(); - assertThat(pickSlipRequest.getRequestId(), is(UUID.fromString(mockRequest.getId()))); - assertThat(pickSlipRequest.getServicePointPickup(), is("Pickup service point")); - assertThat(pickSlipRequest.getRequestDate(), is(mockRequest.getRequestDate())); - assertThat(pickSlipRequest.getRequestExpirationDate(), is(mockRequest.getRequestExpirationDate())); - assertThat(pickSlipRequest.getHoldShelfExpirationDate(), is(mockRequest.getHoldShelfExpirationDate())); - assertThat(pickSlipRequest.getDeliveryAddressType(), is("Delivery address type")); - assertThat(pickSlipRequest.getPatronComments(), is("comment")); - - StaffSlipRequester pickSlipRequester = actualPickSlip.getRequester(); - assertThat(pickSlipRequester.getBarcode(), is("Requester barcode")); - assertThat(pickSlipRequester.getPatronGroup(), is("User group")); - assertThat(pickSlipRequester.getDepartments(), - oneOf("First department; Second department", "Second department; First department")); - assertThat(pickSlipRequester.getFirstName(), is("First name")); - assertThat(pickSlipRequester.getMiddleName(), is("Middle name")); - assertThat(pickSlipRequester.getLastName(), is("Last name")); - assertThat(pickSlipRequester.getPreferredFirstName(), is("Preferred first name")); - assertThat(pickSlipRequester.getAddressLine1(), is("Delivery address line 1")); - assertThat(pickSlipRequester.getAddressLine2(), is("Delivery address line 2")); - assertThat(pickSlipRequester.getCity(), is("Delivery address city")); - assertThat(pickSlipRequester.getRegion(), is("Delivery address region")); - assertThat(pickSlipRequester.getPostalCode(), is("Delivery address zip code")); - assertThat(pickSlipRequester.getCountryId(), is("US")); - assertThat(pickSlipRequester.getAddressType(), is("Delivery address type")); - assertThat(pickSlipRequester.getPrimaryAddressLine1(), is("Primary address line 1")); - assertThat(pickSlipRequester.getPrimaryAddressLine2(), is("Primary address line 2")); - assertThat(pickSlipRequester.getPrimaryCity(), is("Primary address city")); - assertThat(pickSlipRequester.getPrimaryStateProvRegion(), is("Primary address region")); - assertThat(pickSlipRequester.getPrimaryZipPostalCode(), is("Primary address zip code")); - assertThat(pickSlipRequester.getPrimaryCountry(), is("United States")); - assertThat(pickSlipRequester.getPrimaryDeliveryAddressType(), is("Primary address type")); - } - @Test - void noConsortiumTenantsAreFound() { - when(consortiaService.getAllConsortiumTenants()) - .thenReturn(emptyList()); - - Collection staffSlips = pickSlipsService.getStaffSlips(SERVICE_POINT_ID); - - assertThat(staffSlips, empty()); - verifyNoInteractions(locationService, inventoryService, requestService, executionService); - } - - @Test - void noLocationsAreFound() { - when(consortiaService.getAllConsortiumTenants()) - .thenReturn(List.of(new Tenant().id("test_tenant"))); - when(locationService.findLocations(any(CqlQuery.class))) - .thenReturn(emptyList()); - - Collection staffSlips = pickSlipsService.getStaffSlips(SERVICE_POINT_ID); - - assertThat(staffSlips, empty()); - verifyNoInteractions(inventoryService, requestService); - } - - @Test - void noItemsAreFound() { - when(consortiaService.getAllConsortiumTenants()) - .thenReturn(List.of(new Tenant().id("test_tenant"))); - when(locationService.findLocations(any(CqlQuery.class))) - .thenReturn(List.of(new Location().id(randomId()))); - when(inventoryService.findItems(any(), any(), any())) - .thenReturn(emptyList()); - - Collection staffSlips = pickSlipsService.getStaffSlips(SERVICE_POINT_ID); - - assertThat(staffSlips, empty()); - verifyNoInteractions(requestService); - } - - @Test - void noRequestsAreFound() { - when(consortiaService.getAllConsortiumTenants()) - .thenReturn(List.of(new Tenant().id("test_tenant"))); - when(locationService.findLocations(any(CqlQuery.class))) - .thenReturn(List.of(new Location())); - when(inventoryService.findItems(any(), any(), any())) - .thenReturn(List.of(new Item())); - when(requestService.getRequestsFromStorage(any(), any(), any())) - .thenReturn(emptyList()); - - Collection staffSlips = pickSlipsService.getStaffSlips(SERVICE_POINT_ID); - - assertThat(staffSlips, empty()); - } - - private static String randomId() { - return randomUUID().toString(); - } - -} \ No newline at end of file