diff --git a/descriptors/ModuleDescriptor-template.json b/descriptors/ModuleDescriptor-template.json
index fc326f66d3..761ecabaa6 100644
--- a/descriptors/ModuleDescriptor-template.json
+++ b/descriptors/ModuleDescriptor-template.json
@@ -56,6 +56,23 @@
}
]
},
+ {
+ "id": "search-slips",
+ "version": "0.1",
+ "handlers": [
+ {
+ "methods": [
+ "GET"
+ ],
+ "pathPattern": "/circulation/search-slips/{servicePointId}",
+ "permissionsRequired": [
+ "circulation.search-slips.get"
+ ],
+ "modulePermissions": [
+ ]
+ }
+ ]
+ },
{
"id": "request-move",
"version": "0.7",
diff --git a/ramls/examples/pick-slips-response.json b/ramls/examples/staff-slips-response.json
similarity index 100%
rename from ramls/examples/pick-slips-response.json
rename to ramls/examples/staff-slips-response.json
diff --git a/ramls/pick-slips.raml b/ramls/pick-slips.raml
deleted file mode 100644
index 53d968acd1..0000000000
--- a/ramls/pick-slips.raml
+++ /dev/null
@@ -1,26 +0,0 @@
-#%RAML 1.0
-title: Pick Slips
-version: v0.3
-protocols: [ HTTP, HTTPS ]
-baseUri: http://localhost:9130
-
-documentation:
- - title: API for fetching current pick slips
- content: API for pick slips generation
-
-types:
- pick-slips: !include pick-slips-response.json
-
-traits:
- language: !include raml-util/traits/language.raml
-
-resourceTypes:
- collection-get: !include raml-util/rtypes/collection-get.raml
-
-/circulation:
- /pick-slips:
- /{servicePointId}:
- type:
- collection-get:
- exampleCollection: !include examples/pick-slips-response.json
- schemaCollection: pick-slips
diff --git a/ramls/pick-slips-response.json b/ramls/staff-slips-response.json
similarity index 100%
rename from ramls/pick-slips-response.json
rename to ramls/staff-slips-response.json
diff --git a/ramls/staff-slips.raml b/ramls/staff-slips.raml
new file mode 100644
index 0000000000..31b5c699f3
--- /dev/null
+++ b/ramls/staff-slips.raml
@@ -0,0 +1,32 @@
+#%RAML 1.0
+title: Stuff Slips
+version: v0.3
+protocols: [ HTTP, HTTPS ]
+baseUri: http://localhost:9130
+
+documentation:
+ - title: API for fetching current staff slips
+ content: API for staff slips generation
+
+types:
+ stuff-slips: !include staff-slips-response.json
+
+traits:
+ language: !include raml-util/traits/language.raml
+
+resourceTypes:
+ collection-get: !include raml-util/rtypes/collection-get.raml
+
+/circulation:
+ /pick-slips:
+ /{servicePointId}:
+ type:
+ collection-get:
+ exampleCollection: !include examples/staff-slips-response.json
+ schemaCollection: stuff-slips
+ /search-slips:
+ /{servicePointId}:
+ type:
+ collection-get:
+ exampleCollection: !include examples/staff-slips-response.json
+ schemaCollection: stuff-slips
diff --git a/src/main/java/org/folio/circulation/CirculationVerticle.java b/src/main/java/org/folio/circulation/CirculationVerticle.java
index fe3882dbeb..0c3a88cc75 100644
--- a/src/main/java/org/folio/circulation/CirculationVerticle.java
+++ b/src/main/java/org/folio/circulation/CirculationVerticle.java
@@ -38,6 +38,7 @@
import org.folio.circulation.resources.RequestQueueResource;
import org.folio.circulation.resources.RequestScheduledNoticeProcessingResource;
import org.folio.circulation.resources.ScheduledAnonymizationProcessingResource;
+import org.folio.circulation.resources.SearchSlipsResource;
import org.folio.circulation.resources.TenantActivationResource;
import org.folio.circulation.resources.agedtolost.ScheduledAgeToLostFeeChargingResource;
import org.folio.circulation.resources.agedtolost.ScheduledAgeToLostResource;
@@ -100,6 +101,8 @@ public void start(Promise startFuture) {
.register(router);
new PickSlipsResource("/circulation/pick-slips/:servicePointId", client)
.register(router);
+ new SearchSlipsResource("/circulation/search-slips/:servicePointId", client)
+ .register(router);
new CirculationRulesResource("/circulation/rules", client)
.register(router);
diff --git a/src/main/java/org/folio/circulation/resources/PickSlipsResource.java b/src/main/java/org/folio/circulation/resources/PickSlipsResource.java
index 8c2cbafaf4..910cccd773 100644
--- a/src/main/java/org/folio/circulation/resources/PickSlipsResource.java
+++ b/src/main/java/org/folio/circulation/resources/PickSlipsResource.java
@@ -2,26 +2,19 @@
import static java.util.Collections.emptyList;
import static java.util.concurrent.CompletableFuture.completedFuture;
-import static java.util.function.Function.identity;
-import static java.util.stream.Collectors.toMap;
import static java.util.stream.Collectors.toSet;
import static org.folio.circulation.support.fetching.MultipleCqlIndexValuesCriteria.byIndex;
-import static org.folio.circulation.support.fetching.RecordFetching.findWithCqlQuery;
import static org.folio.circulation.support.fetching.RecordFetching.findWithMultipleCqlIndexValues;
import static org.folio.circulation.support.http.client.CqlQuery.exactMatch;
import static org.folio.circulation.support.results.Result.succeeded;
import static org.folio.circulation.support.results.ResultBinding.flatMapResult;
-import static org.folio.circulation.support.utils.LogUtil.collectionAsString;
import static org.folio.circulation.support.utils.LogUtil.multipleRecordsAsString;
import java.lang.invoke.MethodHandles;
import java.util.Collection;
-import java.util.List;
-import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
-import java.util.stream.Collectors;
import org.apache.commons.lang3.StringUtils;
import org.apache.logging.log4j.LogManager;
@@ -41,36 +34,20 @@
import org.folio.circulation.infrastructure.storage.users.DepartmentRepository;
import org.folio.circulation.infrastructure.storage.users.PatronGroupRepository;
import org.folio.circulation.infrastructure.storage.users.UserRepository;
-import org.folio.circulation.storage.mappers.LocationMapper;
import org.folio.circulation.support.Clients;
import org.folio.circulation.support.RouteRegistration;
import org.folio.circulation.support.http.client.CqlQuery;
-import org.folio.circulation.support.http.client.PageLimit;
import org.folio.circulation.support.http.server.JsonHttpResponse;
import org.folio.circulation.support.http.server.WebContext;
import org.folio.circulation.support.results.Result;
import io.vertx.core.http.HttpClient;
-import io.vertx.core.json.JsonObject;
import io.vertx.ext.web.Router;
import io.vertx.ext.web.RoutingContext;
-public class PickSlipsResource extends Resource {
- private static final String STATUS_KEY = "status";
- private static final String ITEM_ID_KEY = "itemId";
- private static final String REQUESTS_KEY = "requests";
- private static final String LOCATIONS_KEY = "locations";
- private static final String PICK_SLIPS_KEY = "pickSlips";
- private static final String STATUS_NAME_KEY = "status.name";
- private static final String REQUEST_TYPE_KEY = "requestType";
- private static final String TOTAL_RECORDS_KEY = "totalRecords";
- private static final String SERVICE_POINT_ID_PARAM = "servicePointId";
- private static final String EFFECTIVE_LOCATION_ID_KEY = "effectiveLocationId";
- private static final String PRIMARY_SERVICE_POINT_KEY = "primaryServicePoint";
-
- private static final PageLimit LOCATIONS_LIMIT = PageLimit.oneThousand();
+public class PickSlipsResource extends SlipsResource {
private static final Logger log = LogManager.getLogger(MethodHandles.lookup().lookupClass());
-
+ private static final String PICK_SLIPS_KEY = "pickSlips";
private final String rootPath;
@@ -85,8 +62,7 @@ public void register(Router router) {
routeRegistration.getMany(this::getMany);
}
-
- private void getMany(RoutingContext routingContext) {
+ protected void getMany(RoutingContext routingContext) {
final WebContext context = new WebContext(routingContext);
final Clients clients = Clients.create(context, client);
@@ -108,22 +84,13 @@ private void getMany(RoutingContext routingContext) {
.thenComposeAsync(r -> r.after(departmentRepository::findDepartmentsForRequestUsers))
.thenComposeAsync(r -> r.after(addressTypeRepository::findAddressTypesForRequests))
.thenComposeAsync(r -> r.after(servicePointRepository::findServicePointsForRequests))
- .thenApply(flatMapResult(this::mapResultToJson))
+ .thenApply(flatMapResult(requests -> mapResultToJson(requests, PICK_SLIPS_KEY)))
.thenComposeAsync(r -> r.combineAfter(() -> servicePointRepository.getServicePointById(servicePointId),
TemplateContextUtil::addPrimaryServicePointNameToStaffSlipContext))
.thenApply(r -> r.map(JsonHttpResponse::ok))
.thenAccept(context::writeResultToHttpResponse);
}
- private CompletableFuture>> fetchLocationsForServicePoint(
- UUID servicePointId, Clients clients) {
-
- log.debug("fetchLocationsForServicePoint:: parameters servicePointId: {}", servicePointId);
-
- return findWithCqlQuery(clients.locationsStorage(), LOCATIONS_KEY, new LocationMapper()::toDomain)
- .findByQuery(exactMatch(PRIMARY_SERVICE_POINT_KEY, servicePointId.toString()), LOCATIONS_LIMIT);
- }
-
private CompletableFuture>> fetchPagedItemsForLocations(
MultipleRecords multipleLocations,
ItemRepository itemRepository, LocationRepository locationRepository) {
@@ -150,48 +117,6 @@ private CompletableFuture>> fetchPagedItemsForLocations(
locationRepository)));
}
- private CompletableFuture>> fetchLocationDetailsForItems(
- MultipleRecords- items, Collection locationsForServicePoint,
- LocationRepository locationRepository) {
-
- log.debug("fetchLocationDetailsForItems:: parameters items: {}",
- () -> multipleRecordsAsString(items));
-
- Set locationIdsFromItems = items.toKeys(Item::getEffectiveLocationId);
-
- Set locationsForItems = locationsForServicePoint.stream()
- .filter(location -> locationIdsFromItems.contains(location.getId()))
- .collect(toSet());
-
- if (locationsForItems.isEmpty()) {
- log.info("fetchLocationDetailsForItems:: locationsForItems is empty");
-
- return completedFuture(succeeded(emptyList()));
- }
-
- return completedFuture(succeeded(locationsForItems))
- .thenComposeAsync(r -> r.after(locationRepository::fetchLibraries))
- .thenComposeAsync(r -> r.after(locationRepository::fetchInstitutions))
- .thenComposeAsync(r -> r.after(locationRepository::fetchCampuses))
- .thenApply(flatMapResult(locations -> matchLocationsToItems(items, locations)));
- }
-
- private Result> matchLocationsToItems(
- MultipleRecords
- items, Collection locations) {
-
- log.debug("matchLocationsToItems:: parameters items: {}, locations: {}",
- () -> multipleRecordsAsString(items), () -> collectionAsString(locations));
-
- Map locationsMap = locations.stream()
- .collect(toMap(Location::getId, identity()));
-
- return succeeded(
- items.mapRecords(item -> item.withLocation(
- locationsMap.getOrDefault(item.getEffectiveLocationId(),
- Location.unknown(item.getEffectiveLocationId()))))
- .getRecords());
- }
-
private CompletableFuture>> fetchOpenPageRequestsForItems(
Collection
- items, Clients clients) {
@@ -200,7 +125,7 @@ private CompletableFuture>> fetchOpenPageRequest
.filter(StringUtils::isNoneBlank)
.collect(toSet());
- if(itemIds.isEmpty()) {
+ if (itemIds.isEmpty()) {
log.info("fetchOpenPageRequestsForItems:: itemIds is empty");
return completedFuture(succeeded(MultipleRecords.empty()));
@@ -214,28 +139,4 @@ private CompletableFuture>> fetchOpenPageRequest
.find(byIndex(ITEM_ID_KEY, itemIds).withQuery(statusAndTypeQuery))
.thenApply(flatMapResult(requests -> matchItemsToRequests(requests, items)));
}
-
- private Result> matchItemsToRequests(
- MultipleRecords requests, Collection
- items) {
-
- Map itemMap = items.stream()
- .collect(toMap(Item::getItemId, identity()));
-
- return succeeded(
- requests.mapRecords(request ->
- request.withItem(itemMap.getOrDefault(request.getItemId(), null))
- ));
- }
-
- private Result mapResultToJson(MultipleRecords requests) {
- log.debug("mapResultToJson:: parameters requests: {}", () -> multipleRecordsAsString(requests));
- List representations = requests.getRecords().stream()
- .map(TemplateContextUtil::createStaffSlipContext)
- .collect(Collectors.toList());
- JsonObject jsonRepresentations = new JsonObject()
- .put(PICK_SLIPS_KEY, representations)
- .put(TOTAL_RECORDS_KEY, representations.size());
-
- return succeeded(jsonRepresentations);
- }
}
diff --git a/src/main/java/org/folio/circulation/resources/SearchSlipsResource.java b/src/main/java/org/folio/circulation/resources/SearchSlipsResource.java
new file mode 100644
index 0000000000..8fe58e5512
--- /dev/null
+++ b/src/main/java/org/folio/circulation/resources/SearchSlipsResource.java
@@ -0,0 +1,46 @@
+package org.folio.circulation.resources;
+
+import static org.folio.circulation.support.results.Result.ofAsync;
+import static org.folio.circulation.support.results.ResultBinding.flatMapResult;
+
+import java.util.concurrent.CompletableFuture;
+
+import org.folio.circulation.domain.MultipleRecords;
+import org.folio.circulation.domain.Request;
+import org.folio.circulation.support.RouteRegistration;
+import org.folio.circulation.support.http.server.JsonHttpResponse;
+import org.folio.circulation.support.http.server.WebContext;
+import org.folio.circulation.support.results.Result;
+
+import io.vertx.core.http.HttpClient;
+import io.vertx.ext.web.Router;
+import io.vertx.ext.web.RoutingContext;
+
+public class SearchSlipsResource extends SlipsResource {
+ private static final String SEARCH_SLIPS_KEY = "searchSlips";
+ private final String rootPath;
+
+ public SearchSlipsResource(String rootPath, HttpClient client) {
+ super(client);
+ this.rootPath = rootPath;
+ }
+
+ @Override
+ public void register(Router router) {
+ RouteRegistration routeRegistration = new RouteRegistration(rootPath, router);
+ routeRegistration.getMany(this::getMany);
+ }
+
+ protected void getMany(RoutingContext routingContext) {
+ final WebContext context = new WebContext(routingContext);
+
+ fetchHoldRequests()
+ .thenApply(flatMapResult(requests -> mapResultToJson(requests, SEARCH_SLIPS_KEY)))
+ .thenApply(r -> r.map(JsonHttpResponse::ok))
+ .thenAccept(context::writeResultToHttpResponse);
+ }
+
+ private CompletableFuture>> fetchHoldRequests() {
+ return ofAsync(MultipleRecords.empty());
+ }
+}
diff --git a/src/main/java/org/folio/circulation/resources/SlipsResource.java b/src/main/java/org/folio/circulation/resources/SlipsResource.java
new file mode 100644
index 0000000000..c8d8faead2
--- /dev/null
+++ b/src/main/java/org/folio/circulation/resources/SlipsResource.java
@@ -0,0 +1,134 @@
+package org.folio.circulation.resources;
+
+import static java.util.Collections.emptyList;
+import static java.util.concurrent.CompletableFuture.completedFuture;
+import static java.util.function.Function.identity;
+import static java.util.stream.Collectors.toMap;
+import static java.util.stream.Collectors.toSet;
+import static org.folio.circulation.support.fetching.RecordFetching.findWithCqlQuery;
+import static org.folio.circulation.support.http.client.CqlQuery.exactMatch;
+import static org.folio.circulation.support.results.Result.succeeded;
+import static org.folio.circulation.support.results.ResultBinding.flatMapResult;
+import static org.folio.circulation.support.utils.LogUtil.collectionAsString;
+import static org.folio.circulation.support.utils.LogUtil.multipleRecordsAsString;
+
+import java.lang.invoke.MethodHandles;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.UUID;
+import java.util.concurrent.CompletableFuture;
+
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.folio.circulation.domain.Item;
+import org.folio.circulation.domain.Location;
+import org.folio.circulation.domain.MultipleRecords;
+import org.folio.circulation.domain.Request;
+import org.folio.circulation.domain.notice.TemplateContextUtil;
+import org.folio.circulation.infrastructure.storage.inventory.LocationRepository;
+import org.folio.circulation.storage.mappers.LocationMapper;
+import org.folio.circulation.support.Clients;
+import org.folio.circulation.support.http.client.PageLimit;
+import org.folio.circulation.support.results.Result;
+
+import io.vertx.core.http.HttpClient;
+import io.vertx.core.json.JsonObject;
+import io.vertx.ext.web.RoutingContext;
+
+public abstract class SlipsResource extends Resource {
+ protected static final String LOCATIONS_KEY = "locations";
+ protected static final String STATUS_KEY = "status";
+ protected static final String REQUESTS_KEY = "requests";
+ protected static final String ITEM_ID_KEY = "itemId";
+ protected static final String STATUS_NAME_KEY = "status.name";
+ protected static final String REQUEST_TYPE_KEY = "requestType";
+ protected static final String TOTAL_RECORDS_KEY = "totalRecords";
+ protected static final String SERVICE_POINT_ID_PARAM = "servicePointId";
+ protected static final String EFFECTIVE_LOCATION_ID_KEY = "effectiveLocationId";
+ protected static final String PRIMARY_SERVICE_POINT_KEY = "primaryServicePoint";
+
+ protected static final PageLimit LOCATIONS_LIMIT = PageLimit.oneThousand();
+ protected static final Logger log = LogManager.getLogger(MethodHandles.lookup().lookupClass());
+
+
+ protected SlipsResource(HttpClient client) {
+ super(client);
+ }
+
+ protected abstract void getMany(RoutingContext routingContext);
+
+ protected CompletableFuture>> fetchLocationsForServicePoint(
+ UUID servicePointId, Clients clients) {
+
+ log.debug("fetchLocationsForServicePoint:: parameters servicePointId: {}", servicePointId);
+
+ return findWithCqlQuery(clients.locationsStorage(), LOCATIONS_KEY, new LocationMapper()::toDomain)
+ .findByQuery(exactMatch(PRIMARY_SERVICE_POINT_KEY, servicePointId.toString()), LOCATIONS_LIMIT);
+ }
+
+ protected CompletableFuture>> fetchLocationDetailsForItems(
+ MultipleRecords
- items, Collection locationsForServicePoint,
+ LocationRepository locationRepository) {
+
+ log.debug("fetchLocationDetailsForItems:: parameters items: {}",
+ () -> multipleRecordsAsString(items));
+
+ Set locationIdsFromItems = items.toKeys(Item::getEffectiveLocationId);
+ Set locationsForItems = locationsForServicePoint.stream()
+ .filter(location -> locationIdsFromItems.contains(location.getId()))
+ .collect(toSet());
+
+ if (locationsForItems.isEmpty()) {
+ log.info("fetchLocationDetailsForItems:: locationsForItems is empty");
+
+ return completedFuture(succeeded(emptyList()));
+ }
+
+ return completedFuture(succeeded(locationsForItems))
+ .thenComposeAsync(r -> r.after(locationRepository::fetchLibraries))
+ .thenComposeAsync(r -> r.after(locationRepository::fetchInstitutions))
+ .thenComposeAsync(r -> r.after(locationRepository::fetchCampuses))
+ .thenApply(flatMapResult(locations -> matchLocationsToItems(items, locations)));
+ }
+
+ protected Result> matchLocationsToItems(
+ MultipleRecords
- items, Collection locations) {
+
+ log.debug("matchLocationsToItems:: parameters items: {}, locations: {}",
+ () -> multipleRecordsAsString(items), () -> collectionAsString(locations));
+
+ Map locationsMap = locations.stream()
+ .collect(toMap(Location::getId, identity()));
+
+ return succeeded(items.mapRecords(item -> item.withLocation(
+ locationsMap.getOrDefault(item.getEffectiveLocationId(),
+ Location.unknown(item.getEffectiveLocationId()))))
+ .getRecords());
+ }
+
+ protected Result> matchItemsToRequests(
+ MultipleRecords requests, Collection
- items) {
+
+ Map itemMap = items.stream()
+ .collect(toMap(Item::getItemId, identity()));
+
+ return succeeded(requests.mapRecords(request -> request.withItem(
+ itemMap.getOrDefault(request.getItemId(), null))));
+ }
+
+ protected Result mapResultToJson(MultipleRecords requests,
+ String slipsKey) {
+
+ log.debug("mapResultToJson:: parameters requests: {}", () -> multipleRecordsAsString(requests));
+ List representations = requests.getRecords().stream()
+ .map(TemplateContextUtil::createStaffSlipContext)
+ .toList();
+ JsonObject jsonRepresentations = new JsonObject()
+ .put(slipsKey, representations)
+ .put(TOTAL_RECORDS_KEY, representations.size());
+
+ return succeeded(jsonRepresentations);
+ }
+}
diff --git a/src/test/java/api/requests/SearchSlipsTests.java b/src/test/java/api/requests/SearchSlipsTests.java
new file mode 100644
index 0000000000..ff57d26baf
--- /dev/null
+++ b/src/test/java/api/requests/SearchSlipsTests.java
@@ -0,0 +1,32 @@
+package api.requests;
+
+import static java.net.HttpURLConnection.HTTP_OK;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.core.Is.is;
+
+import java.util.UUID;
+
+import org.folio.circulation.support.http.client.Response;
+import org.junit.jupiter.api.Test;
+
+import api.support.APITests;
+import api.support.http.ResourceClient;
+import io.vertx.core.json.JsonObject;
+
+class SearchSlipsTests extends APITests {
+ private static final String TOTAL_RECORDS_KEY = "totalRecords";
+ private static final String SEARCH_SLIPS_KEY = "searchSlips";
+
+ @Test
+ void responseShouldHaveEmptyListOfSearchSlipsRecords() {
+ Response response = ResourceClient.forSearchSlips().getById(UUID.randomUUID());
+ assertThat(response.getStatusCode(), is(HTTP_OK));
+ assertResponseHasItems(response, 0);
+ }
+
+ private void assertResponseHasItems(Response response, int itemsCount) {
+ JsonObject responseJson = response.getJson();
+ assertThat(responseJson.getJsonArray(SEARCH_SLIPS_KEY).size(), is(itemsCount));
+ assertThat(responseJson.getInteger(TOTAL_RECORDS_KEY), is(itemsCount));
+ }
+}
diff --git a/src/test/java/api/support/http/InterfaceUrls.java b/src/test/java/api/support/http/InterfaceUrls.java
index 01bfe547fd..b4b5c0ec78 100644
--- a/src/test/java/api/support/http/InterfaceUrls.java
+++ b/src/test/java/api/support/http/InterfaceUrls.java
@@ -129,6 +129,10 @@ static URL pickSlipsUrl(String servicePointId) {
return circulationModuleUrl("/circulation/pick-slips" + servicePointId);
}
+ static URL searchSlipsUrl(String servicePointId) {
+ return circulationModuleUrl("/circulation/search-slips" + servicePointId);
+ }
+
public static URL requestQueueUrl(UUID itemId) {
return requestsUrl(String.format("/queue/item/%s", itemId));
}
diff --git a/src/test/java/api/support/http/ResourceClient.java b/src/test/java/api/support/http/ResourceClient.java
index 02f4f3740a..fc947fe16d 100644
--- a/src/test/java/api/support/http/ResourceClient.java
+++ b/src/test/java/api/support/http/ResourceClient.java
@@ -53,6 +53,10 @@ public static ResourceClient forPickSlips() {
return new ResourceClient(InterfaceUrls::pickSlipsUrl, "pickSlips");
}
+ public static ResourceClient forSearchSlips() {
+ return new ResourceClient(InterfaceUrls::searchSlipsUrl, "searchSlips");
+ }
+
public static ResourceClient forLoans() {
return new ResourceClient(InterfaceUrls::loansUrl, "loans");
}