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-1933: Implementation for Search Slips API #1391

Merged
merged 8 commits into from
Dec 26, 2023
19 changes: 18 additions & 1 deletion descriptors/ModuleDescriptor-template.json
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@
"circulation.search-slips.get"
],
"modulePermissions": [
"modperms.circulation.search-slips.get"
]
}
]
Expand Down Expand Up @@ -1557,7 +1558,8 @@
"circulation.requests.hold-shelf-clearance-report.get",
"circulation.requests.allowed-service-points.get",
"circulation.inventory.items-in-transit-report.get",
"circulation.pick-slips.get"
"circulation.pick-slips.get",
"circulation.search-slips.get"
]
},
{
Expand Down Expand Up @@ -2321,6 +2323,21 @@
],
"visible": false
},
{
"permissionName": "modperms.circulation.search-slips.get",
"displayName": "module permissions for one op",
"description": "to reduce X-Okapi-Token size",
"subPermissions": [
"circulation.internal.fetch-items",
"circulation-storage.requests.item.get",
"circulation-storage.requests.collection.get",
"users.item.get",
"users.collection.get",
"addresstypes.item.get",
"addresstypes.collection.get"
],
"visible": false
},
{
"permissionName": "circulation.internal.fetch-items",
"displayName" : "Fetch item(s)",
Expand Down
8 changes: 4 additions & 4 deletions ramls/staff-slips.raml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
#%RAML 1.0
title: Stuff Slips
title: Staff Slips
version: v0.3
protocols: [ HTTP, HTTPS ]
baseUri: http://localhost:9130
Expand All @@ -9,7 +9,7 @@ documentation:
content: <b>API for staff slips generation</b>

types:
stuff-slips: !include staff-slips-response.json
staff-slips: !include staff-slips-response.json

traits:
language: !include raml-util/traits/language.raml
Expand All @@ -23,10 +23,10 @@ resourceTypes:
type:
collection-get:
exampleCollection: !include examples/staff-slips-response.json
schemaCollection: stuff-slips
schemaCollection: staff-slips
/search-slips:
/{servicePointId}:
type:
collection-get:
exampleCollection: !include examples/staff-slips-response.json
schemaCollection: stuff-slips
schemaCollection: staff-slips
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
import java.util.Arrays;
import java.util.EnumMap;
import java.util.List;
import java.util.Map;

public class RequestTypeItemStatusWhiteList {
private static EnumMap<ItemStatus, Boolean> recallRules;
Expand Down Expand Up @@ -161,4 +162,13 @@ public static List<RequestType> getRequestTypesAllowedForItemStatus(ItemStatus i
.filter(requestType -> requestsRulesMap.get(requestType).get(itemStatus))
.toList();
}

public static List<ItemStatus> getItemStatusesAllowedForRequestType(RequestType requestType) {
return requestsRulesMap.get(requestType)
.entrySet()
.stream()
.filter(entry -> Boolean.TRUE.equals(entry.getValue()))
.map(Map.Entry::getKey)
.toList();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,6 @@ public class TemplateContextUtil {
private static final String FEE_ACTION = "feeAction";
private static final String UNLIMITED = "unlimited";
public static final String CURRENT_DATE_TIME = "currentDateTime";
private static final String PICK_SLIPS_KEY = "pickSlips";
private static final String ADDITIONAL_INFO_KEY = "additionalInfo";

private TemplateContextUtil() {
Expand Down Expand Up @@ -120,25 +119,30 @@ public static JsonObject createStaffSlipContext(Request request) {
return createStaffSlipContext(request.getItem(), request);
}

public static JsonObject addPrimaryServicePointNameToStaffSlipContext(JsonObject entries, ServicePoint primaryServicePoint) {
log.debug("addPrimaryServicePointNameToStaffSlipContext:: parameters entries: {} primaryServicePoint: {}", entries, primaryServicePoint);
public static JsonObject addPrimaryServicePointNameToStaffSlipContext(JsonObject entries,
ServicePoint primaryServicePoint, String slipsCollectionName) {

log.debug("addPrimaryServicePointNameToStaffSlipContext:: parameters entries: {}, " +
"primaryServicePoint: {}, slipsCollectionName: {}", entries, primaryServicePoint, slipsCollectionName);
if (primaryServicePoint == null) {
log.info("addPrimaryServicePointNameToStaffSlipContext:: primaryServicePoint object is null");
return entries;
}

if (entries == null) {
log.info("addPrimaryServicePointNameToStaffSlipContext:: entries JsonObject is null, primaryServicePointName: {}", primaryServicePoint.getName());
log.info("addPrimaryServicePointNameToStaffSlipContext:: entries JsonObject is null, " +
"primaryServicePointName: {}", primaryServicePoint.getName());
return new JsonObject();
}

entries.getJsonArray(PICK_SLIPS_KEY)
entries.getJsonArray(slipsCollectionName)
.stream()
.map(JsonObject.class::cast)
.map(pickSlip -> pickSlip.getJsonObject(ITEM))
.forEach(item -> item.put("effectiveLocationPrimaryServicePointName", primaryServicePoint.getName()));

log.info("addPrimaryServicePointNameToStaffSlipContext:: Result entries: {}, primaryServicePointName: {}", entries, primaryServicePoint.getName());
log.debug("addPrimaryServicePointNameToStaffSlipContext:: Result entries: {}, " +
"primaryServicePointName: {}", () -> entries, primaryServicePoint::getName);

return entries;
}
Expand Down
135 changes: 3 additions & 132 deletions src/main/java/org/folio/circulation/resources/PickSlipsResource.java
Original file line number Diff line number Diff line change
@@ -1,142 +1,13 @@
package org.folio.circulation.resources;

import static java.util.Collections.emptyList;
import static java.util.concurrent.CompletableFuture.completedFuture;
import static java.util.stream.Collectors.toSet;
import static org.folio.circulation.support.fetching.MultipleCqlIndexValuesCriteria.byIndex;
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.multipleRecordsAsString;

import java.lang.invoke.MethodHandles;
import java.util.Collection;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;

import org.apache.commons.lang3.StringUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.folio.circulation.domain.Item;
import org.folio.circulation.domain.ItemStatus;
import org.folio.circulation.domain.Location;
import org.folio.circulation.domain.MultipleRecords;
import org.folio.circulation.domain.Request;
import org.folio.circulation.domain.RequestStatus;
import org.folio.circulation.domain.RequestType;
import org.folio.circulation.domain.notice.TemplateContextUtil;
import org.folio.circulation.infrastructure.storage.ServicePointRepository;
import org.folio.circulation.infrastructure.storage.inventory.ItemRepository;
import org.folio.circulation.infrastructure.storage.inventory.LocationRepository;
import org.folio.circulation.infrastructure.storage.users.AddressTypeRepository;
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.support.Clients;
import org.folio.circulation.support.RouteRegistration;
import org.folio.circulation.support.http.client.CqlQuery;
import org.folio.circulation.support.http.server.JsonHttpResponse;
import org.folio.circulation.support.http.server.WebContext;
import org.folio.circulation.support.results.Result;
import static org.folio.circulation.domain.ItemStatus.PAGED;
import static org.folio.circulation.domain.RequestType.PAGE;

import io.vertx.core.http.HttpClient;
import io.vertx.ext.web.Router;
import io.vertx.ext.web.RoutingContext;

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;


public PickSlipsResource(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);
final Clients clients = Clients.create(context, client);

final var userRepository = new UserRepository(clients);
final var itemRepository = new ItemRepository(clients);
final AddressTypeRepository addressTypeRepository = new AddressTypeRepository(clients);
final ServicePointRepository servicePointRepository = new ServicePointRepository(clients);
final PatronGroupRepository patronGroupRepository = new PatronGroupRepository(clients);
final DepartmentRepository departmentRepository = new DepartmentRepository(clients);
final UUID servicePointId = UUID.fromString(
routingContext.request().getParam(SERVICE_POINT_ID_PARAM));

fetchLocationsForServicePoint(servicePointId, clients)
.thenComposeAsync(r -> r.after(locations -> fetchPagedItemsForLocations(locations,
itemRepository, LocationRepository.using(clients, servicePointRepository))))
.thenComposeAsync(r -> r.after(items -> fetchOpenPageRequestsForItems(items, clients)))
.thenComposeAsync(r -> r.after(userRepository::findUsersForRequests))
.thenComposeAsync(result -> result.after(patronGroupRepository::findPatronGroupsForRequestsUsers))
.thenComposeAsync(r -> r.after(departmentRepository::findDepartmentsForRequestUsers))
.thenComposeAsync(r -> r.after(addressTypeRepository::findAddressTypesForRequests))
.thenComposeAsync(r -> r.after(servicePointRepository::findServicePointsForRequests))
.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<Collection<Item>>> fetchPagedItemsForLocations(
MultipleRecords<Location> multipleLocations,
ItemRepository itemRepository, LocationRepository locationRepository) {

log.debug("fetchPagedItemsForLocations:: parameters multipleLocations: {}",
() -> multipleRecordsAsString(multipleLocations));
Collection<Location> locations = multipleLocations.getRecords();

Set<String> locationIds = locations.stream()
.map(Location::getId)
.filter(StringUtils::isNoneBlank)
.collect(toSet());

if (locationIds.isEmpty()) {
log.info("fetchPagedItemsForLocations:: locationIds is empty");

return completedFuture(succeeded(emptyList()));
}

Result<CqlQuery> statusQuery = exactMatch(STATUS_NAME_KEY, ItemStatus.PAGED.getValue());

return itemRepository.findByIndexNameAndQuery(locationIds, EFFECTIVE_LOCATION_ID_KEY, statusQuery)
.thenComposeAsync(r -> r.after(items -> fetchLocationDetailsForItems(items, locations,
locationRepository)));
}

private CompletableFuture<Result<MultipleRecords<Request>>> fetchOpenPageRequestsForItems(
Collection<Item> items, Clients clients) {

Set<String> itemIds = items.stream()
.map(Item::getItemId)
.filter(StringUtils::isNoneBlank)
.collect(toSet());

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

return completedFuture(succeeded(MultipleRecords.empty()));
}

final Result<CqlQuery> typeQuery = exactMatch(REQUEST_TYPE_KEY, RequestType.PAGE.getValue());
final Result<CqlQuery> statusQuery = exactMatch(STATUS_KEY, RequestStatus.OPEN_NOT_YET_FILLED.getValue());
final Result<CqlQuery> statusAndTypeQuery = typeQuery.combine(statusQuery, CqlQuery::and);

return findWithMultipleCqlIndexValues(clients.requestsStorage(), REQUESTS_KEY, Request::from)
.find(byIndex(ITEM_ID_KEY, itemIds).withQuery(statusAndTypeQuery))
.thenApply(flatMapResult(requests -> matchItemsToRequests(requests, items)));
super(rootPath, client, "pickSlips", PAGE, PAGED);
}
}
Original file line number Diff line number Diff line change
@@ -1,46 +1,13 @@
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 static org.folio.circulation.domain.RequestType.HOLD;
import static org.folio.circulation.domain.RequestTypeItemStatusWhiteList.getItemStatusesAllowedForRequestType;

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<Result<MultipleRecords<Request>>> fetchHoldRequests() {
return ofAsync(MultipleRecords.empty());
super(rootPath, client, "searchSlips", HOLD, getItemStatusesAllowedForRequestType(HOLD));
}
}
Loading