Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[CIRC-1932] Implement search-slips endpoint skeleton #1358

Merged
merged 7 commits into from
Oct 31, 2023
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions descriptors/ModuleDescriptor-template.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
26 changes: 26 additions & 0 deletions ramls/search-slips.raml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
#%RAML 1.0
title: Pick Slips
version: v0.3
protocols: [ HTTP, HTTPS ]
baseUri: http://localhost:9130

documentation:
- title: API for fetching current search slips
content: <b>API for search slips generation</b>

types:
search-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:
/search-slips:
/{servicePointId}:
type:
collection-get:
exampleCollection: !include examples/pick-slips-response.json
schemaCollection: search-slips
3 changes: 3 additions & 0 deletions src/main/java/org/folio/circulation/CirculationVerticle.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -100,6 +101,8 @@ public void start(Promise<Void> 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);
Expand Down
109 changes: 5 additions & 104 deletions src/main/java/org/folio/circulation/resources/PickSlipsResource.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;


Expand All @@ -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);

Expand All @@ -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<Result<MultipleRecords<Location>>> 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<Result<Collection<Item>>> fetchPagedItemsForLocations(
MultipleRecords<Location> multipleLocations,
ItemRepository itemRepository, LocationRepository locationRepository) {
Expand All @@ -150,48 +117,6 @@ private CompletableFuture<Result<Collection<Item>>> fetchPagedItemsForLocations(
locationRepository)));
}

private CompletableFuture<Result<Collection<Item>>> fetchLocationDetailsForItems(
MultipleRecords<Item> items, Collection<Location> locationsForServicePoint,
LocationRepository locationRepository) {

log.debug("fetchLocationDetailsForItems:: parameters items: {}",
() -> multipleRecordsAsString(items));

Set<String> locationIdsFromItems = items.toKeys(Item::getEffectiveLocationId);

Set<Location> 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<Collection<Item>> matchLocationsToItems(
MultipleRecords<Item> items, Collection<Location> locations) {

log.debug("matchLocationsToItems:: parameters items: {}, locations: {}",
() -> multipleRecordsAsString(items), () -> collectionAsString(locations));

Map<String, Location> 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<Result<MultipleRecords<Request>>> fetchOpenPageRequestsForItems(
Collection<Item> items, Clients clients) {

Expand All @@ -200,7 +125,7 @@ private CompletableFuture<Result<MultipleRecords<Request>>> fetchOpenPageRequest
.filter(StringUtils::isNoneBlank)
.collect(toSet());

if(itemIds.isEmpty()) {
if (itemIds.isEmpty()) {
log.info("fetchOpenPageRequestsForItems:: itemIds is empty");

return completedFuture(succeeded(MultipleRecords.empty()));
Expand All @@ -214,28 +139,4 @@ private CompletableFuture<Result<MultipleRecords<Request>>> fetchOpenPageRequest
.find(byIndex(ITEM_ID_KEY, itemIds).withQuery(statusAndTypeQuery))
.thenApply(flatMapResult(requests -> matchItemsToRequests(requests, items)));
}

private Result<MultipleRecords<Request>> matchItemsToRequests(
MultipleRecords<Request> requests, Collection<Item> items) {

Map<String, Item> itemMap = items.stream()
.collect(toMap(Item::getItemId, identity()));

return succeeded(
requests.mapRecords(request ->
request.withItem(itemMap.getOrDefault(request.getItemId(), null))
));
}

private Result<JsonObject> mapResultToJson(MultipleRecords<Request> requests) {
log.debug("mapResultToJson:: parameters requests: {}", () -> multipleRecordsAsString(requests));
List<JsonObject> 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);
}
}
Original file line number Diff line number Diff line change
@@ -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";
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This collection name can be an instance variable injected via constructor

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<Result<MultipleRecords<Request>>> fetchHoldRequests() {
return ofAsync(MultipleRecords.empty());
}
}
Loading