From 69d9888a55a3e09a2cec862ef009168a6dbe52d3 Mon Sep 17 00:00:00 2001 From: Kapil Verma <85541312+kapil-epam@users.noreply.github.com> Date: Wed, 25 Sep 2024 15:43:03 +0530 Subject: [PATCH 01/18] CIRC-2148: Reverted CIRC-2100 PR and Rework for PrintEventDetail Pagination, searching and sorting (#1492) * CIRC-2148: Reverted CIRC-2100 PR * CIRC-2148: [Impl] mapped UserDetails user data in Request * CIRC-2148: [Impl] mapped UserDetails user data in Request * CIRC-2148: code refactoring * CIRC-2148: code refactoring * CIRC-2148: added lastPrintRequester in removeRelatedRecordInformation for update request, reverted some permissions, added new printDetails fields in request.json * CIRC-2148: test case added partial for debug * CIRC-2148: fix test case * CIRC-2148: Fixed PR comments * CIRC-2148: Bump up interface version * CIRC-2148: Bump up interface version * CIRC-2148: reverted circulation version --- descriptors/ModuleDescriptor-template.json | 6 +- ramls/examples/request.json | 8 +- ramls/request.json | 14 +- .../circulation/domain/PrintEventDetail.java | 48 ----- .../org/folio/circulation/domain/Request.java | 13 +- .../domain/RequestRepresentation.java | 47 +++-- .../storage/PrintEventsRepository.java | 74 +------- .../storage/requests/RequestRepository.java | 25 +-- .../storage/users/UserRepository.java | 21 ++- .../resources/PrintEventsResource.java | 4 +- .../RequestFromRepresentationService.java | 4 + .../folio/circulation/support/Clients.java | 15 +- .../requests/RequestsAPICreationTests.java | 174 +----------------- .../requests/RequestsAPIRetrievalTests.java | 89 +++++++-- .../api/support/builders/RequestBuilder.java | 40 +++- .../java/api/support/fakes/FakeOkapi.java | 2 - .../fakes/FakePrintEventStatusModule.java | 83 --------- .../api/support/fakes/FakeStorageModule.java | 4 +- 18 files changed, 193 insertions(+), 478 deletions(-) delete mode 100644 src/main/java/org/folio/circulation/domain/PrintEventDetail.java delete mode 100644 src/test/java/api/support/fakes/FakePrintEventStatusModule.java diff --git a/descriptors/ModuleDescriptor-template.json b/descriptors/ModuleDescriptor-template.json index f2e3bb42e7..95fccc6e00 100644 --- a/descriptors/ModuleDescriptor-template.json +++ b/descriptors/ModuleDescriptor-template.json @@ -1222,7 +1222,7 @@ }, { "id": "request-storage", - "version": "6.0" + "version": "6.1" }, { "id": "request-storage-batch", @@ -2065,9 +2065,7 @@ "users.collection.get", "addresstypes.collection.get", "usergroups.collection.get", - "usergroups.item.get", - "print-events-storage.print-events-status.item.post", - "circulation-storage.circulation-settings.collection.get" + "usergroups.item.get" ], "visible": false }, diff --git a/ramls/examples/request.json b/ramls/examples/request.json index 228c97f1b7..2072c6b617 100644 --- a/ramls/examples/request.json +++ b/ramls/examples/request.json @@ -47,11 +47,13 @@ "pickupServicePointName": "Circ Desk 1" }, "printDetails": { - "count": 4, - "lastPrintedDate": "2024-07-29T11:54:07.000Z", + "printCount": 32, + "requesterId": "21457ab5-4635-4e56-906a-908f05e9233b", + "isPrinted": true, + "printEventDate": "2024-09-13T06:34:16.035+00:00", "lastPrintRequester": { - "lastName": "lastName", "firstName": "firstName", + "lastName": "lastName", "middleName": "middleName" } } diff --git a/ramls/request.json b/ramls/request.json index 1b80913dbb..fb0d7b7a08 100644 --- a/ramls/request.json +++ b/ramls/request.json @@ -363,12 +363,22 @@ "type": "object", "readonly": true, "properties": { - "count": { + "printCount": { "description": "Total no of times the request is printed", "type": "integer", "readOnly": true }, - "lastPrintedDate": { + "requesterId": { + "description": "User uuid of last print requester", + "type": "string", + "readOnly": true + }, + "isPrinted": { + "description": "Whether the request is ever printed", + "type": "boolean", + "readOnly": true + }, + "printEventDate": { "description": "Recent printed time of the request", "type": "string", "format": "date-time", diff --git a/src/main/java/org/folio/circulation/domain/PrintEventDetail.java b/src/main/java/org/folio/circulation/domain/PrintEventDetail.java deleted file mode 100644 index b52a440c30..0000000000 --- a/src/main/java/org/folio/circulation/domain/PrintEventDetail.java +++ /dev/null @@ -1,48 +0,0 @@ -package org.folio.circulation.domain; - -import io.vertx.core.json.JsonObject; -import lombok.AllArgsConstructor; -import lombok.ToString; -import lombok.With; -import lombok.extern.log4j.Log4j2; - -import java.time.ZonedDateTime; - -import static org.folio.circulation.support.json.JsonPropertyFetcher.getDateTimeProperty; -import static org.folio.circulation.support.json.JsonPropertyFetcher.getIntegerProperty; -import static org.folio.circulation.support.json.JsonPropertyFetcher.getProperty; - - -@AllArgsConstructor -@Log4j2 -public class PrintEventDetail { - @ToString.Include - private final JsonObject representation; - @With - private final User printeduser; - - public static PrintEventDetail from(JsonObject representation) { - return new PrintEventDetail(representation, null); - } - - public String getUserId() { - return getProperty(representation, "requesterId"); - } - - public String getRequestId() { - return getProperty(representation, "requestId"); - } - - public int getCount() { - return getIntegerProperty(representation, "count", 0); - } - - public ZonedDateTime getPrintEventDate() { - return getDateTimeProperty(representation, "printEventDate"); - } - - public User getUser() { - return printeduser; - } - -} diff --git a/src/main/java/org/folio/circulation/domain/Request.java b/src/main/java/org/folio/circulation/domain/Request.java index d1cecb8085..b604a37548 100644 --- a/src/main/java/org/folio/circulation/domain/Request.java +++ b/src/main/java/org/folio/circulation/domain/Request.java @@ -102,8 +102,9 @@ public class Request implements ItemRelatedRecord, UserRelatedRecord { private boolean changedPosition; private Integer previousPosition; private boolean changedStatus; + @With - private PrintEventDetail printEventDetail; + private final User printDetailsRequester; public static Request from(JsonObject representation) { // TODO: make sure that operation and TLR settings don't matter for all processes calling @@ -225,7 +226,7 @@ public Request withItem(Item newItem) { cancellationReasonRepresentation, instance, instanceItems, instanceItemsRequestPolicies, newItem, requester, proxy, addressType, loan == null ? null : loan.withItem(newItem), pickupServicePoint, changedPosition, - previousPosition, changedStatus, printEventDetail); + previousPosition, changedStatus, null); } @Override @@ -243,6 +244,10 @@ public User getUser() { return getRequester(); } + public User getPrintDetailsRequester() { + return printDetailsRequester; + } + public String getfulfillmentPreferenceName() { return requestRepresentation.getString("fulfillmentPreference"); } @@ -291,6 +296,10 @@ public JsonObject getRequesterFromRepresentation() { return requestRepresentation.getJsonObject("requester"); } + public JsonObject getPrintDetails() { + return requestRepresentation.getJsonObject("printDetails"); + } + public String getRequesterBarcode() { return getRequesterFromRepresentation().getString("barcode", EMPTY); } diff --git a/src/main/java/org/folio/circulation/domain/RequestRepresentation.java b/src/main/java/org/folio/circulation/domain/RequestRepresentation.java index 6a51004c10..2f03abc5c6 100644 --- a/src/main/java/org/folio/circulation/domain/RequestRepresentation.java +++ b/src/main/java/org/folio/circulation/domain/RequestRepresentation.java @@ -33,12 +33,33 @@ public JsonObject extendedRepresentation(Request request) { addAdditionalProxyProperties(requestRepresentation, request.getProxy()); addAdditionalServicePointProperties(requestRepresentation, request.getPickupServicePoint()); addDeliveryAddress(requestRepresentation, request, request.getRequester()); - addPrintEventProperties(requestRepresentation, request.getPrintEventDetail()); + addPrintDetailsProperties(request, requestRepresentation); removeSearchIndexFields(requestRepresentation); + return requestRepresentation; } + private void addPrintDetailsProperties(Request request, JsonObject requestRepresentation) { + JsonObject printDetails = requestRepresentation.getJsonObject("printDetails"); + if (printDetails == null) { + if (log.isInfoEnabled()) { + log.info("addPrintEventProperties:: printDetails property is null for" + + " requestId {}", requestRepresentation.getString("id")); + } + return; + } + + User printDetailsUser = request.getPrintDetailsRequester(); + if (printDetailsUser != null) { + JsonObject lastPrintRequester = new JsonObject(); + lastPrintRequester.put("firstName", printDetailsUser.getFirstName()); + lastPrintRequester.put("lastName", printDetailsUser.getLastName()); + lastPrintRequester.put("middleName", printDetailsUser.getMiddleName()); + printDetails.put("lastPrintRequester", lastPrintRequester); + } + } + private static void addAdditionalRequesterProperties(JsonObject request, User requester) { if (requester == null) { String msg = "Unable to add requester properties to the request: {}, requester is null."; @@ -258,29 +279,5 @@ private static JsonObject userSummary(User user) { private static void removeSearchIndexFields(JsonObject request) { request.remove("searchIndex"); } - - private static void addPrintEventProperties(JsonObject request, PrintEventDetail printEventDetail) { - if (printEventDetail == null) { - if (log.isInfoEnabled()) { - log.info("addPrintEventProperties:: printEvent property is null for requestId {}", request.getString("id")); - } - return; - } - - var printEvent = new JsonObject(); - write(printEvent, "count", printEventDetail.getCount()); - write(printEvent, "lastPrintedDate", printEventDetail.getPrintEventDate()); - - var user = printEventDetail.getUser(); - if (user != null) { - var userSummary = new JsonObject(); - write(userSummary, "lastName", user.getLastName()); - write(userSummary, "firstName", user.getFirstName()); - write(userSummary, "middleName", user.getMiddleName()); - write(printEvent, "lastPrintRequester", userSummary); - } - write(request, "printDetails", printEvent); - } - } diff --git a/src/main/java/org/folio/circulation/infrastructure/storage/PrintEventsRepository.java b/src/main/java/org/folio/circulation/infrastructure/storage/PrintEventsRepository.java index 8622357131..1a179857ff 100644 --- a/src/main/java/org/folio/circulation/infrastructure/storage/PrintEventsRepository.java +++ b/src/main/java/org/folio/circulation/infrastructure/storage/PrintEventsRepository.java @@ -1,46 +1,26 @@ package org.folio.circulation.infrastructure.storage; -import io.vertx.core.json.JsonObject; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; -import org.folio.circulation.domain.MultipleRecords; -import org.folio.circulation.domain.PrintEventDetail; import org.folio.circulation.domain.PrintEventRequest; -import org.folio.circulation.domain.Request; import org.folio.circulation.support.Clients; import org.folio.circulation.support.CollectionResourceClient; import org.folio.circulation.support.http.client.ResponseInterpreter; import org.folio.circulation.support.results.Result; import java.lang.invoke.MethodHandles; -import java.util.Collection; -import java.util.Map; -import java.util.Optional; -import java.util.Set; import java.util.concurrent.CompletableFuture; -import static java.util.concurrent.CompletableFuture.completedFuture; -import static org.folio.circulation.resources.PrintEventsResource.PRINT_EVENT_FLAG_PROPERTY_NAME; -import static org.folio.circulation.resources.PrintEventsResource.PRINT_EVENT_FLAG_QUERY; import static org.folio.circulation.support.http.ResponseMapping.forwardOnFailure; -import static org.folio.circulation.support.json.JsonPropertyFetcher.getProperty; -import static org.folio.circulation.support.results.Result.of; 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; public class PrintEventsRepository { private static final Logger log = LogManager.getLogger(MethodHandles.lookup().lookupClass()); - private static final String RECORDS_PROPERTY_NAME = "printEventsStatusResponses"; - private static final String REQUEST_IDS = "requestIds"; + private final CollectionResourceClient printEventsStorageClient; - private final CollectionResourceClient printEventsStorageStatusClient; - private final CirculationSettingsRepository circulationSettingsRepository; public PrintEventsRepository(Clients clients) { - this.printEventsStorageClient = clients.printEventsStorageClient(); - this.printEventsStorageStatusClient = clients.printEventsStorageStatusClient(); - this.circulationSettingsRepository = new CirculationSettingsRepository(clients); + printEventsStorageClient = clients.printEventsStorageClient(); } public CompletableFuture> create(PrintEventRequest printEventRequest) { @@ -52,54 +32,4 @@ public CompletableFuture> create(PrintEventRequest printEventReques return printEventsStorageClient.post(storagePrintEventRequest).thenApply(interpreter::flatMap); } - public CompletableFuture>> findPrintEventDetails( - MultipleRecords multipleRequests) { - log.debug("findPrintEventDetails:: parameters multipleRequests: {}", - () -> multipleRecordsAsString(multipleRequests)); - var requestIds = multipleRequests.toKeys(Request::getId); - if (requestIds.isEmpty()) { - log.info("fetchAndMapPrintEventDetails:: No request id found"); - return completedFuture(succeeded(multipleRequests)); - } - return validatePrintEventFeatureFlag() - .thenCompose(isEnabled -> Boolean.TRUE.equals(isEnabled) - ? fetchAndMapPrintEventDetails(multipleRequests, requestIds) - : completedFuture(succeeded(multipleRequests))); - } - - private CompletableFuture>> fetchAndMapPrintEventDetails( - MultipleRecords multipleRequests, Set requestIds) { - log.debug("fetchAndMapPrintEventDetails:: parameters multipleRequests: {}, requestIds {}", - () -> multipleRecordsAsString(multipleRequests), () -> requestIds); - return fetchPrintDetailsByRequestIds(requestIds) - .thenApply(printEventRecordsResult -> printEventRecordsResult - .next(printEventRecords -> mapPrintEventDetailsToRequest(printEventRecords, multipleRequests))); - } - - private CompletableFuture validatePrintEventFeatureFlag() { - log.debug("validatePrintEventFeatureFlag:: Fetching and validating enablePrintLog flag from settings"); - return circulationSettingsRepository.findBy(PRINT_EVENT_FLAG_QUERY) - .thenApply(res -> Optional.ofNullable(res.value()) - .flatMap(records -> records.getRecords().stream().findFirst()) - .map(setting -> Boolean.valueOf(getProperty(setting.getValue(), PRINT_EVENT_FLAG_PROPERTY_NAME))) - .orElse(false)); - } - - private CompletableFuture>> fetchPrintDetailsByRequestIds - (Collection requestIds) { - log.debug("fetchPrintDetailsByRequestIds:: fetching print event details for requestIds {}", requestIds); - return printEventsStorageStatusClient.post(new JsonObject().put(REQUEST_IDS, requestIds)) - .thenApply(flatMapResult(response -> - MultipleRecords.from(response, PrintEventDetail::from, RECORDS_PROPERTY_NAME))); - } - - private Result> mapPrintEventDetailsToRequest( - MultipleRecords printEventDetails, MultipleRecords requests) { - log.debug("mapPrintEventDetailsToRequest:: Mapping print event details {} with requests {}", - () -> multipleRecordsAsString(printEventDetails), () -> multipleRecordsAsString(requests)); - Map printEventDetailMap = printEventDetails.toMap(PrintEventDetail::getRequestId); - return of(() -> - requests.mapRecords(request -> request - .withPrintEventDetail(printEventDetailMap.getOrDefault(request.getId(), null)))); - } } diff --git a/src/main/java/org/folio/circulation/infrastructure/storage/requests/RequestRepository.java b/src/main/java/org/folio/circulation/infrastructure/storage/requests/RequestRepository.java index c17aa06dc5..208c2bf0da 100644 --- a/src/main/java/org/folio/circulation/infrastructure/storage/requests/RequestRepository.java +++ b/src/main/java/org/folio/circulation/infrastructure/storage/requests/RequestRepository.java @@ -30,7 +30,6 @@ import org.folio.circulation.domain.ServicePoint; import org.folio.circulation.domain.StoredRequestRepresentation; import org.folio.circulation.domain.User; -import org.folio.circulation.infrastructure.storage.PrintEventsRepository; import org.folio.circulation.infrastructure.storage.ServicePointRepository; import org.folio.circulation.infrastructure.storage.inventory.InstanceRepository; import org.folio.circulation.infrastructure.storage.inventory.ItemRepository; @@ -67,7 +66,6 @@ public class RequestRepository { private final ServicePointRepository servicePointRepository; private final PatronGroupRepository patronGroupRepository; private final InstanceRepository instanceRepository; - private final PrintEventsRepository printEventsRepository; /** * Public constructor to avoid creating repositories twice @@ -78,8 +76,7 @@ public RequestRepository(org.folio.circulation.support.Clients clients, this(new Clients(clients.requestsStorage(), clients.requestsBatchStorage(), clients.cancellationReasonStorage()), itemRepository, userRepository, - loanRepository, servicePointRepository, patronGroupRepository, new InstanceRepository(clients), - new PrintEventsRepository(clients)); + loanRepository, servicePointRepository, patronGroupRepository, new InstanceRepository(clients)); } /** @@ -90,10 +87,9 @@ public RequestRepository(org.folio.circulation.support.Clients clients) { } private RequestRepository(Clients clients, ItemRepository itemRepository, - UserRepository userRepository, LoanRepository loanRepository, - ServicePointRepository servicePointRepository, - PatronGroupRepository patronGroupRepository, InstanceRepository instanceRepository, - PrintEventsRepository printEventsRepository) { + UserRepository userRepository, LoanRepository loanRepository, + ServicePointRepository servicePointRepository, + PatronGroupRepository patronGroupRepository, InstanceRepository instanceRepository) { this.requestsStorageClient = clients.getRequestsStorageClient(); this.requestsBatchStorageClient = clients.getRequestsBatchStorageClient(); @@ -104,7 +100,6 @@ private RequestRepository(Clients clients, ItemRepository itemRepository, this.servicePointRepository = servicePointRepository; this.patronGroupRepository = patronGroupRepository; this.instanceRepository = instanceRepository; - this.printEventsRepository = printEventsRepository; } public static RequestRepository using( @@ -118,14 +113,12 @@ public static RequestRepository using( itemRepository, userRepository, loanRepository, new ServicePointRepository(clients), new PatronGroupRepository(clients), - new InstanceRepository(clients), - new PrintEventsRepository(clients)); + new InstanceRepository(clients)); } public CompletableFuture>> findBy(String query) { return requestsStorageClient.getManyWithRawQueryStringParameters(query) .thenApply(flatMapResult(this::mapResponseToRequests)) - .thenCompose(r -> r.after(this::fetchPrintEventDetails)) .thenCompose(r -> r.after(this::fetchAdditionalFields)); } @@ -149,14 +142,6 @@ private CompletableFuture>> fetchAdditionalField .thenComposeAsync(result -> result.after(instanceRepository::findInstancesForRequests)); } - private CompletableFuture>> fetchPrintEventDetails( - MultipleRecords requestRecords) { - log.debug("fetchPrintEventDetails:: Fetching print event details for requestRecords: {}", - ()-> multipleRecordsAsString(requestRecords)); - return ofAsync(() -> requestRecords) - .thenComposeAsync(result -> result.after(printEventsRepository::findPrintEventDetails)); - } - CompletableFuture>> findByWithoutItems( CqlQuery query, PageLimit pageLimit) { diff --git a/src/main/java/org/folio/circulation/infrastructure/storage/users/UserRepository.java b/src/main/java/org/folio/circulation/infrastructure/storage/users/UserRepository.java index 8e8256f990..190d39e91d 100644 --- a/src/main/java/org/folio/circulation/infrastructure/storage/users/UserRepository.java +++ b/src/main/java/org/folio/circulation/infrastructure/storage/users/UserRepository.java @@ -19,11 +19,13 @@ import java.util.List; import java.util.Map; import java.util.Objects; +import java.util.Optional; import java.util.concurrent.CompletableFuture; import java.util.stream.Collectors; + +import io.vertx.core.json.JsonObject; import org.folio.circulation.domain.Loan; import org.folio.circulation.domain.MultipleRecords; -import org.folio.circulation.domain.PrintEventDetail; import org.folio.circulation.domain.Request; import org.folio.circulation.domain.User; import org.folio.circulation.domain.UserRelatedRecord; @@ -42,6 +44,7 @@ public class UserRepository { private static final Logger log = LogManager.getLogger(MethodHandles.lookup().lookupClass()); private static final String USERS_RECORD_PROPERTY = "users"; + private static final String REQUESTER_ID = "requesterId"; private final CollectionResourceClient usersStorageClient; @@ -272,8 +275,10 @@ private ArrayList getUsersFromRequest(Request request) { usersToFetch.add(request.getProxyUserId()); } - if (request.getPrintEventDetail() != null && request.getPrintEventDetail().getUserId() != null) { - usersToFetch.add(request.getPrintEventDetail().getUserId()); + JsonObject printDetails = request.getPrintDetails(); + if (printDetails != null && + StringUtils.isNotBlank(printDetails.getString(REQUESTER_ID))) { + usersToFetch.add(printDetails.getString(REQUESTER_ID)); } return usersToFetch; @@ -288,13 +293,9 @@ private Request matchUsersToRequests( return request .withRequester(userMap.getOrDefault(request.getUserId(), null)) .withProxy(userMap.getOrDefault(request.getProxyUserId(), null)) - .withPrintEventDetail(mapUserToPrintEventDetails(request, userMap)); - } - - private PrintEventDetail mapUserToPrintEventDetails(Request request, Map userMap) { - var printEventDetail = request.getPrintEventDetail(); - return printEventDetail != null ? - printEventDetail.withPrinteduser(userMap.getOrDefault(printEventDetail.getUserId(), null)) : null; + .withPrintDetailsRequester(userMap + .getOrDefault(Optional.ofNullable(request.getPrintDetails()) + .map(pd -> pd.getString(REQUESTER_ID)).orElse(null), null)); } private Result> mapResponseToUsers(Response response) { diff --git a/src/main/java/org/folio/circulation/resources/PrintEventsResource.java b/src/main/java/org/folio/circulation/resources/PrintEventsResource.java index b530e20110..605919b60b 100644 --- a/src/main/java/org/folio/circulation/resources/PrintEventsResource.java +++ b/src/main/java/org/folio/circulation/resources/PrintEventsResource.java @@ -30,11 +30,11 @@ public class PrintEventsResource extends Resource { private static final Logger log = LogManager.getLogger(MethodHandles.lookup().lookupClass()); - public static final String PRINT_EVENT_FLAG_QUERY = "query=name=printEventLogFeature"; + private static final String PRINT_EVENT_FLAG_QUERY = "query=name=printEventLogFeature"; private static final String PRINT_EVENT_FEATURE_DISABLED_ERROR = "print event feature is disabled for this tenant"; private static final String NO_CONFIG_FOUND_ERROR = "No configuration found for print event feature"; private static final String MULTIPLE_CONFIGS_ERROR = "Multiple configurations found for print event feature"; - public static final String PRINT_EVENT_FLAG_PROPERTY_NAME = "enablePrintLog"; + private static final String PRINT_EVENT_FLAG_PROPERTY_NAME = "enablePrintLog"; public PrintEventsResource(HttpClient client) { super(client); diff --git a/src/main/java/org/folio/circulation/resources/RequestFromRepresentationService.java b/src/main/java/org/folio/circulation/resources/RequestFromRepresentationService.java index 255b015535..a11b86d231 100644 --- a/src/main/java/org/folio/circulation/resources/RequestFromRepresentationService.java +++ b/src/main/java/org/folio/circulation/resources/RequestFromRepresentationService.java @@ -578,6 +578,10 @@ private Request removeRelatedRecordInformation(Request request) { representation.remove("pickupServicePoint"); representation.remove("deliveryAddress"); + JsonObject printDetails = representation.getJsonObject("printDetails"); + if (printDetails != null && printDetails.containsKey("lastPrintRequester")) { + printDetails.remove("lastPrintRequester"); + } return request; } diff --git a/src/main/java/org/folio/circulation/support/Clients.java b/src/main/java/org/folio/circulation/support/Clients.java index 9ff7028965..99be88183c 100644 --- a/src/main/java/org/folio/circulation/support/Clients.java +++ b/src/main/java/org/folio/circulation/support/Clients.java @@ -71,7 +71,7 @@ public class Clients { private final GetManyRecordsClient settingsStorageClient; private final CollectionResourceClient circulationSettingsStorageClient; private final CollectionResourceClient printEventsStorageClient; - private final CollectionResourceClient printEventsStorageStatusClient; + public static Clients create(WebContext context, HttpClient httpClient) { return new Clients(context.createHttpClient(httpClient), context); @@ -141,7 +141,7 @@ private Clients(OkapiHttpClient client, WebContext context) { circulationItemClient = createCirculationItemClient(client, context); circulationSettingsStorageClient = createCirculationSettingsStorageClient(client, context); printEventsStorageClient = createPrintEventsStorageClient(client, context); - printEventsStorageStatusClient = createPrintEventsStorageStatusClient(client, context); + } catch(MalformedURLException e) { throw new InvalidOkapiLocationException(context.getOkapiLocation(), e); @@ -388,10 +388,6 @@ public CollectionResourceClient printEventsStorageClient() { return printEventsStorageClient; } - public CollectionResourceClient printEventsStorageStatusClient() { - return printEventsStorageStatusClient; - } - private static CollectionResourceClient getCollectionResourceClient( OkapiHttpClient client, WebContext context, String path) @@ -833,13 +829,6 @@ private CollectionResourceClient createPrintEventsStorageClient( "/print-events-storage/print-events-entry"); } - private CollectionResourceClient createPrintEventsStorageStatusClient( - OkapiHttpClient client, WebContext context) throws MalformedURLException { - - return getCollectionResourceClient(client, context, - "/print-events-storage/print-events-status"); - } - private GetManyRecordsClient createSettingsStorageClient( OkapiHttpClient client, WebContext context) throws MalformedURLException { diff --git a/src/test/java/api/requests/RequestsAPICreationTests.java b/src/test/java/api/requests/RequestsAPICreationTests.java index a7493260b7..7b08979d85 100644 --- a/src/test/java/api/requests/RequestsAPICreationTests.java +++ b/src/test/java/api/requests/RequestsAPICreationTests.java @@ -16,7 +16,6 @@ import static api.support.fixtures.TemplateContextMatchers.getUserContextMatchers; import static api.support.http.CqlQuery.exactMatch; import static api.support.http.CqlQuery.notEqual; -import static api.support.http.InterfaceUrls.printEventsUrl; import static api.support.http.Limit.limit; import static api.support.http.Offset.noOffset; import static api.support.matchers.EventMatchers.isValidLoanDueDateChangedEvent; @@ -108,7 +107,6 @@ import java.util.stream.IntStream; import java.util.stream.Stream; -import api.support.builders.CirculationSettingBuilder; import org.apache.http.HttpStatus; import org.awaitility.Awaitility; import org.folio.circulation.domain.ItemStatus; @@ -196,7 +194,6 @@ public class RequestsAPICreationTests extends APITests { public void afterEach() { mockClockManagerToReturnDefaultDateTime(); configurationsFixture.deleteTlrFeatureConfig(); - circulationSettingsClient.deleteAll(); } @Test @@ -232,7 +229,10 @@ void canCreateARequest() { .withHoldShelfExpiration(LocalDate.of(2017, 8, 31)) .withPickupServicePointId(pickupServicePointId) .withTags(new RequestBuilder.Tags(asList("new", "important"))) - .withPatronComments("I need this book")); + .withPatronComments("I need this book") + .withPrintDetails(new RequestBuilder.PrintDetails(49, + requester.getId().toString(), true, "2024-09-16T11:58:22" + + ".295+00:00"))); JsonObject representation = request.getJson(); @@ -4862,124 +4862,6 @@ void createHoldRequestForDcbItemAndResponseContainsDcbTitle() { } - @Test - void fetchRequestWithOutPrintEventFeature() { - assertThat("Circulation settings enabled", circulationSettingsClient.getAll().isEmpty()); - var barcode1 = "barcode1"; - var barcode2 = "barcode2"; - // creating 2 different requests and assert request details without enabling printEvent feature - var userResource = usersFixture.charlotte(); - var servicePointId = servicePointsFixture.cd1().getId(); - var requestId1 = createRequest(userResource, barcode1, servicePointId).getId(); - var requestId2 = createRequest(userResource, barcode2, servicePointId).getId(); - - JsonObject requestRepresentation1 = requestsClient.getMany(exactMatch("id", requestId1.toString())).getFirst(); - JsonObject requestRepresentation2 = requestsClient.getMany(exactMatch("id", requestId2.toString())).getFirst(); - assertRequestDetails(requestRepresentation1, requestId1, barcode1, servicePointId); - assertRequestDetails(requestRepresentation2, requestId2, barcode2, servicePointId); - assertThat("printDetails should be null for request1 because the print event feature is not enabled", - requestRepresentation1.getJsonObject("printDetails"), Matchers.nullValue()); - assertThat("printDetails should be null for request2 because the print event feature is not enabled", - requestRepresentation2.getJsonObject("printDetails"), Matchers.nullValue()); - } - - @Test - void printAndFetchDetailWithPrintEventFeatureEnabled() { - assertThat("Circulation settings enabled", circulationSettingsClient.getAll().isEmpty()); - var barcode1 = "barcode1"; - var barcode2 = "barcode2"; - // creating 2 different requests and print those 2 requests - // assert request details with enabling printEvent feature - circulationSettingsClient.create(new CirculationSettingBuilder() - .withName("printEventLogFeature") - .withValue(new JsonObject().put("enablePrintLog", true))); - var userResource1 = usersFixture.charlotte(); - var userResource2 = usersFixture.jessica(); - var servicePointId = servicePointsFixture.cd1().getId(); - var requestId1 = createRequest(userResource1, barcode1, servicePointId).getId(); - var requestId2 = createRequest(userResource2, barcode2, servicePointId).getId(); - // Printing request1 using user1 and assertRequest details - var printRequest = getPrintEvent(); - printRequest.put("requestIds", List.of(requestId1)); - printRequest.put("requesterId", userResource1.getId()); - - restAssuredClient.post(printRequest, printEventsUrl("/print-events-entry"), "post-print-event"); - - // Printing request2 using user2 and assertRequest details - printRequest = getPrintEvent(); - printRequest.put("requestIds", List.of(requestId2)); - printRequest.put("requesterId", userResource2.getId()); - - restAssuredClient.post(printRequest, printEventsUrl("/print-events-entry"), "post-print-event"); - JsonObject requestRepresentation1 = requestsClient.getMany(exactMatch("id", requestId1.toString())).getFirst(); - JsonObject requestRepresentation2 = requestsClient.getMany(exactMatch("id", requestId2.toString())).getFirst(); - assertRequestDetails(requestRepresentation1, requestId1, barcode1, servicePointId); - assertRequestDetails(requestRepresentation2, requestId2, barcode2, servicePointId); - assertThat("printDetails should not be null for request1 as the feature is enabled and the request is printed", - requestRepresentation1.getJsonObject("printDetails"), Matchers.notNullValue()); - assertThat("printDetails should not be null for request2 as the feature is enabled and the request is printed", - requestRepresentation2.getJsonObject("printDetails"), Matchers.notNullValue()); - assertPrintDetails(requestRepresentation1, 1, "2024-06-25T11:54:07.000Z", userResource1); - assertPrintDetails(requestRepresentation2, 1, "2024-06-25T11:54:07.000Z", userResource2); - - // printing both request for second time using user2 and assert requestDetails - printRequest.put("printEventDate", "2024-06-25T12:54:07.000Z"); - printRequest.put("requestIds", List.of(requestId1, requestId2)); - printRequest.put("requesterId", userResource2.getId()); - - restAssuredClient.post(printRequest, printEventsUrl("/print-events-entry"), "post-print-event"); - - requestRepresentation1 = requestsClient.getMany(exactMatch("id", requestId1.toString())).getFirst(); - requestRepresentation2 = requestsClient.getMany(exactMatch("id", requestId2.toString())).getFirst(); - assertRequestDetails(requestRepresentation1, requestId1, barcode1, servicePointId); - assertRequestDetails(requestRepresentation2, requestId2, barcode2, servicePointId); - assertPrintDetails(requestRepresentation1, 2, "2024-06-25T12:54:07.000Z", userResource2); - assertPrintDetails(requestRepresentation2, 2, "2024-06-25T12:54:07.000Z", userResource2); - - // printing request1 for third time using user1 and assert requestDetails - printRequest.put("printEventDate", "2024-06-25T12:58:07.000Z"); - printRequest.put("requestIds", List.of(requestId1)); - printRequest.put("requesterId", userResource1.getId()); - - restAssuredClient.post(printRequest, printEventsUrl("/print-events-entry"), "post-print-event"); - - requestRepresentation1 = requestsClient.getMany(exactMatch("id", requestId1.toString())).getFirst(); - requestRepresentation2 = requestsClient.getMany(exactMatch("id", requestId2.toString())).getFirst(); - assertRequestDetails(requestRepresentation1, requestId1, barcode1, servicePointId); - assertRequestDetails(requestRepresentation2, requestId2, barcode2, servicePointId); - assertPrintDetails(requestRepresentation1, 3, "2024-06-25T12:58:07.000Z", userResource1); - assertPrintDetails(requestRepresentation2, 2, "2024-06-25T12:54:07.000Z", userResource2); - } - - @Test - void printAndFetchRequestWithPrintEventFeatureDisabled() { - assertThat("Circulation settings enabled", circulationSettingsClient.getAll().isEmpty()); - var barcode1 = "barcode1"; - var barcode2 = "barcode2"; - // creating 2 different requests and print it - // assert request details without enabling printEvent feature - circulationSettingsClient.create(new CirculationSettingBuilder() - .withName("printEventLogFeature") - .withValue(new JsonObject().put("enablePrintLog", false))); - var userResource = usersFixture.charlotte(); - var servicePointId = servicePointsFixture.cd1().getId(); - var requestId1 = createRequest(userResource, barcode1, servicePointId).getId(); - var requestId2 = createRequest(userResource, barcode2, servicePointId).getId(); - var printRequest = getPrintEvent(); - printRequest.put("requestIds", List.of(requestId1, requestId2)); - - restAssuredClient.post(printRequest, printEventsUrl("/print-events-entry"), "post-print-event"); - - JsonObject requestRepresentation1 = requestsClient.getMany(exactMatch("id", requestId1.toString())).getFirst(); - JsonObject requestRepresentation2 = requestsClient.getMany(exactMatch("id", requestId2.toString())).getFirst(); - assertRequestDetails(requestRepresentation1, requestId1, barcode1, servicePointId); - assertRequestDetails(requestRepresentation2, requestId2, barcode2, servicePointId); - assertThat("printDetails should be null for request1 because the print event feature is disabled", - requestRepresentation1.getJsonObject("printDetails"), Matchers.nullValue()); - assertThat("printDetails should be null for request2 because the print event feature is disabled", - requestRepresentation2.getJsonObject("printDetails"), Matchers.nullValue()); - } - @Test void itemLevelRequestShouldBeCreatedWithDeliveryFulfillmentPreference() { final UUID requestPolicyId = UUID.randomUUID(); @@ -5350,52 +5232,4 @@ private void validateInstanceRepresentation(JsonObject requestInstance) { assertThat(firstPublication.getString("place"), is("New York")); assertThat(firstPublication.getString("dateOfPublication"), is("2016")); } - - private IndividualResource createRequest(UserResource userResource, String itemBarcode, - UUID pickupServicePointId) { - return requestsFixture.place( - new RequestBuilder() - .open() - .page() - .forItem(itemsFixture.basedUponSmallAngryPlanet(itemBarcode)) - .by(userResource) - .fulfillToHoldShelf() - .withRequestExpiration(LocalDate.of(2024, 7, 30)) - .withHoldShelfExpiration(LocalDate.of(2024, 8, 15)) - .withPickupServicePointId(pickupServicePointId)); - } - - private void assertRequestDetails(JsonObject representation, UUID id, String barcodeName, UUID servicePointId) { - assertThat(representation.getString("id"), is(id.toString())); - assertThat(representation.getString("requestType"), is("Page")); - assertThat(representation.getString("requestLevel"), is("Item")); - assertThat(representation.getString("requestDate"), is("2017-07-15T09:35:27.000Z")); - assertThat(representation.getJsonObject("item").getString("barcode"), is(barcodeName)); - assertThat(representation.getString("fulfillmentPreference"), is("Hold Shelf")); - assertThat(representation.getString("requestExpirationDate"), is("2024-07-30T23:59:59.000Z")); - assertThat(representation.getString("holdShelfExpirationDate"), is("2024-08-15")); - assertThat(representation.getString("status"), is("Open - Not yet filled")); - assertThat(representation.getString("pickupServicePointId"), is(servicePointId.toString())); - } - - private JsonObject getPrintEvent() { - return new JsonObject() - .put("requesterId", "5f5751b4-e352-4121-adca-204b0c2aec43") - .put("requesterName", "requester") - .put("printEventDate", "2024-06-25T11:54:07.000Z"); - } - - private void assertPrintDetails(JsonObject representation, int count, String printEventDate, - UserResource userResource) { - var printDetailObject = representation.getJsonObject("printDetails"); - var lastPrintRequesterObject = printDetailObject.getJsonObject("lastPrintRequester"); - assertThat(printDetailObject.getInteger("count"), is(count)); - assertThat(printDetailObject.getString("lastPrintedDate"), is(printEventDate)); - assertThat(lastPrintRequesterObject.getString("middleName"), - is(userResource.getJson().getJsonObject("personal").getString("middleName"))); - assertThat(lastPrintRequesterObject.getString("lastName"), - is(userResource.getJson().getJsonObject("personal").getString("lastName"))); - assertThat(lastPrintRequesterObject.getString("firstName"), - is(userResource.getJson().getJsonObject("personal").getString("firstName"))); - } } diff --git a/src/test/java/api/requests/RequestsAPIRetrievalTests.java b/src/test/java/api/requests/RequestsAPIRetrievalTests.java index 6605d72546..3c71fcc441 100644 --- a/src/test/java/api/requests/RequestsAPIRetrievalTests.java +++ b/src/test/java/api/requests/RequestsAPIRetrievalTests.java @@ -1,5 +1,24 @@ package api.requests; +import api.support.APITests; +import api.support.MultipleJsonRecords; +import api.support.builders.Address; +import api.support.builders.ItemBuilder; +import api.support.builders.RequestBuilder; +import api.support.http.IndividualResource; +import api.support.http.ItemResource; +import api.support.http.UserResource; +import io.vertx.core.json.JsonArray; +import io.vertx.core.json.JsonObject; +import org.folio.circulation.support.http.client.Response; +import org.hamcrest.CoreMatchers; +import org.junit.jupiter.api.Test; + +import java.net.HttpURLConnection; +import java.time.LocalDate; +import java.time.ZonedDateTime; +import java.util.UUID; + import static api.support.builders.ItemBuilder.CHECKED_OUT; import static api.support.fixtures.ConfigurationExample.newYorkTimezoneConfiguration; import static api.support.http.CqlQuery.noQuery; @@ -26,26 +45,6 @@ import static org.hamcrest.core.Is.is; import static org.junit.jupiter.api.Assertions.assertTrue; -import java.net.HttpURLConnection; -import java.time.LocalDate; -import java.time.ZonedDateTime; -import java.util.UUID; - -import org.folio.circulation.support.http.client.Response; -import org.hamcrest.CoreMatchers; -import org.junit.jupiter.api.Test; - -import api.support.APITests; -import api.support.MultipleJsonRecords; -import api.support.TlrFeatureStatus; -import api.support.builders.Address; -import api.support.builders.ItemBuilder; -import api.support.builders.RequestBuilder; -import api.support.http.IndividualResource; -import api.support.http.ItemResource; -import io.vertx.core.json.JsonArray; -import io.vertx.core.json.JsonObject; - class RequestsAPIRetrievalTests extends APITests { private static final String NEW_TAG = "new"; private static final String IMPORTANT_TAG = "important"; @@ -869,4 +868,54 @@ private void assertThatRequestsHavePatronComments(MultipleJsonRecords requests) hasJsonPath("patronComments", "Comment 5") )); } + + + @Test + void printDetailsTest() { + UserResource printDetailsRequester = usersFixture.charlotte(); + UUID printDetailsRequesterId = printDetailsRequester.getId(); + final IndividualResource smallAngryPlanet = itemsFixture.basedUponSmallAngryPlanet(); + final IndividualResource workAddressType = addressTypesFixture.work(); + final IndividualResource charlotte = usersFixture.charlotte( + builder -> builder.withAddress( + new Address(workAddressType.getId(), + "Fake first address line", + "Fake second address line", + "Fake city", + "Fake region", + "Fake postal code", + "Fake country code"))); + + requestsFixture.place(new RequestBuilder() + .page() + .forItem(smallAngryPlanet) + .deliverToAddress(workAddressType.getId()) + .by(charlotte) + .withPrintDetails(new RequestBuilder + .PrintDetails(49, printDetailsRequesterId.toString(), + true, "2024-09-16T11:58:22.295+00:00"))); + + final MultipleJsonRecords requests = requestsFixture.getRequests( + queryFromTemplate("printDetails.isPrinted==%s", "true"), + noLimit(), noOffset()); + + assertThat(requests.size(), is(1)); + assertThat(requests.totalRecords(), is(1)); + + JsonObject printDetails = requests.getFirst().getJsonObject("printDetails"); + assertThat(printDetails.getInteger("printCount"), is(49)); + assertThat(printDetails.getString("requesterId"), + is(printDetailsRequesterId.toString())); + assertTrue(printDetails.getBoolean("isPrinted")); + + JsonObject lastPrintRequester = printDetails.getJsonObject( + "lastPrintRequester"); + JsonObject expectedLastPrintRequester = printDetailsRequester.getJson() + .getJsonObject("personal"); + + assertThat(lastPrintRequester.getString("firstName"), + is(expectedLastPrintRequester.getString("firstName"))); + assertThat(lastPrintRequester.getString("lastName"), + is(expectedLastPrintRequester.getString("lastName"))); + } } diff --git a/src/test/java/api/support/builders/RequestBuilder.java b/src/test/java/api/support/builders/RequestBuilder.java index 2514482a1e..180de0a22b 100644 --- a/src/test/java/api/support/builders/RequestBuilder.java +++ b/src/test/java/api/support/builders/RequestBuilder.java @@ -63,6 +63,7 @@ public class RequestBuilder extends JsonBuilder implements Builder { private final Tags tags; private final String patronComments; private final BlockOverrides blockOverrides; + private final PrintDetails printDetails; public RequestBuilder() { this(UUID.randomUUID(), @@ -89,6 +90,7 @@ public RequestBuilder() { null, null, null, + null, null); } @@ -120,7 +122,8 @@ public static RequestBuilder from(IndividualResource response) { getUUIDProperty(representation, "pickupServicePointId"), new Tags((toStream(representation.getJsonObject("tags"), "tagList").collect(toList()))), getProperty(representation, "patronComments"), - null + null, + PrintDetails.fromRepresentation(representation) ); } @@ -191,6 +194,10 @@ public JsonObject create() { } } + if (printDetails != null) { + put(request, "printDetails", printDetails.toJsonObject()); + } + return request; } @@ -321,4 +328,35 @@ private static class PatronSummary { public static class Tags { private final List tagList; } + + @AllArgsConstructor + @Getter + public static class PrintDetails { + private final Integer printCount; + private final String requesterId; + private final Boolean isPrinted; + private final String printEventDate; + + public static PrintDetails fromRepresentation(JsonObject representation) { + JsonObject printDetails = representation.getJsonObject("printDetails"); + if (printDetails != null) { + final Integer printCount = printDetails.getInteger("printCount"); + final String requesterId = printDetails.getString("requesterId"); + final Boolean isPrinted = printDetails.getBoolean("isPrinted"); + final String printEventDate = printDetails.getString("printEventDate"); + return new PrintDetails(printCount, requesterId, isPrinted, + printEventDate); + } + return null; + } + + public JsonObject toJsonObject() { + JsonObject printDetails = new JsonObject(); + printDetails.put("printCount", printCount); + printDetails.put("requesterId", requesterId); + printDetails.put("isPrinted", isPrinted); + printDetails.put("printEventDate", printEventDate); + return printDetails; + } + } } diff --git a/src/test/java/api/support/fakes/FakeOkapi.java b/src/test/java/api/support/fakes/FakeOkapi.java index c146982ce8..1ed5b8f5b0 100644 --- a/src/test/java/api/support/fakes/FakeOkapi.java +++ b/src/test/java/api/support/fakes/FakeOkapi.java @@ -427,8 +427,6 @@ public void start(Promise startFuture) throws IOException { .withChangeMetadata() .create().register(router); - new FakePrintEventStatusModule().register(router); - new FakeFeeFineOperationsModule().register(router); server.requestHandler(router) diff --git a/src/test/java/api/support/fakes/FakePrintEventStatusModule.java b/src/test/java/api/support/fakes/FakePrintEventStatusModule.java deleted file mode 100644 index aaeced3434..0000000000 --- a/src/test/java/api/support/fakes/FakePrintEventStatusModule.java +++ /dev/null @@ -1,83 +0,0 @@ -package api.support.fakes; - -import io.vertx.core.buffer.Buffer; -import io.vertx.core.json.JsonObject; -import io.vertx.ext.web.Router; -import io.vertx.ext.web.RoutingContext; -import lombok.SneakyThrows; - -import java.text.ParseException; -import java.text.SimpleDateFormat; -import java.util.ArrayList; -import java.util.Date; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; - -import static api.support.APITestContext.getTenantId; -import static api.support.fakes.Storage.getStorage; -import static org.folio.HttpStatus.HTTP_UNPROCESSABLE_ENTITY; -import static org.folio.circulation.support.http.server.JsonHttpResponse.ok; - -public class FakePrintEventStatusModule { - - @SneakyThrows - public void register(Router router) { - router.post("/print-events-storage/print-events-status") - .handler(this::handlePrintEventStatusRequest); - } - - private void handlePrintEventStatusRequest(RoutingContext routingContext) { - var request = routingContext.body().asJsonObject(); - var requestIds = request.getJsonArray("requestIds"); - if (requestIds.isEmpty()) { - Buffer buffer = Buffer.buffer( - "size must be between 1 and 2147483647", "UTF-8"); - routingContext.response() - .setStatusCode(HTTP_UNPROCESSABLE_ENTITY.toInt()) - .putHeader("content-type", "text/plain; charset=utf-8") - .putHeader("content-length", Integer.toString(buffer.length())) - .write(buffer); - routingContext.response().end(); - } else { - var jsonObjectList = new ArrayList<>(getStorage() - .getTenantResources("/print-events-storage/print-events-entry", getTenantId()) - .values() - .stream() - .toList()); - SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSX"); - // Sorting jsonObjectList based on PrintEventDate so that it will always return latest printEventDetail - jsonObjectList.sort((obj1, obj2) -> { - try { - Date date1 = dateFormat.parse(obj1.getString("printEventDate")); - Date date2 = dateFormat.parse(obj2.getString("printEventDate")); - return date2.compareTo(date1); - } catch (ParseException e) { - throw new RuntimeException(e); - } - }); - Map> groupByRequestIdMap = new LinkedHashMap<>(); - requestIds.forEach(requestId -> { - var requestList = jsonObjectList.stream().filter(jsonObject -> - jsonObject.getJsonArray("requestIds").contains(requestId)) - .toList(); - groupByRequestIdMap.put((String) requestId, requestList); - }); - var jsonObjectResponse = new JsonObject(); - var printEventStatusResponses = new ArrayList<>(); - jsonObjectResponse.put("printEventsStatusResponses", printEventStatusResponses); - requestIds.forEach(id -> { - var requestDetail = groupByRequestIdMap.get(id); - if (requestDetail != null && !requestDetail.isEmpty()) { - var object = new JsonObject() - .put("requestId", id) - .put("count", requestDetail.size()) - .put("requesterId", requestDetail.get(0).getString("requesterId")) - .put("printEventDate", requestDetail.get(0).getString("printEventDate")); - printEventStatusResponses.add(object); - } - }); - ok(jsonObjectResponse).writeTo(routingContext.response()); - } - } -} diff --git a/src/test/java/api/support/fakes/FakeStorageModule.java b/src/test/java/api/support/fakes/FakeStorageModule.java index a3cf6cdff1..2224d6b95a 100644 --- a/src/test/java/api/support/fakes/FakeStorageModule.java +++ b/src/test/java/api/support/fakes/FakeStorageModule.java @@ -390,7 +390,9 @@ private void getMany(RoutingContext routingContext) { result.put(collectionPropertyName, new JsonArray(pagedItems)); result.put("totalRecords", filteredItems.size()); - + if(collectionPropertyName.equalsIgnoreCase("requests")) { + System.out.println(); + } log.debug("Found {} resources: {}", recordTypeName, result.encodePrettily()); HttpServerResponse response = routingContext.response(); From cf733df447daae3076f05eb7ebedf051712be206 Mon Sep 17 00:00:00 2001 From: JanisSaldabols <143704574+JanisSaldabols@users.noreply.github.com> Date: Thu, 3 Oct 2024 11:13:09 +0300 Subject: [PATCH 02/18] =?UTF-8?q?CIRC-2141=20Allow=20specifying=20item=20l?= =?UTF-8?q?ocation=20when=20creating=20title-level=20re=E2=80=A6=20(#1491)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * CIRC-2141 Allow specifying item location when creating title-level requests --- ramls/request.json | 6 +- .../org/folio/circulation/domain/Campus.java | 3 +- .../folio/circulation/domain/Institution.java | 3 +- .../org/folio/circulation/domain/Item.java | 8 +++ .../org/folio/circulation/domain/Library.java | 3 +- .../org/folio/circulation/domain/Request.java | 5 ++ .../representations/RequestProperties.java | 1 + .../storage/inventory/LocationRepository.java | 30 +++++++++- .../services/ItemForTlrService.java | 17 +++++- .../storage/mappers/CampusMapper.java | 3 +- .../storage/mappers/InstitutionMapper.java | 3 +- .../storage/mappers/LibraryMapper.java | 3 +- .../requests/RequestsAPICreationTests.java | 58 +++++++++++++++++++ .../api/support/builders/RequestBuilder.java | 17 ++++-- 14 files changed, 145 insertions(+), 15 deletions(-) diff --git a/ramls/request.json b/ramls/request.json index fb0d7b7a08..491928db08 100644 --- a/ramls/request.json +++ b/ramls/request.json @@ -17,7 +17,7 @@ "requestLevel": { "description": "Level of the request - Item or Title", "type": "string", - "enum": ["Item"] + "enum": ["Item", "Title"] }, "requestDate": { "description": "Date the request was made", @@ -433,6 +433,10 @@ "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, diff --git a/src/main/java/org/folio/circulation/domain/Campus.java b/src/main/java/org/folio/circulation/domain/Campus.java index 1b294cce79..dd1d88ffee 100644 --- a/src/main/java/org/folio/circulation/domain/Campus.java +++ b/src/main/java/org/folio/circulation/domain/Campus.java @@ -9,9 +9,10 @@ public static Campus unknown() { } public static Campus unknown(String id) { - return new Campus(id, null); + return new Campus(id, null, null); } String id; String name; + String code; } diff --git a/src/main/java/org/folio/circulation/domain/Institution.java b/src/main/java/org/folio/circulation/domain/Institution.java index 3d94f43ec4..216a0a8b1c 100644 --- a/src/main/java/org/folio/circulation/domain/Institution.java +++ b/src/main/java/org/folio/circulation/domain/Institution.java @@ -9,9 +9,10 @@ public static Institution unknown() { } public static Institution unknown(String id) { - return new Institution(id, null); + return new Institution(id, null, null); } String id; String name; + String code; } diff --git a/src/main/java/org/folio/circulation/domain/Item.java b/src/main/java/org/folio/circulation/domain/Item.java index c27a296ef1..a5341abb48 100644 --- a/src/main/java/org/folio/circulation/domain/Item.java +++ b/src/main/java/org/folio/circulation/domain/Item.java @@ -415,4 +415,12 @@ public String getLendingLibraryCode() { public String getDcbItemTitle() { return getProperty(itemRepresentation, "instanceTitle"); } + + public boolean isAtLocation(String locationCode) { + return locationCode != null && getLocation() != null && ( + locationCode.equals(getLocation().getCode()) || + locationCode.equals(getLocation().getLibrary().getCode()) || + locationCode.equals(getLocation().getCampus().getCode()) || + locationCode.equals(getLocation().getInstitution().getCode())); + } } diff --git a/src/main/java/org/folio/circulation/domain/Library.java b/src/main/java/org/folio/circulation/domain/Library.java index 372f754731..d702165ab5 100644 --- a/src/main/java/org/folio/circulation/domain/Library.java +++ b/src/main/java/org/folio/circulation/domain/Library.java @@ -9,9 +9,10 @@ public static Library unknown() { } public static Library unknown(String id) { - return new Library(id, null); + return new Library(id, null, null); } String id; String name; + String code; } diff --git a/src/main/java/org/folio/circulation/domain/Request.java b/src/main/java/org/folio/circulation/domain/Request.java index b604a37548..4075fdf50d 100644 --- a/src/main/java/org/folio/circulation/domain/Request.java +++ b/src/main/java/org/folio/circulation/domain/Request.java @@ -17,6 +17,7 @@ import static org.folio.circulation.domain.representations.RequestProperties.CANCELLATION_REASON_ID; import static org.folio.circulation.domain.representations.RequestProperties.CANCELLATION_REASON_NAME; import static org.folio.circulation.domain.representations.RequestProperties.CANCELLATION_REASON_PUBLIC_DESCRIPTION; +import static org.folio.circulation.domain.representations.RequestProperties.ITEM_LOCATION_CODE; import static org.folio.circulation.domain.representations.RequestProperties.HOLDINGS_RECORD_ID; import static org.folio.circulation.domain.representations.RequestProperties.HOLD_SHELF_EXPIRATION_DATE; import static org.folio.circulation.domain.representations.RequestProperties.INSTANCE_ID; @@ -388,6 +389,10 @@ public String getPatronComments() { return getProperty(requestRepresentation, "patronComments"); } + public String geItemLocationCode() { + return getProperty(requestRepresentation, ITEM_LOCATION_CODE); + } + public Request truncateRequestExpirationDateToTheEndOfTheDay(ZoneId zone) { ZonedDateTime requestExpirationDate = getRequestExpirationDate(); if (requestExpirationDate != null) { diff --git a/src/main/java/org/folio/circulation/domain/representations/RequestProperties.java b/src/main/java/org/folio/circulation/domain/representations/RequestProperties.java index cf3be67cb1..830f2109d7 100644 --- a/src/main/java/org/folio/circulation/domain/representations/RequestProperties.java +++ b/src/main/java/org/folio/circulation/domain/representations/RequestProperties.java @@ -21,4 +21,5 @@ private RequestProperties() { } public static final String REQUESTER_ID = "requesterId"; public static final String FULFILLMENT_PREFERENCE = "fulfillmentPreference"; public static final String PICKUP_SERVICE_POINT_ID = "pickupServicePointId"; + public static final String ITEM_LOCATION_CODE = "itemLocationCode"; } diff --git a/src/main/java/org/folio/circulation/infrastructure/storage/inventory/LocationRepository.java b/src/main/java/org/folio/circulation/infrastructure/storage/inventory/LocationRepository.java index 52fe73dc2d..6099d95589 100644 --- a/src/main/java/org/folio/circulation/infrastructure/storage/inventory/LocationRepository.java +++ b/src/main/java/org/folio/circulation/infrastructure/storage/inventory/LocationRepository.java @@ -138,7 +138,9 @@ public CompletableFuture>> fetchLocations( new LocationMapper()::toDomain); return fetcher.findByIds(locationIds) - .thenCompose(this::loadLibrariesForLocations); + .thenCompose(this::loadLibrariesForLocations) + .thenCompose(this::loadCampusesForLocations) + .thenCompose(this::loadInstitutionsForLocations); } private CompletableFuture> loadLibrary(Location location) { @@ -208,6 +210,19 @@ public CompletableFuture>> getLibraries( .thenApply(mapResult(records -> records.toMap(Library::getId))); } + private CompletableFuture>> loadCampusesForLocations( + Result> multipleRecordsResult) { + + log.debug("loadCampusesForLocations:: parameters multipleRecordsResult: {}", + () -> resultAsString(multipleRecordsResult)); + + return multipleRecordsResult.combineAfter( + locations -> getCampuses(locations.getRecords()), (locations, campuses) -> + locations.mapRecords(location -> location.withCampus( + campuses.getOrDefault(location.getCampusId(), Campus.unknown(location.getCampusId()))))); + + } + public CompletableFuture>> getCampuses( Collection locations) { @@ -223,6 +238,19 @@ public CompletableFuture>> getCampuses( .thenApply(mapResult(records -> records.toMap(Campus::getId))); } + private CompletableFuture>> loadInstitutionsForLocations( + Result> multipleRecordsResult) { + + log.debug("loadInstitutionsForLocations:: parameters multipleRecordsResult: {}", + () -> resultAsString(multipleRecordsResult)); + + return multipleRecordsResult.combineAfter( + locations -> getInstitutions(locations.getRecords()), (locations, institutions) -> + locations.mapRecords(location -> location.withInstitution( + institutions.getOrDefault(location.getInstitutionId(), Institution.unknown(location.getInstitutionId()))))); + + } + public CompletableFuture>> getInstitutions( Collection locations) { diff --git a/src/main/java/org/folio/circulation/services/ItemForTlrService.java b/src/main/java/org/folio/circulation/services/ItemForTlrService.java index 3a306991a7..49e19e6ff9 100644 --- a/src/main/java/org/folio/circulation/services/ItemForTlrService.java +++ b/src/main/java/org/folio/circulation/services/ItemForTlrService.java @@ -4,6 +4,7 @@ import static java.util.stream.Collectors.toList; import static java.util.stream.Collectors.toSet; import static org.folio.circulation.domain.RequestType.PAGE; +import static org.folio.circulation.domain.representations.RequestProperties.ITEM_LOCATION_CODE; import static org.folio.circulation.domain.representations.RequestProperties.INSTANCE_ID; import static org.folio.circulation.support.ValidationErrorFailure.failedValidation; import static org.folio.circulation.support.results.Result.of; @@ -69,7 +70,21 @@ private Result> refusePageRequestWhenNoAvailablePageableItemsExist(Re return failedValidation(message, INSTANCE_ID, request.getInstanceId()); } - return of(() -> availablePageableItems); + if (request.geItemLocationCode() == null) { + return of(() -> availablePageableItems); + } else { + List finalAvailablePageableItems = availablePageableItems.stream() + .filter(item -> item.isAtLocation(request.geItemLocationCode())) + .toList(); + if (finalAvailablePageableItems.isEmpty()) { + String message = "Cannot create page TLR for this instance ID - no pageable available " + + "items found in requested location"; + log.info("{}. Instance ID: {}, Requested location code {}", + message, request.getInstanceId(), request.geItemLocationCode()); + return failedValidation(message, ITEM_LOCATION_CODE, request.geItemLocationCode()); + } + return of(() -> finalAvailablePageableItems); + } } private static Item pickClosestItem(Collection requestedLocations, List availableItems) { diff --git a/src/main/java/org/folio/circulation/storage/mappers/CampusMapper.java b/src/main/java/org/folio/circulation/storage/mappers/CampusMapper.java index 12f44d5cf5..42ef65d610 100644 --- a/src/main/java/org/folio/circulation/storage/mappers/CampusMapper.java +++ b/src/main/java/org/folio/circulation/storage/mappers/CampusMapper.java @@ -9,6 +9,7 @@ public class CampusMapper { public Campus toDomain(JsonObject representation) { return new Campus(getProperty(representation, "id"), - getProperty(representation, "name")); + getProperty(representation, "name"), + getProperty(representation, "code")); } } diff --git a/src/main/java/org/folio/circulation/storage/mappers/InstitutionMapper.java b/src/main/java/org/folio/circulation/storage/mappers/InstitutionMapper.java index 061a5d1c4a..899d14fa9d 100644 --- a/src/main/java/org/folio/circulation/storage/mappers/InstitutionMapper.java +++ b/src/main/java/org/folio/circulation/storage/mappers/InstitutionMapper.java @@ -9,6 +9,7 @@ public class InstitutionMapper { public Institution toDomain(JsonObject representation) { return new Institution(getProperty(representation, "id"), - getProperty(representation, "name")); + getProperty(representation, "name"), + getProperty(representation, "code")); } } diff --git a/src/main/java/org/folio/circulation/storage/mappers/LibraryMapper.java b/src/main/java/org/folio/circulation/storage/mappers/LibraryMapper.java index ceb319c3d3..f509d1dff6 100644 --- a/src/main/java/org/folio/circulation/storage/mappers/LibraryMapper.java +++ b/src/main/java/org/folio/circulation/storage/mappers/LibraryMapper.java @@ -9,6 +9,7 @@ public class LibraryMapper { public Library toDomain(JsonObject representation) { return new Library(getProperty(representation, "id"), - getProperty(representation, "name")); + getProperty(representation, "name"), + getProperty(representation, "code")); } } diff --git a/src/test/java/api/requests/RequestsAPICreationTests.java b/src/test/java/api/requests/RequestsAPICreationTests.java index 7b08979d85..b463e766a6 100644 --- a/src/test/java/api/requests/RequestsAPICreationTests.java +++ b/src/test/java/api/requests/RequestsAPICreationTests.java @@ -631,6 +631,64 @@ void canCreateTitleLevelRequestWhenTlrEnabled() { assertThat(publishedEvents.filterToList(byEventType("LOAN_DUE_DATE_CHANGED")), hasSize(0)); } + @ParameterizedTest + @CsvSource(value = { + "NU/JC/DL/3F", + "DLRC", + "JC", + "NU" + }) + void createTitleLevelRequestWhenTlrEnabledSetLocation(String locationCode) { + UUID patronId = usersFixture.charlotte().getId(); + final UUID pickupServicePointId = servicePointsFixture.cd1().getId(); + + final var items = itemsFixture.createMultipleItemsForTheSameInstance(2); + UUID instanceId = items.get(0).getInstanceId(); + + configurationsFixture.enableTlrFeature(); + + IndividualResource requestResource = requestsClient.create(new RequestBuilder() + .page() + .withNoHoldingsRecordId() + .withNoItemId() + .titleRequestLevel() + .withInstanceId(instanceId) + .withPickupServicePointId(pickupServicePointId) + .withRequesterId(patronId) + .withItemLocationCode(locationCode)); + + JsonObject request = requestResource.getJson(); + assertThat(request.getString("requestLevel"), is("Title")); + } + + @Test + void createTitleLevelRequestWhenTlrEnabledSetLocationNoItems() { + UUID patronId = usersFixture.charlotte().getId(); + final UUID pickupServicePointId = servicePointsFixture.cd1().getId(); + + final var items = itemsFixture.createMultipleItemsForTheSameInstance(2); + UUID instanceId = items.get(0).getInstanceId(); + + configurationsFixture.enableTlrFeature(); + + Response response = requestsClient.attemptCreate( + new RequestBuilder() + .page() + .withNoHoldingsRecordId() + .withNoItemId() + .titleRequestLevel() + .withInstanceId(instanceId) + .withPickupServicePointId(pickupServicePointId) + .withRequesterId(patronId) + .withItemLocationCode("DoesNotExist") + .create()); + + assertThat(response.getStatusCode(), is(422)); + assertThat(response.getJson(), hasErrorWith( + hasMessage("Cannot create page TLR for this instance ID - no pageable " + + "available items found in requested location"))); + } + @Test void cannotCreateRequestWithNonExistentRequestLevelWhenTlrEnabled() { UUID patronId = usersFixture.charlotte().getId(); diff --git a/src/test/java/api/support/builders/RequestBuilder.java b/src/test/java/api/support/builders/RequestBuilder.java index 180de0a22b..c9eea6fa95 100644 --- a/src/test/java/api/support/builders/RequestBuilder.java +++ b/src/test/java/api/support/builders/RequestBuilder.java @@ -3,6 +3,7 @@ import static api.support.utl.DateTimeUtils.getLocalDatePropertyForDateWithTime; import static java.time.ZoneOffset.UTC; import static java.util.stream.Collectors.toList; +import static org.folio.circulation.domain.representations.RequestProperties.ITEM_LOCATION_CODE; import static org.folio.circulation.support.json.JsonPropertyFetcher.getDateTimeProperty; import static org.folio.circulation.support.json.JsonPropertyFetcher.getIntegerProperty; import static org.folio.circulation.support.json.JsonPropertyFetcher.getLocalDateProperty; @@ -63,6 +64,7 @@ public class RequestBuilder extends JsonBuilder implements Builder { private final Tags tags; private final String patronComments; private final BlockOverrides blockOverrides; + private final String itemLocationCode; private final PrintDetails printDetails; public RequestBuilder() { @@ -91,6 +93,7 @@ public RequestBuilder() { null, null, null, + null, null); } @@ -123,6 +126,7 @@ public static RequestBuilder from(IndividualResource response) { new Tags((toStream(representation.getJsonObject("tags"), "tagList").collect(toList()))), getProperty(representation, "patronComments"), null, + getProperty(representation, ITEM_LOCATION_CODE), PrintDetails.fromRepresentation(representation) ); } @@ -152,6 +156,7 @@ public JsonObject create() { put(request, "cancelledDate", formatDateTimeOptional(cancelledDate)); put(request, "pickupServicePointId", this.pickupServicePointId); put(request, "patronComments", this.patronComments); + put(request, ITEM_LOCATION_CODE, this.itemLocationCode); if (itemSummary != null) { final JsonObject itemRepresentation = new JsonObject(); @@ -340,12 +345,12 @@ public static class PrintDetails { public static PrintDetails fromRepresentation(JsonObject representation) { JsonObject printDetails = representation.getJsonObject("printDetails"); if (printDetails != null) { - final Integer printCount = printDetails.getInteger("printCount"); - final String requesterId = printDetails.getString("requesterId"); - final Boolean isPrinted = printDetails.getBoolean("isPrinted"); - final String printEventDate = printDetails.getString("printEventDate"); - return new PrintDetails(printCount, requesterId, isPrinted, - printEventDate); + final Integer printCount = printDetails.getInteger("printCount"); + final String requesterId = printDetails.getString("requesterId"); + final Boolean isPrinted = printDetails.getBoolean("isPrinted"); + final String printEventDate = printDetails.getString("printEventDate"); + return new PrintDetails(printCount, requesterId, isPrinted, + printEventDate); } return null; } From d5a9b5931fc021ccf5f6097ff2c2b812b9096c89 Mon Sep 17 00:00:00 2001 From: Oleksandr Vidinieiev Date: Wed, 9 Oct 2024 14:05:51 +0300 Subject: [PATCH 03/18] CIRC-2153 Add support for interface instance-storage 11.0 --- descriptors/ModuleDescriptor-template.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/descriptors/ModuleDescriptor-template.json b/descriptors/ModuleDescriptor-template.json index 95fccc6e00..c0f310b59b 100644 --- a/descriptors/ModuleDescriptor-template.json +++ b/descriptors/ModuleDescriptor-template.json @@ -1214,7 +1214,7 @@ }, { "id": "instance-storage", - "version": "4.0 5.0 6.0 7.0 8.0 9.0 10.0" + "version": "4.0 5.0 6.0 7.0 8.0 9.0 10.0 11.0" }, { "id": "holdings-storage", From a1c0cff65ea24633ae7ccaaa0a47ab877fbe1522 Mon Sep 17 00:00:00 2001 From: Roman_Barannyk Date: Thu, 10 Oct 2024 14:14:48 +0300 Subject: [PATCH 04/18] CIRC-2134 add TLR Hold requests handling to the print slips logic --- descriptors/ModuleDescriptor-template.json | 4 + .../folio/circulation/domain/Holdings.java | 3 +- .../domain/notice/TemplateContextUtil.java | 2 + .../storage/inventory/HoldingsRepository.java | 13 ++ .../storage/inventory/InstanceRepository.java | 18 +- .../storage/users/UserRepository.java | 11 +- .../circulation/resources/SlipsResource.java | 163 +++++++++++++++--- .../resources/context/SearchSlipsContext.java | 32 ++++ .../storage/mappers/HoldingsMapper.java | 3 +- .../java/api/requests/StaffSlipsTests.java | 64 +++++++ .../api/support/builders/HoldingBuilder.java | 26 ++- .../domain/ItemPermanentLocationTest.java | 4 +- 12 files changed, 303 insertions(+), 40 deletions(-) create mode 100644 src/main/java/org/folio/circulation/resources/context/SearchSlipsContext.java diff --git a/descriptors/ModuleDescriptor-template.json b/descriptors/ModuleDescriptor-template.json index 95fccc6e00..5017a34777 100644 --- a/descriptors/ModuleDescriptor-template.json +++ b/descriptors/ModuleDescriptor-template.json @@ -2445,6 +2445,10 @@ "circulation.internal.fetch-items", "circulation-storage.requests.item.get", "circulation-storage.requests.collection.get", + "inventory-storage.holdings.item.get", + "inventory-storage.holdings.collection.get", + "inventory-storage.instances.item.get", + "inventory-storage.instances.collection.get", "users.item.get", "users.collection.get", "addresstypes.item.get", diff --git a/src/main/java/org/folio/circulation/domain/Holdings.java b/src/main/java/org/folio/circulation/domain/Holdings.java index 13a2753b37..8cf2dc22d4 100644 --- a/src/main/java/org/folio/circulation/domain/Holdings.java +++ b/src/main/java/org/folio/circulation/domain/Holdings.java @@ -5,7 +5,7 @@ @Value public class Holdings { public static Holdings unknown(String id) { - return new Holdings(id, null, null, null); + return new Holdings(id, null, null, null, null); } public static Holdings unknown() { @@ -16,4 +16,5 @@ public static Holdings unknown() { String instanceId; String copyNumber; String permanentLocationId; + String effectiveLocationId; } diff --git a/src/main/java/org/folio/circulation/domain/notice/TemplateContextUtil.java b/src/main/java/org/folio/circulation/domain/notice/TemplateContextUtil.java index 948a695327..5e66ecc48c 100644 --- a/src/main/java/org/folio/circulation/domain/notice/TemplateContextUtil.java +++ b/src/main/java/org/folio/circulation/domain/notice/TemplateContextUtil.java @@ -10,6 +10,7 @@ import java.util.ArrayList; import java.util.Collection; import java.util.Locale; +import java.util.Objects; import java.util.Optional; import java.util.stream.Stream; @@ -139,6 +140,7 @@ public static JsonObject addPrimaryServicePointNameToStaffSlipContext(JsonObject .stream() .map(JsonObject.class::cast) .map(pickSlip -> pickSlip.getJsonObject(ITEM)) + .filter(Objects::nonNull) .forEach(item -> item.put("effectiveLocationPrimaryServicePointName", primaryServicePoint.getName())); log.debug("addPrimaryServicePointNameToStaffSlipContext:: Result entries: {}, " + diff --git a/src/main/java/org/folio/circulation/infrastructure/storage/inventory/HoldingsRepository.java b/src/main/java/org/folio/circulation/infrastructure/storage/inventory/HoldingsRepository.java index 22ce77055a..f747d17c13 100644 --- a/src/main/java/org/folio/circulation/infrastructure/storage/inventory/HoldingsRepository.java +++ b/src/main/java/org/folio/circulation/infrastructure/storage/inventory/HoldingsRepository.java @@ -4,6 +4,7 @@ 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.http.client.CqlQuery.exactMatchAny; import static org.folio.circulation.support.results.ResultBinding.mapResult; import java.util.Collection; @@ -48,6 +49,18 @@ CompletableFuture>> fetchByInstanceId(String in return holdingsRecordFetcher.findByQuery(exactMatch("instanceId", instanceId)); } + public CompletableFuture>> fetchByInstances( + Collection instanceIds) { + + final var mapper = new HoldingsMapper(); + + final var holdingsRecordFetcher = findWithCqlQuery( + holdingsClient, HOLDINGS_RECORDS, mapper::toDomain); + + return holdingsRecordFetcher.findByQuery(exactMatchAny("instanceId", + instanceIds)); + } + CompletableFuture>> fetchByIds( Collection holdingsRecordIds) { diff --git a/src/main/java/org/folio/circulation/infrastructure/storage/inventory/InstanceRepository.java b/src/main/java/org/folio/circulation/infrastructure/storage/inventory/InstanceRepository.java index 0c3f44ebb3..d11a1f8676 100644 --- a/src/main/java/org/folio/circulation/infrastructure/storage/inventory/InstanceRepository.java +++ b/src/main/java/org/folio/circulation/infrastructure/storage/inventory/InstanceRepository.java @@ -49,6 +49,17 @@ public CompletableFuture> fetchById(String instanceId) { .thenApply(r -> r.map(mapper::toDomain)); } + public CompletableFuture>> fetchByRequests( + MultipleRecords multipleRequests) { + + log.debug("fetchByRequests:: parameters multipleRequests: {}", + () -> multipleRecordsAsString(multipleRequests)); + + return fetchByIds(multipleRequests.getRecords().stream() + .map(Request::getInstanceId) + .toList()); + } + public CompletableFuture>> fetchByIds( Collection instanceIds) { @@ -61,8 +72,11 @@ public CompletableFuture>> fetchByIds( } - public CompletableFuture>> findInstancesForRequests(MultipleRecords multipleRequests) { - log.debug("findInstancesForRequests:: parameters multipleRequests: {}", () -> multipleRecordsAsString(multipleRequests)); + public CompletableFuture>> findInstancesForRequests( + MultipleRecords multipleRequests) { + + log.debug("findInstancesForRequests:: parameters multipleRequests: {}", + () -> multipleRecordsAsString(multipleRequests)); Collection requests = multipleRequests.getRecords(); final List instanceIdsToFetch = requests.stream() diff --git a/src/main/java/org/folio/circulation/infrastructure/storage/users/UserRepository.java b/src/main/java/org/folio/circulation/infrastructure/storage/users/UserRepository.java index 190d39e91d..404bce7e7d 100644 --- a/src/main/java/org/folio/circulation/infrastructure/storage/users/UserRepository.java +++ b/src/main/java/org/folio/circulation/infrastructure/storage/users/UserRepository.java @@ -2,9 +2,9 @@ import static java.util.Objects.isNull; import static java.util.concurrent.CompletableFuture.completedFuture; +import static org.folio.circulation.support.ErrorCode.USER_BARCODE_NOT_FOUND; import static org.folio.circulation.support.ValidationErrorFailure.failedValidation; import static org.folio.circulation.support.fetching.RecordFetching.findWithMultipleCqlIndexValues; -import static org.folio.circulation.support.ErrorCode.USER_BARCODE_NOT_FOUND; import static org.folio.circulation.support.results.Result.of; import static org.folio.circulation.support.results.Result.ofAsync; import static org.folio.circulation.support.results.Result.succeeded; @@ -23,15 +23,14 @@ import java.util.concurrent.CompletableFuture; import java.util.stream.Collectors; -import io.vertx.core.json.JsonObject; +import org.apache.commons.lang3.StringUtils; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import org.folio.circulation.domain.Loan; import org.folio.circulation.domain.MultipleRecords; import org.folio.circulation.domain.Request; import org.folio.circulation.domain.User; import org.folio.circulation.domain.UserRelatedRecord; -import org.apache.commons.lang3.StringUtils; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; import org.folio.circulation.support.Clients; import org.folio.circulation.support.CollectionResourceClient; import org.folio.circulation.support.FetchSingleRecord; @@ -41,6 +40,8 @@ import org.folio.circulation.support.http.client.Response; import org.folio.circulation.support.results.Result; +import io.vertx.core.json.JsonObject; + public class UserRepository { private static final Logger log = LogManager.getLogger(MethodHandles.lookup().lookupClass()); private static final String USERS_RECORD_PROPERTY = "users"; diff --git a/src/main/java/org/folio/circulation/resources/SlipsResource.java b/src/main/java/org/folio/circulation/resources/SlipsResource.java index 6b6d3c926c..31c4ba36ea 100644 --- a/src/main/java/org/folio/circulation/resources/SlipsResource.java +++ b/src/main/java/org/folio/circulation/resources/SlipsResource.java @@ -16,16 +16,21 @@ import static org.folio.circulation.support.utils.LogUtil.multipleRecordsAsString; import java.lang.invoke.MethodHandles; +import java.util.ArrayList; import java.util.Collection; +import java.util.HashMap; 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; import org.apache.logging.log4j.Logger; +import org.folio.circulation.domain.Holdings; +import org.folio.circulation.domain.Instance; import org.folio.circulation.domain.Item; import org.folio.circulation.domain.ItemStatus; import org.folio.circulation.domain.Location; @@ -36,12 +41,15 @@ import org.folio.circulation.domain.ServicePoint; import org.folio.circulation.domain.notice.TemplateContextUtil; import org.folio.circulation.infrastructure.storage.ServicePointRepository; +import org.folio.circulation.infrastructure.storage.inventory.HoldingsRepository; +import org.folio.circulation.infrastructure.storage.inventory.InstanceRepository; 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.resources.context.SearchSlipsContext; import org.folio.circulation.storage.mappers.LocationMapper; import org.folio.circulation.support.Clients; import org.folio.circulation.support.RouteRegistration; @@ -110,38 +118,112 @@ private void getMany(RoutingContext routingContext) { final UUID servicePointId = UUID.fromString( routingContext.request().getParam(SERVICE_POINT_ID_PARAM)); - fetchLocationsForServicePoint(servicePointId, clients) - .thenComposeAsync(r -> r.after(locations -> fetchItemsForLocations(locations, - itemRepository, LocationRepository.using(clients, servicePointRepository)))) - .thenComposeAsync(r -> r.after(items -> fetchRequests(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(this::mapResultToJson)) - .thenComposeAsync(r -> r.combineAfter(() -> servicePointRepository.getServicePointById(servicePointId), - this::addPrimaryServicePointNameToStaffSlipContext)) - .thenApply(r -> r.map(JsonHttpResponse::ok)) - .thenAccept(context::writeResultToHttpResponse); + fetchLocationsForServicePoint(servicePointId, clients) + .thenComposeAsync(r -> r.after(ctx -> fetchItemsForLocations(ctx, + itemRepository, LocationRepository.using(clients, servicePointRepository)))) + .thenComposeAsync(r -> r.after(ctx -> fetchRequests(ctx, clients))) + .thenComposeAsync(r -> r.after(ctx -> fetchTlrRequests(ctx, clients))) + .thenComposeAsync(r -> r.after(ctx -> userRepository.findUsersForRequests( + ctx.getRequests()))) + .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(this::mapResultToJson)) + .thenComposeAsync(r -> r.combineAfter(() -> servicePointRepository.getServicePointById(servicePointId), + this::addPrimaryServicePointNameToStaffSlipContext)) + .thenApply(r -> r.map(JsonHttpResponse::ok)) + .thenAccept(context::writeResultToHttpResponse); } - private CompletableFuture>> fetchLocationsForServicePoint( + private CompletableFuture> fetchTlrRequests( + SearchSlipsContext searchSlipsContext, Clients clients) { + + final var instanceRepository = new InstanceRepository(clients); + final var holdingsRepository = new HoldingsRepository(clients.holdingsStorage()); + + return fetchTlrRequests(clients, searchSlipsContext) + .thenComposeAsync(r -> r.combineAfter(ctx -> instanceRepository.fetchByRequests( + ctx.getTlrRequests()), SearchSlipsContext::withInstances)) + .thenApply(r -> r.next(this::mapRequestsToInstances)) + .thenComposeAsync(r -> r.combineAfter(ctx -> holdingsRepository.fetchByInstances( + ctx.getRequestToInstanceIdMap().values()), SearchSlipsContext::withHoldings)) + .thenApply(r -> r.next(this::mapRequestsToHoldings)) + .thenApply(r -> r.next(this::includeTlrRequests)); + } + + private Result mapRequestsToInstances(SearchSlipsContext context) { + Set fetchedInstanceIds = context.getInstances().getRecords().stream() + .map(Instance::getId) + .collect(Collectors.toSet()); + + Map requestToInstanceIdMap = new HashMap<>(); + for (Request request : context.getTlrRequests().getRecords()) { + String instanceId = request.getInstanceId(); + if (fetchedInstanceIds.contains(instanceId)) { + requestToInstanceIdMap.put(request, instanceId); + } + } + + return succeeded(context.withRequestToInstanceIdMap(requestToInstanceIdMap)); + } + + private Result mapRequestsToHoldings(SearchSlipsContext context) { + Map instanceIdToHoldingsMap = new HashMap<>(); + for (Holdings holding : context.getHoldings().getRecords()) { + instanceIdToHoldingsMap.put(holding.getInstanceId(), holding); + } + + Map requestToHoldingsMap = new HashMap<>(); + Map requestToInstanceIdMap = context.getRequestToInstanceIdMap(); + for (Request request : requestToInstanceIdMap.keySet()) { + String instanceId = requestToInstanceIdMap.get(request); + if (instanceId != null && instanceIdToHoldingsMap.containsKey(instanceId)) { + requestToHoldingsMap.put(request, instanceIdToHoldingsMap.get(instanceId)); + } + } + + return succeeded(context.withRequestToHoldingMap(requestToHoldingsMap)); + } + + private Result includeTlrRequests(SearchSlipsContext ctx) { + Set locationIds = ctx.getLocations().getRecords().stream() + .map(Location::getId) + .collect(Collectors.toSet()); + + List requestsToAdd = new ArrayList<>(); + for (Map.Entry entry : ctx.getRequestToHoldingMap().entrySet()) { + Holdings holding = entry.getValue(); + Request request = entry.getKey(); + if (locationIds.contains(holding.getEffectiveLocationId())) { + requestsToAdd.add(request); + } + } + + List updatedRequests = new ArrayList<>(ctx.getRequests().getRecords()); + updatedRequests.addAll(requestsToAdd); + + return succeeded(ctx.withRequests(new MultipleRecords<>(updatedRequests, + updatedRequests.size()))); + } + + 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); + .findByQuery(exactMatch(PRIMARY_SERVICE_POINT_KEY, servicePointId.toString()), LOCATIONS_LIMIT) + .thenApply(r -> r.map(locations -> new SearchSlipsContext().withLocations(locations))); } - private CompletableFuture>> fetchItemsForLocations( - MultipleRecords multipleLocations, + private CompletableFuture> fetchItemsForLocations( + SearchSlipsContext context, ItemRepository itemRepository, LocationRepository locationRepository) { - log.debug("fetchPagedItemsForLocations:: parameters multipleLocations: {}", - () -> multipleRecordsAsString(multipleLocations)); - Collection locations = multipleLocations.getRecords(); + log.debug("fetchPagedItemsForLocations:: multipleLocations: {}", + () -> multipleRecordsAsString(context.getLocations())); + Collection locations = context.getLocations().getRecords(); Set locationIds = locations.stream() .map(Location::getId) .filter(StringUtils::isNoneBlank) @@ -150,7 +232,7 @@ private CompletableFuture>> fetchItemsForLocations( if (locationIds.isEmpty()) { log.info("fetchPagedItemsForLocations:: locationIds is empty"); - return completedFuture(succeeded(emptyList())); + return completedFuture(succeeded(context)); } List itemStatusValues = itemStatuses.stream() @@ -160,13 +242,15 @@ private CompletableFuture>> fetchItemsForLocations( return itemRepository.findByIndexNameAndQuery(locationIds, EFFECTIVE_LOCATION_ID_KEY, statusQuery) .thenComposeAsync(r -> r.after(items -> fetchLocationDetailsForItems(items, locations, - locationRepository))); + locationRepository))) + .thenApply(r -> r.map(context::withItems)); } - private CompletableFuture>> fetchRequests( - Collection items, Clients clients) { + private CompletableFuture> fetchRequests( + SearchSlipsContext context, Clients clients) { - Set itemIds = items.stream() + Collection items = context.getItems(); + Set itemIds = context.getItems().stream() .map(Item::getItemId) .filter(StringUtils::isNoneBlank) .collect(toSet()); @@ -174,7 +258,7 @@ private CompletableFuture>> fetchRequests( if (itemIds.isEmpty()) { log.info("fetchOpenPageRequestsForItems:: itemIds is empty"); - return completedFuture(succeeded(MultipleRecords.empty())); + return completedFuture(succeeded(context)); } final Result typeQuery = exactMatch(REQUEST_TYPE_KEY, requestType.getValue()); @@ -183,7 +267,30 @@ private CompletableFuture>> fetchRequests( return findWithMultipleCqlIndexValues(clients.requestsStorage(), REQUESTS_KEY, Request::from) .find(byIndex(ITEM_ID_KEY, itemIds).withQuery(statusAndTypeQuery)) - .thenApply(flatMapResult(requests -> matchItemsToRequests(requests, items))); + .thenApply(flatMapResult(requests -> matchItemsToRequests(requests, items))) + .thenApply(r -> r.map(context::withRequests)); + } + + private CompletableFuture> fetchTlrRequests( + Clients clients, SearchSlipsContext context) { + + final Result typeQuery = exactMatch(REQUEST_TYPE_KEY, requestType.getValue()); + final Result statusQuery = exactMatch(STATUS_KEY, RequestStatus.OPEN_NOT_YET_FILLED.getValue()); + final Result statusAndTypeQuery = typeQuery.combine(statusQuery, CqlQuery::and); + + return findWithCqlQuery( + clients.requestsStorage(), REQUESTS_KEY, Request::from) + .findByQuery(statusAndTypeQuery) + .thenApply(r -> r.next(this::filterRequests)) + .thenApply(r -> r.next(tlrRequests -> succeeded(context.withTlrRequests(tlrRequests)))); + } + + private Result> filterRequests(MultipleRecords requests) { + var filteredRequests = requests.getRecords().stream() + .filter(request -> request.getItemId() == null) + .toList(); + + return succeeded(new MultipleRecords<>(filteredRequests, filteredRequests.size())); } private CompletableFuture>> fetchLocationDetailsForItems( diff --git a/src/main/java/org/folio/circulation/resources/context/SearchSlipsContext.java b/src/main/java/org/folio/circulation/resources/context/SearchSlipsContext.java new file mode 100644 index 0000000000..eec7d91e20 --- /dev/null +++ b/src/main/java/org/folio/circulation/resources/context/SearchSlipsContext.java @@ -0,0 +1,32 @@ +package org.folio.circulation.resources.context; + +import java.util.Collection; +import java.util.Map; + +import org.folio.circulation.domain.Holdings; +import org.folio.circulation.domain.Instance; +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 lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.With; + +@NoArgsConstructor +@AllArgsConstructor +@Getter +@With +public class SearchSlipsContext { + private MultipleRecords locations; + private MultipleRecords requests; + private MultipleRecords tlrRequests; + private MultipleRecords instances; + private MultipleRecords holdings; + private Collection items; + private Map requestToInstanceIdMap; + private Map requestToHoldingMap; + +} diff --git a/src/main/java/org/folio/circulation/storage/mappers/HoldingsMapper.java b/src/main/java/org/folio/circulation/storage/mappers/HoldingsMapper.java index 5231ca48b3..36ac5ee376 100644 --- a/src/main/java/org/folio/circulation/storage/mappers/HoldingsMapper.java +++ b/src/main/java/org/folio/circulation/storage/mappers/HoldingsMapper.java @@ -11,6 +11,7 @@ public Holdings toDomain(JsonObject holdingsRepresentation) { return new Holdings(getProperty(holdingsRepresentation, "id"), getProperty(holdingsRepresentation, "instanceId"), getProperty(holdingsRepresentation, "copyNumber"), - getProperty(holdingsRepresentation, "permanentLocationId")); + getProperty(holdingsRepresentation, "permanentLocationId"), + getProperty(holdingsRepresentation, "effectiveLocationId")); } } diff --git a/src/test/java/api/requests/StaffSlipsTests.java b/src/test/java/api/requests/StaffSlipsTests.java index 083101558f..1ad4783bf7 100644 --- a/src/test/java/api/requests/StaffSlipsTests.java +++ b/src/test/java/api/requests/StaffSlipsTests.java @@ -11,6 +11,7 @@ import static org.folio.circulation.support.json.JsonPropertyFetcher.getNestedStringProperty; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.hasSize; import static org.hamcrest.collection.ArrayMatching.arrayContainingInAnyOrder; import static org.hamcrest.core.Is.is; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -543,6 +544,34 @@ void responseContainsSlipsWhenServicePointHasManyLocations(SlipsType slipsType) assertResponseContains(response, slipsType, item, pageRequest, james); } + @Test + void responseContainsSearchSlipsForTLRRequestsOfTypeHoldOnly() { + configurationsFixture.enableTlrFeature(); + var servicePointId = servicePointsFixture.cd1().getId(); + var steve = usersFixture.steve(); + var instance = instancesFixture.basedUponDunkirk(); + var location = locationsFixture.mainFloor(); + ItemResource item = buildItem(instance.getId(), location); + checkOutFixture.checkOutByBarcode(item); + var holdRequestBuilder = new RequestBuilder() + .withStatus(RequestStatus.OPEN_NOT_YET_FILLED.getValue()) + .hold() + .titleRequestLevel() + .withNoItemId() + .withNoHoldingsRecordId() + .withPickupServicePointId(servicePointId) + .withInstanceId(instance.getId()) + .by(steve); + + var holdRequest = requestsClient.create(holdRequestBuilder); + assertThat(requestsClient.getAll(), hasSize(1)); + + Response response = SlipsType.SEARCH_SLIPS.get(servicePointId); + assertThat(response.getStatusCode(), is(HTTP_OK)); + assertResponseHasItems(response, 1, SlipsType.SEARCH_SLIPS); + assertResponseContains(response, SlipsType.SEARCH_SLIPS, holdRequest, steve); + } + private void assertDatetimeEquivalent(ZonedDateTime firstDateTime, ZonedDateTime secondDateTime) { assertThat(firstDateTime.compareTo(secondDateTime), is(0)); } @@ -577,6 +606,28 @@ private void assertResponseContains(Response response, SlipsType slipsType, Item } } + private void assertResponseContains(Response response, SlipsType slipsType, + IndividualResource request, UserResource requester) { + + long count = getSlipsStream(response, slipsType) + .filter(ps -> + request.getId().toString().equals( + getNestedStringProperty(ps, REQUEST_KEY, "requestID")) + && requester.getBarcode().equals( + getNestedStringProperty(ps, REQUESTER_KEY, "barcode"))) + .count(); + + if (count == 0) { + fail("Response does not contain a pick slip with expected combination" + + " of item, request and requester"); + } + + if (count > 1) { + fail("Response contains multiple pick slips with expected combination" + + " of item, request and requester: " + count); + } + } + private Stream getSlipsStream(Response response, SlipsType slipsType) { return JsonObjectArrayPropertyFetcher.toStream(response.getJson(), slipsType.getCollectionName()); } @@ -590,6 +641,19 @@ private String getName(JsonObject jsonObject) { return jsonObject.getString("name"); } + private ItemResource buildItem(UUID instanceId, IndividualResource location) { + UUID isbnIdentifierId = identifierTypesFixture.isbn().getId(); + + return itemsFixture.basedUponSmallAngryPlanet( + holdingBuilder -> holdingBuilder.forInstance(instanceId) + .withEffectiveLocationId(location.getId()), + instanceBuilder -> instanceBuilder + .addIdentifier(isbnIdentifierId, "9780866989732") + .withId(instanceId), + itemBuilder -> itemBuilder.withBarcode("test") + .withMaterialType(materialTypesFixture.book().getId())); + } + @AllArgsConstructor private enum SlipsType { PICK_SLIPS(ResourceClient.forPickSlips(), "pickSlips", PAGE), diff --git a/src/test/java/api/support/builders/HoldingBuilder.java b/src/test/java/api/support/builders/HoldingBuilder.java index 3de008cdf4..12a534401c 100644 --- a/src/test/java/api/support/builders/HoldingBuilder.java +++ b/src/test/java/api/support/builders/HoldingBuilder.java @@ -11,19 +11,21 @@ public class HoldingBuilder extends JsonBuilder implements Builder { private final UUID instanceId; private final UUID permanentLocationId; private final UUID temporaryLocationId; + private final UUID effectiveLocationId; private final String callNumber; private final String callNumberPrefix; private final String callNumberSuffix; private final String copyNumber; public HoldingBuilder() { - this(null, null, null, null, null, null, null); + this(null, null, null, null, null, null, null, null); } private HoldingBuilder( UUID instanceId, UUID permanentLocationId, UUID temporaryLocationId, + UUID effectiveLocationId, String callNumber, String callNumberPrefix, String callNumberSuffix, @@ -32,6 +34,7 @@ private HoldingBuilder( this.instanceId = instanceId; this.permanentLocationId = permanentLocationId; this.temporaryLocationId = temporaryLocationId; + this.effectiveLocationId = effectiveLocationId; this.callNumber = callNumber; this.callNumberPrefix = callNumberPrefix; this.callNumberSuffix = callNumberSuffix; @@ -46,6 +49,7 @@ public JsonObject create() { put(holdings, "_version", 5); put(holdings, "permanentLocationId", permanentLocationId); put(holdings, "temporaryLocationId", temporaryLocationId); + put(holdings, "effectiveLocationId", effectiveLocationId); put(holdings, "callNumber", callNumber); put(holdings, "callNumberPrefix", callNumberPrefix); put(holdings, "callNumberSuffix", callNumberSuffix); @@ -59,6 +63,7 @@ public HoldingBuilder forInstance(UUID instanceId) { instanceId, this.permanentLocationId, this.temporaryLocationId, + this.effectiveLocationId, this.callNumber, this.callNumberPrefix, this.callNumberSuffix, @@ -75,6 +80,7 @@ public HoldingBuilder withPermanentLocation(UUID locationId) { this.instanceId, locationId, this.temporaryLocationId, + this.effectiveLocationId, this.callNumber, this.callNumberPrefix, this.callNumberSuffix, @@ -95,6 +101,7 @@ public HoldingBuilder withTemporaryLocation(UUID locationId) { this.instanceId, this.permanentLocationId, locationId, + this.effectiveLocationId, this.callNumber, this.callNumberPrefix, this.callNumberSuffix, @@ -106,11 +113,25 @@ public HoldingBuilder withNoTemporaryLocation() { return withTemporaryLocation((UUID)null); } + public HoldingBuilder withEffectiveLocationId(UUID effectiveLocationId) { + return new HoldingBuilder( + this.instanceId, + this.permanentLocationId, + this.temporaryLocationId, + effectiveLocationId, + this.callNumber, + this.callNumberPrefix, + this.callNumberSuffix, + this.copyNumber + ); + } + public HoldingBuilder withCallNumber(String callNumber) { return new HoldingBuilder( this.instanceId, this.permanentLocationId, this.temporaryLocationId, + this.effectiveLocationId, callNumber, this.callNumberPrefix, this.callNumberSuffix, @@ -123,6 +144,7 @@ public HoldingBuilder withCallNumberPrefix(String callNumberPrefix) { this.instanceId, this.permanentLocationId, this.temporaryLocationId, + this.effectiveLocationId, this.callNumber, callNumberPrefix, this.callNumberSuffix, @@ -135,6 +157,7 @@ public HoldingBuilder withCallNumberSuffix(String callNumberSuffix) { this.instanceId, this.permanentLocationId, this.temporaryLocationId, + this.effectiveLocationId, this.callNumber, this.callNumberPrefix, callNumberSuffix, @@ -147,6 +170,7 @@ public HoldingBuilder withCopyNumbers(List copyNumbers) { this.instanceId, this.permanentLocationId, this.temporaryLocationId, + this.effectiveLocationId, this.callNumber, this.callNumberPrefix, this.callNumberSuffix, diff --git a/src/test/java/org/folio/circulation/domain/ItemPermanentLocationTest.java b/src/test/java/org/folio/circulation/domain/ItemPermanentLocationTest.java index 0c0bfa5998..1fb3ee4c8c 100644 --- a/src/test/java/org/folio/circulation/domain/ItemPermanentLocationTest.java +++ b/src/test/java/org/folio/circulation/domain/ItemPermanentLocationTest.java @@ -20,7 +20,7 @@ void itemLocationTakesPriorityOverHoldings() { val itemJson = new ItemBuilder().withPermanentLocation(itemLocation).create(); val item = Item.from(itemJson) - .withHoldings(new Holdings(null, null, null, holdingsLocation)); + .withHoldings(new Holdings(null, null, null, holdingsLocation, null)); assertThat(item.getPermanentLocationId(), is(itemLocation.toString())); } @@ -32,7 +32,7 @@ void holdingsLocationIsReturnedByDefault() { val itemJson = new ItemBuilder().withPermanentLocation((UUID) null).create(); val item = Item.from(itemJson) - .withHoldings(new Holdings(null, null, null, holdingsLocation)); + .withHoldings(new Holdings(null, null, null, holdingsLocation, null)); assertThat(item.getPermanentLocationId(), is(holdingsLocation.toString())); } From b4314374d664f437fb103ed66e7f3329c1fbbd82 Mon Sep 17 00:00:00 2001 From: Roman_Barannyk Date: Thu, 10 Oct 2024 14:18:29 +0300 Subject: [PATCH 05/18] CIRC-2104 remove empty line --- .../infrastructure/storage/inventory/HoldingsRepository.java | 1 - src/test/java/api/requests/StaffSlipsTests.java | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/main/java/org/folio/circulation/infrastructure/storage/inventory/HoldingsRepository.java b/src/main/java/org/folio/circulation/infrastructure/storage/inventory/HoldingsRepository.java index f747d17c13..07e4a913d8 100644 --- a/src/main/java/org/folio/circulation/infrastructure/storage/inventory/HoldingsRepository.java +++ b/src/main/java/org/folio/circulation/infrastructure/storage/inventory/HoldingsRepository.java @@ -53,7 +53,6 @@ public CompletableFuture>> fetchByInstances( Collection instanceIds) { final var mapper = new HoldingsMapper(); - final var holdingsRecordFetcher = findWithCqlQuery( holdingsClient, HOLDINGS_RECORDS, mapper::toDomain); diff --git a/src/test/java/api/requests/StaffSlipsTests.java b/src/test/java/api/requests/StaffSlipsTests.java index 1ad4783bf7..6ff87cd7aa 100644 --- a/src/test/java/api/requests/StaffSlipsTests.java +++ b/src/test/java/api/requests/StaffSlipsTests.java @@ -551,7 +551,7 @@ void responseContainsSearchSlipsForTLRRequestsOfTypeHoldOnly() { var steve = usersFixture.steve(); var instance = instancesFixture.basedUponDunkirk(); var location = locationsFixture.mainFloor(); - ItemResource item = buildItem(instance.getId(), location); + var item = buildItem(instance.getId(), location); checkOutFixture.checkOutByBarcode(item); var holdRequestBuilder = new RequestBuilder() .withStatus(RequestStatus.OPEN_NOT_YET_FILLED.getValue()) From e158b6cc9ee13c5b1ecbf9c57547abcd00fb16ff Mon Sep 17 00:00:00 2001 From: Roman_Barannyk Date: Thu, 10 Oct 2024 16:23:00 +0300 Subject: [PATCH 06/18] CIRC-2104 fix broken tests --- .../storage/inventory/InstanceRepository.java | 6 +- .../circulation/resources/SlipsResource.java | 106 +++++++++++++----- ...ipsContext.java => StuffSlipsContext.java} | 2 +- 3 files changed, 85 insertions(+), 29 deletions(-) rename src/main/java/org/folio/circulation/resources/context/{SearchSlipsContext.java => StuffSlipsContext.java} (96%) diff --git a/src/main/java/org/folio/circulation/infrastructure/storage/inventory/InstanceRepository.java b/src/main/java/org/folio/circulation/infrastructure/storage/inventory/InstanceRepository.java index d11a1f8676..24d3fc1ab9 100644 --- a/src/main/java/org/folio/circulation/infrastructure/storage/inventory/InstanceRepository.java +++ b/src/main/java/org/folio/circulation/infrastructure/storage/inventory/InstanceRepository.java @@ -50,12 +50,12 @@ public CompletableFuture> fetchById(String instanceId) { } public CompletableFuture>> fetchByRequests( - MultipleRecords multipleRequests) { + MultipleRecords requests) { log.debug("fetchByRequests:: parameters multipleRequests: {}", - () -> multipleRecordsAsString(multipleRequests)); + () -> multipleRecordsAsString(requests)); - return fetchByIds(multipleRequests.getRecords().stream() + return fetchByIds(requests.getRecords().stream() .map(Request::getInstanceId) .toList()); } diff --git a/src/main/java/org/folio/circulation/resources/SlipsResource.java b/src/main/java/org/folio/circulation/resources/SlipsResource.java index 31c4ba36ea..1bcb57c528 100644 --- a/src/main/java/org/folio/circulation/resources/SlipsResource.java +++ b/src/main/java/org/folio/circulation/resources/SlipsResource.java @@ -10,6 +10,7 @@ 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.http.client.CqlQuery.exactMatchAny; +import static org.folio.circulation.support.results.Result.ofAsync; 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; @@ -49,7 +50,7 @@ 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.resources.context.SearchSlipsContext; +import org.folio.circulation.resources.context.StuffSlipsContext; import org.folio.circulation.storage.mappers.LocationMapper; import org.folio.circulation.support.Clients; import org.folio.circulation.support.RouteRegistration; @@ -136,24 +137,29 @@ private void getMany(RoutingContext routingContext) { .thenAccept(context::writeResultToHttpResponse); } - private CompletableFuture> fetchTlrRequests( - SearchSlipsContext searchSlipsContext, Clients clients) { + private CompletableFuture> fetchTlrRequests( + StuffSlipsContext stuffSlipsContext, Clients clients) { final var instanceRepository = new InstanceRepository(clients); final var holdingsRepository = new HoldingsRepository(clients.holdingsStorage()); - return fetchTlrRequests(clients, searchSlipsContext) - .thenComposeAsync(r -> r.combineAfter(ctx -> instanceRepository.fetchByRequests( - ctx.getTlrRequests()), SearchSlipsContext::withInstances)) + return fetchTlrRequests(clients, stuffSlipsContext) + .thenComposeAsync(r -> r.after(ctx -> fetchByInstancesByRequests(ctx, instanceRepository))) .thenApply(r -> r.next(this::mapRequestsToInstances)) - .thenComposeAsync(r -> r.combineAfter(ctx -> holdingsRepository.fetchByInstances( - ctx.getRequestToInstanceIdMap().values()), SearchSlipsContext::withHoldings)) + .thenComposeAsync(r -> r.after(ctx -> fetchHoldingsByInstances(ctx, holdingsRepository))) .thenApply(r -> r.next(this::mapRequestsToHoldings)) .thenApply(r -> r.next(this::includeTlrRequests)); } - private Result mapRequestsToInstances(SearchSlipsContext context) { - Set fetchedInstanceIds = context.getInstances().getRecords().stream() + private Result mapRequestsToInstances(StuffSlipsContext context) { + MultipleRecords instances = context.getInstances(); + if (instances == null || instances.isEmpty()) { + log.info("mapRequestsToInstances:: no instances found"); + + return succeeded(context); + } + + Set fetchedInstanceIds = instances.getRecords().stream() .map(Instance::getId) .collect(Collectors.toSet()); @@ -168,14 +174,27 @@ private Result mapRequestsToInstances(SearchSlipsContext con return succeeded(context.withRequestToInstanceIdMap(requestToInstanceIdMap)); } - private Result mapRequestsToHoldings(SearchSlipsContext context) { + private Result mapRequestsToHoldings(StuffSlipsContext context) { + MultipleRecords holdings = context.getHoldings(); + if (holdings == null || holdings.isEmpty()) { + log.info("mapRequestsToHoldings:: holdings is empty"); + + return succeeded(context); + } + Map instanceIdToHoldingsMap = new HashMap<>(); - for (Holdings holding : context.getHoldings().getRecords()) { + for (Holdings holding : holdings.getRecords()) { instanceIdToHoldingsMap.put(holding.getInstanceId(), holding); } Map requestToHoldingsMap = new HashMap<>(); Map requestToInstanceIdMap = context.getRequestToInstanceIdMap(); + if (requestToInstanceIdMap == null || requestToInstanceIdMap.isEmpty()) { + log.info("mapRequestsToHoldings:: no requests matched to holdings"); + + return succeeded(context); + } + for (Request request : requestToInstanceIdMap.keySet()) { String instanceId = requestToInstanceIdMap.get(request); if (instanceId != null && instanceIdToHoldingsMap.containsKey(instanceId)) { @@ -186,11 +205,43 @@ private Result mapRequestsToHoldings(SearchSlipsContext cont return succeeded(context.withRequestToHoldingMap(requestToHoldingsMap)); } - private Result includeTlrRequests(SearchSlipsContext ctx) { + private CompletableFuture> fetchByInstancesByRequests( + StuffSlipsContext ctx, InstanceRepository instanceRepository) { + + var tlrRequests = ctx.getTlrRequests(); + if (tlrRequests == null || tlrRequests.isEmpty()) { + log.info("fetchByInstancesByRequests:: no TLR requests found"); + + return ofAsync(ctx); + } + + return instanceRepository.fetchByRequests(ctx.getTlrRequests()) + .thenApply(r -> r.map(ctx::withInstances)); + } + + private CompletableFuture> fetchHoldingsByInstances( + StuffSlipsContext ctx, HoldingsRepository holdingsRepository) { + + if (ctx.getRequestToInstanceIdMap() == null || ctx.getRequestToInstanceIdMap().isEmpty()) { + log.info("fetchHoldingsByInstances:: instances no requests matched to instances found"); + + return ofAsync(ctx); + } + + return holdingsRepository.fetchByInstances(ctx.getRequestToInstanceIdMap().values()) + .thenApply(r -> r.map(ctx::withHoldings)); + } + + private Result includeTlrRequests(StuffSlipsContext ctx) { + var requestToHoldingMap = ctx.getRequestToHoldingMap(); + if (requestToHoldingMap == null || requestToHoldingMap.isEmpty()){ + log.info("includeTlrRequests:: no tlr requests to include"); + + return succeeded(ctx); + } Set locationIds = ctx.getLocations().getRecords().stream() .map(Location::getId) .collect(Collectors.toSet()); - List requestsToAdd = new ArrayList<>(); for (Map.Entry entry : ctx.getRequestToHoldingMap().entrySet()) { Holdings holding = entry.getValue(); @@ -207,19 +258,19 @@ private Result includeTlrRequests(SearchSlipsContext ctx) { updatedRequests.size()))); } - private CompletableFuture> fetchLocationsForServicePoint( + 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) - .thenApply(r -> r.map(locations -> new SearchSlipsContext().withLocations(locations))); + .thenApply(r -> r.map(locations -> new StuffSlipsContext().withLocations(locations))); } - private CompletableFuture> fetchItemsForLocations( - SearchSlipsContext context, - ItemRepository itemRepository, LocationRepository locationRepository) { + private CompletableFuture> fetchItemsForLocations( + StuffSlipsContext context, ItemRepository itemRepository, + LocationRepository locationRepository) { log.debug("fetchPagedItemsForLocations:: multipleLocations: {}", () -> multipleRecordsAsString(context.getLocations())); @@ -246,11 +297,16 @@ private CompletableFuture> fetchItemsForLocations( .thenApply(r -> r.map(context::withItems)); } - private CompletableFuture> fetchRequests( - SearchSlipsContext context, Clients clients) { + private CompletableFuture> fetchRequests( + StuffSlipsContext context, Clients clients) { Collection items = context.getItems(); - Set itemIds = context.getItems().stream() + if (items == null || items.isEmpty()) { + log.info("fetchOpenPageRequestsForItems:: no items fetched"); + + return ofAsync(context.withRequests(MultipleRecords.empty())); + } + Set itemIds = items.stream() .map(Item::getItemId) .filter(StringUtils::isNoneBlank) .collect(toSet()); @@ -258,7 +314,7 @@ private CompletableFuture> fetchRequests( if (itemIds.isEmpty()) { log.info("fetchOpenPageRequestsForItems:: itemIds is empty"); - return completedFuture(succeeded(context)); + return ofAsync(context.withRequests(MultipleRecords.empty())); } final Result typeQuery = exactMatch(REQUEST_TYPE_KEY, requestType.getValue()); @@ -271,8 +327,8 @@ private CompletableFuture> fetchRequests( .thenApply(r -> r.map(context::withRequests)); } - private CompletableFuture> fetchTlrRequests( - Clients clients, SearchSlipsContext context) { + private CompletableFuture> fetchTlrRequests( + Clients clients, StuffSlipsContext context) { final Result typeQuery = exactMatch(REQUEST_TYPE_KEY, requestType.getValue()); final Result statusQuery = exactMatch(STATUS_KEY, RequestStatus.OPEN_NOT_YET_FILLED.getValue()); diff --git a/src/main/java/org/folio/circulation/resources/context/SearchSlipsContext.java b/src/main/java/org/folio/circulation/resources/context/StuffSlipsContext.java similarity index 96% rename from src/main/java/org/folio/circulation/resources/context/SearchSlipsContext.java rename to src/main/java/org/folio/circulation/resources/context/StuffSlipsContext.java index eec7d91e20..664dd27987 100644 --- a/src/main/java/org/folio/circulation/resources/context/SearchSlipsContext.java +++ b/src/main/java/org/folio/circulation/resources/context/StuffSlipsContext.java @@ -19,7 +19,7 @@ @AllArgsConstructor @Getter @With -public class SearchSlipsContext { +public class StuffSlipsContext { private MultipleRecords locations; private MultipleRecords requests; private MultipleRecords tlrRequests; From 81ed63b520d3e79816ba5d3824718de4064c82e5 Mon Sep 17 00:00:00 2001 From: Roman_Barannyk Date: Thu, 10 Oct 2024 16:50:29 +0300 Subject: [PATCH 07/18] CIRC-21034 fix code smell --- .../java/org/folio/circulation/resources/SlipsResource.java | 5 +++-- src/test/java/api/requests/StaffSlipsTests.java | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/folio/circulation/resources/SlipsResource.java b/src/main/java/org/folio/circulation/resources/SlipsResource.java index 1bcb57c528..18ba817d2e 100644 --- a/src/main/java/org/folio/circulation/resources/SlipsResource.java +++ b/src/main/java/org/folio/circulation/resources/SlipsResource.java @@ -195,8 +195,9 @@ private Result mapRequestsToHoldings(StuffSlipsContext contex return succeeded(context); } - for (Request request : requestToInstanceIdMap.keySet()) { - String instanceId = requestToInstanceIdMap.get(request); + for (Map.Entry entry : requestToInstanceIdMap.entrySet()) { + Request request = entry.getKey(); + String instanceId = entry.getValue(); if (instanceId != null && instanceIdToHoldingsMap.containsKey(instanceId)) { requestToHoldingsMap.put(request, instanceIdToHoldingsMap.get(instanceId)); } diff --git a/src/test/java/api/requests/StaffSlipsTests.java b/src/test/java/api/requests/StaffSlipsTests.java index 6ff87cd7aa..d4be9194cc 100644 --- a/src/test/java/api/requests/StaffSlipsTests.java +++ b/src/test/java/api/requests/StaffSlipsTests.java @@ -545,7 +545,7 @@ void responseContainsSlipsWhenServicePointHasManyLocations(SlipsType slipsType) } @Test - void responseContainsSearchSlipsForTLRRequestsOfTypeHoldOnly() { + void responseContainsSearchSlipsForTLR() { configurationsFixture.enableTlrFeature(); var servicePointId = servicePointsFixture.cd1().getId(); var steve = usersFixture.steve(); From f2e5fab60c72f2a4d044e565aed73e666e722fad Mon Sep 17 00:00:00 2001 From: Roman_Barannyk Date: Fri, 11 Oct 2024 12:42:27 +0300 Subject: [PATCH 08/18] CIRC-2134 refactoring --- .../circulation/resources/SlipsResource.java | 94 +++++++++---------- 1 file changed, 43 insertions(+), 51 deletions(-) diff --git a/src/main/java/org/folio/circulation/resources/SlipsResource.java b/src/main/java/org/folio/circulation/resources/SlipsResource.java index 18ba817d2e..7a63f2e59d 100644 --- a/src/main/java/org/folio/circulation/resources/SlipsResource.java +++ b/src/main/java/org/folio/circulation/resources/SlipsResource.java @@ -1,7 +1,6 @@ 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; @@ -19,7 +18,6 @@ import java.lang.invoke.MethodHandles; import java.util.ArrayList; import java.util.Collection; -import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; @@ -123,7 +121,6 @@ private void getMany(RoutingContext routingContext) { .thenComposeAsync(r -> r.after(ctx -> fetchItemsForLocations(ctx, itemRepository, LocationRepository.using(clients, servicePointRepository)))) .thenComposeAsync(r -> r.after(ctx -> fetchRequests(ctx, clients))) - .thenComposeAsync(r -> r.after(ctx -> fetchTlrRequests(ctx, clients))) .thenComposeAsync(r -> r.after(ctx -> userRepository.findUsersForRequests( ctx.getRequests()))) .thenComposeAsync(result -> result.after(patronGroupRepository::findPatronGroupsForRequestsUsers)) @@ -137,71 +134,60 @@ private void getMany(RoutingContext routingContext) { .thenAccept(context::writeResultToHttpResponse); } - private CompletableFuture> fetchTlrRequests( + private CompletableFuture> fetchTitleLevelRequests( StuffSlipsContext stuffSlipsContext, Clients clients) { final var instanceRepository = new InstanceRepository(clients); final var holdingsRepository = new HoldingsRepository(clients.holdingsStorage()); - return fetchTlrRequests(clients, stuffSlipsContext) + return fetchTitleLevelRequests(clients, stuffSlipsContext) .thenComposeAsync(r -> r.after(ctx -> fetchByInstancesByRequests(ctx, instanceRepository))) .thenApply(r -> r.next(this::mapRequestsToInstances)) .thenComposeAsync(r -> r.after(ctx -> fetchHoldingsByInstances(ctx, holdingsRepository))) - .thenApply(r -> r.next(this::mapRequestsToHoldings)) - .thenApply(r -> r.next(this::includeTlrRequests)); + .thenApply(r -> r.next(this::mapRequestsToHoldings)); } private Result mapRequestsToInstances(StuffSlipsContext context) { - MultipleRecords instances = context.getInstances(); - if (instances == null || instances.isEmpty()) { + log.debug("mapRequestsToInstances:: parameters context: {}", context); + if (context.getInstances() == null || context.getInstances().isEmpty()) { log.info("mapRequestsToInstances:: no instances found"); - return succeeded(context); } - Set fetchedInstanceIds = instances.getRecords().stream() + Set fetchedInstanceIds = context.getInstances().getRecords().stream() .map(Instance::getId) .collect(Collectors.toSet()); - Map requestToInstanceIdMap = new HashMap<>(); - for (Request request : context.getTlrRequests().getRecords()) { - String instanceId = request.getInstanceId(); - if (fetchedInstanceIds.contains(instanceId)) { - requestToInstanceIdMap.put(request, instanceId); - } - } + Map requestToInstanceIdMap = context.getTlrRequests().getRecords().stream() + .filter(request -> fetchedInstanceIds.contains(request.getInstanceId())) + .collect(Collectors.toMap(identity(), Request::getInstanceId)); return succeeded(context.withRequestToInstanceIdMap(requestToInstanceIdMap)); } private Result mapRequestsToHoldings(StuffSlipsContext context) { + log.debug("mapRequestsToHoldings:: parameters context: {}", context); MultipleRecords holdings = context.getHoldings(); if (holdings == null || holdings.isEmpty()) { - log.info("mapRequestsToHoldings:: holdings is empty"); - + log.info("mapRequestsToHoldings:: holdings are empty"); return succeeded(context); } - Map instanceIdToHoldingsMap = new HashMap<>(); - for (Holdings holding : holdings.getRecords()) { - instanceIdToHoldingsMap.put(holding.getInstanceId(), holding); - } + Map instanceIdToHoldingsMap = holdings.getRecords().stream() + .collect(Collectors.toMap(Holdings::getInstanceId, identity(), + (existing, replacement) -> existing)); - Map requestToHoldingsMap = new HashMap<>(); Map requestToInstanceIdMap = context.getRequestToInstanceIdMap(); if (requestToInstanceIdMap == null || requestToInstanceIdMap.isEmpty()) { log.info("mapRequestsToHoldings:: no requests matched to holdings"); - return succeeded(context); } - for (Map.Entry entry : requestToInstanceIdMap.entrySet()) { - Request request = entry.getKey(); - String instanceId = entry.getValue(); - if (instanceId != null && instanceIdToHoldingsMap.containsKey(instanceId)) { - requestToHoldingsMap.put(request, instanceIdToHoldingsMap.get(instanceId)); - } - } + Map requestToHoldingsMap = requestToInstanceIdMap.entrySet().stream() + .filter(entry -> entry.getValue() != null && instanceIdToHoldingsMap.containsKey( + entry.getValue())) + .collect(Collectors.toMap(Map.Entry::getKey, entry -> instanceIdToHoldingsMap.get( + entry.getValue()))); return succeeded(context.withRequestToHoldingMap(requestToHoldingsMap)); } @@ -233,24 +219,22 @@ private CompletableFuture> fetchHoldingsByInstances( .thenApply(r -> r.map(ctx::withHoldings)); } - private Result includeTlrRequests(StuffSlipsContext ctx) { - var requestToHoldingMap = ctx.getRequestToHoldingMap(); - if (requestToHoldingMap == null || requestToHoldingMap.isEmpty()){ - log.info("includeTlrRequests:: no tlr requests to include"); - + private Result combineRequests(StuffSlipsContext ctx) { + log.debug("combineRequests:: parameters ctx: {}", ctx); + Map requestToHoldingMap = ctx.getRequestToHoldingMap(); + if (requestToHoldingMap == null || requestToHoldingMap.isEmpty()) { + log.info("combineRequests:: no tlr requests to combine"); return succeeded(ctx); } + Set locationIds = ctx.getLocations().getRecords().stream() .map(Location::getId) .collect(Collectors.toSet()); - List requestsToAdd = new ArrayList<>(); - for (Map.Entry entry : ctx.getRequestToHoldingMap().entrySet()) { - Holdings holding = entry.getValue(); - Request request = entry.getKey(); - if (locationIds.contains(holding.getEffectiveLocationId())) { - requestsToAdd.add(request); - } - } + + List requestsToAdd = requestToHoldingMap.entrySet().stream() + .filter(entry -> locationIds.contains(entry.getValue().getEffectiveLocationId())) + .map(Map.Entry::getKey) + .toList(); List updatedRequests = new ArrayList<>(ctx.getRequests().getRecords()); updatedRequests.addAll(requestsToAdd); @@ -284,7 +268,7 @@ private CompletableFuture> fetchItemsForLocations( if (locationIds.isEmpty()) { log.info("fetchPagedItemsForLocations:: locationIds is empty"); - return completedFuture(succeeded(context)); + return ofAsync(context); } List itemStatusValues = itemStatuses.stream() @@ -301,6 +285,14 @@ private CompletableFuture> fetchItemsForLocations( private CompletableFuture> fetchRequests( StuffSlipsContext context, Clients clients) { + return fetchItemLevelRequests(context, clients) + .thenComposeAsync(r -> r.after(ctx -> fetchTitleLevelRequests(ctx, clients))) + .thenApply(r -> r.next(this::combineRequests)); + } + + private CompletableFuture> fetchItemLevelRequests( + StuffSlipsContext context, Clients clients) { + Collection items = context.getItems(); if (items == null || items.isEmpty()) { log.info("fetchOpenPageRequestsForItems:: no items fetched"); @@ -328,7 +320,7 @@ private CompletableFuture> fetchRequests( .thenApply(r -> r.map(context::withRequests)); } - private CompletableFuture> fetchTlrRequests( + private CompletableFuture> fetchTitleLevelRequests( Clients clients, StuffSlipsContext context) { final Result typeQuery = exactMatch(REQUEST_TYPE_KEY, requestType.getValue()); @@ -339,7 +331,7 @@ private CompletableFuture> fetchTlrRequests( clients.requestsStorage(), REQUESTS_KEY, Request::from) .findByQuery(statusAndTypeQuery) .thenApply(r -> r.next(this::filterRequests)) - .thenApply(r -> r.next(tlrRequests -> succeeded(context.withTlrRequests(tlrRequests)))); + .thenApply(r -> r.map(context::withTlrRequests)); } private Result> filterRequests(MultipleRecords requests) { @@ -365,10 +357,10 @@ private CompletableFuture>> fetchLocationDetailsForItems if (locationsForItems.isEmpty()) { log.info("fetchLocationDetailsForItems:: locationsForItems is empty"); - return completedFuture(succeeded(emptyList())); + return ofAsync(emptyList()); } - return completedFuture(succeeded(locationsForItems)) + return ofAsync(locationsForItems) .thenComposeAsync(r -> r.after(locationRepository::fetchLibraries)) .thenComposeAsync(r -> r.after(locationRepository::fetchInstitutions)) .thenComposeAsync(r -> r.after(locationRepository::fetchCampuses)) From f4471014be1076b919894f3f88dd891fe2fa6cec Mon Sep 17 00:00:00 2001 From: Pavlo Smahin Date: Mon, 14 Oct 2024 11:21:01 +0300 Subject: [PATCH 09/18] CIRC-2154: Upgrade notes interface to v4.0 (#1497) --- descriptors/ModuleDescriptor-template.json | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/descriptors/ModuleDescriptor-template.json b/descriptors/ModuleDescriptor-template.json index eab23cfaf4..07dc26013f 100644 --- a/descriptors/ModuleDescriptor-template.json +++ b/descriptors/ModuleDescriptor-template.json @@ -1322,7 +1322,7 @@ }, { "id": "notes", - "version": "1.0 2.0 3.0" + "version": "4.0" }, { "id": "actual-cost-record-storage", @@ -2292,7 +2292,6 @@ "pubsub.publish.post", "note.types.collection.get", "notes.item.post", - "notes.domain.all", "actual-cost-record-storage.actual-cost-records.item.post", "actual-cost-record-storage.actual-cost-records.collection.get", "actual-cost-record-storage.actual-cost-records.item.get", @@ -2368,7 +2367,6 @@ "addresstypes.collection.get", "notes.item.post", "note.types.collection.get", - "notes.domain.all", "pubsub.publish.post" ], "visible": false From 64f12b0a61cf3f2a6800c9cfb8c6743b57d6865b Mon Sep 17 00:00:00 2001 From: Roman_Barannyk Date: Mon, 14 Oct 2024 15:40:49 +0300 Subject: [PATCH 10/18] CIRC-2134 fix typo --- .../circulation/resources/SlipsResource.java | 42 +++++++++---------- ...ipsContext.java => StaffSlipsContext.java} | 2 +- 2 files changed, 22 insertions(+), 22 deletions(-) rename src/main/java/org/folio/circulation/resources/context/{StuffSlipsContext.java => StaffSlipsContext.java} (96%) diff --git a/src/main/java/org/folio/circulation/resources/SlipsResource.java b/src/main/java/org/folio/circulation/resources/SlipsResource.java index 7a63f2e59d..31180b8ed3 100644 --- a/src/main/java/org/folio/circulation/resources/SlipsResource.java +++ b/src/main/java/org/folio/circulation/resources/SlipsResource.java @@ -48,7 +48,7 @@ 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.resources.context.StuffSlipsContext; +import org.folio.circulation.resources.context.StaffSlipsContext; import org.folio.circulation.storage.mappers.LocationMapper; import org.folio.circulation.support.Clients; import org.folio.circulation.support.RouteRegistration; @@ -134,20 +134,20 @@ private void getMany(RoutingContext routingContext) { .thenAccept(context::writeResultToHttpResponse); } - private CompletableFuture> fetchTitleLevelRequests( - StuffSlipsContext stuffSlipsContext, Clients clients) { + private CompletableFuture> fetchTitleLevelRequests( + StaffSlipsContext staffSlipsContext, Clients clients) { final var instanceRepository = new InstanceRepository(clients); final var holdingsRepository = new HoldingsRepository(clients.holdingsStorage()); - return fetchTitleLevelRequests(clients, stuffSlipsContext) + return fetchTitleLevelRequests(clients, staffSlipsContext) .thenComposeAsync(r -> r.after(ctx -> fetchByInstancesByRequests(ctx, instanceRepository))) .thenApply(r -> r.next(this::mapRequestsToInstances)) .thenComposeAsync(r -> r.after(ctx -> fetchHoldingsByInstances(ctx, holdingsRepository))) .thenApply(r -> r.next(this::mapRequestsToHoldings)); } - private Result mapRequestsToInstances(StuffSlipsContext context) { + private Result mapRequestsToInstances(StaffSlipsContext context) { log.debug("mapRequestsToInstances:: parameters context: {}", context); if (context.getInstances() == null || context.getInstances().isEmpty()) { log.info("mapRequestsToInstances:: no instances found"); @@ -165,7 +165,7 @@ private Result mapRequestsToInstances(StuffSlipsContext conte return succeeded(context.withRequestToInstanceIdMap(requestToInstanceIdMap)); } - private Result mapRequestsToHoldings(StuffSlipsContext context) { + private Result mapRequestsToHoldings(StaffSlipsContext context) { log.debug("mapRequestsToHoldings:: parameters context: {}", context); MultipleRecords holdings = context.getHoldings(); if (holdings == null || holdings.isEmpty()) { @@ -192,8 +192,8 @@ private Result mapRequestsToHoldings(StuffSlipsContext contex return succeeded(context.withRequestToHoldingMap(requestToHoldingsMap)); } - private CompletableFuture> fetchByInstancesByRequests( - StuffSlipsContext ctx, InstanceRepository instanceRepository) { + private CompletableFuture> fetchByInstancesByRequests( + StaffSlipsContext ctx, InstanceRepository instanceRepository) { var tlrRequests = ctx.getTlrRequests(); if (tlrRequests == null || tlrRequests.isEmpty()) { @@ -206,8 +206,8 @@ private CompletableFuture> fetchByInstancesByRequests( .thenApply(r -> r.map(ctx::withInstances)); } - private CompletableFuture> fetchHoldingsByInstances( - StuffSlipsContext ctx, HoldingsRepository holdingsRepository) { + private CompletableFuture> fetchHoldingsByInstances( + StaffSlipsContext ctx, HoldingsRepository holdingsRepository) { if (ctx.getRequestToInstanceIdMap() == null || ctx.getRequestToInstanceIdMap().isEmpty()) { log.info("fetchHoldingsByInstances:: instances no requests matched to instances found"); @@ -219,7 +219,7 @@ private CompletableFuture> fetchHoldingsByInstances( .thenApply(r -> r.map(ctx::withHoldings)); } - private Result combineRequests(StuffSlipsContext ctx) { + private Result combineRequests(StaffSlipsContext ctx) { log.debug("combineRequests:: parameters ctx: {}", ctx); Map requestToHoldingMap = ctx.getRequestToHoldingMap(); if (requestToHoldingMap == null || requestToHoldingMap.isEmpty()) { @@ -243,18 +243,18 @@ private Result combineRequests(StuffSlipsContext ctx) { updatedRequests.size()))); } - private CompletableFuture> fetchLocationsForServicePoint( + 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) - .thenApply(r -> r.map(locations -> new StuffSlipsContext().withLocations(locations))); + .thenApply(r -> r.map(locations -> new StaffSlipsContext().withLocations(locations))); } - private CompletableFuture> fetchItemsForLocations( - StuffSlipsContext context, ItemRepository itemRepository, + private CompletableFuture> fetchItemsForLocations( + StaffSlipsContext context, ItemRepository itemRepository, LocationRepository locationRepository) { log.debug("fetchPagedItemsForLocations:: multipleLocations: {}", @@ -282,16 +282,16 @@ private CompletableFuture> fetchItemsForLocations( .thenApply(r -> r.map(context::withItems)); } - private CompletableFuture> fetchRequests( - StuffSlipsContext context, Clients clients) { + private CompletableFuture> fetchRequests( + StaffSlipsContext context, Clients clients) { return fetchItemLevelRequests(context, clients) .thenComposeAsync(r -> r.after(ctx -> fetchTitleLevelRequests(ctx, clients))) .thenApply(r -> r.next(this::combineRequests)); } - private CompletableFuture> fetchItemLevelRequests( - StuffSlipsContext context, Clients clients) { + private CompletableFuture> fetchItemLevelRequests( + StaffSlipsContext context, Clients clients) { Collection items = context.getItems(); if (items == null || items.isEmpty()) { @@ -320,8 +320,8 @@ private CompletableFuture> fetchItemLevelRequests( .thenApply(r -> r.map(context::withRequests)); } - private CompletableFuture> fetchTitleLevelRequests( - Clients clients, StuffSlipsContext context) { + private CompletableFuture> fetchTitleLevelRequests( + Clients clients, StaffSlipsContext context) { final Result typeQuery = exactMatch(REQUEST_TYPE_KEY, requestType.getValue()); final Result statusQuery = exactMatch(STATUS_KEY, RequestStatus.OPEN_NOT_YET_FILLED.getValue()); diff --git a/src/main/java/org/folio/circulation/resources/context/StuffSlipsContext.java b/src/main/java/org/folio/circulation/resources/context/StaffSlipsContext.java similarity index 96% rename from src/main/java/org/folio/circulation/resources/context/StuffSlipsContext.java rename to src/main/java/org/folio/circulation/resources/context/StaffSlipsContext.java index 664dd27987..7dda147783 100644 --- a/src/main/java/org/folio/circulation/resources/context/StuffSlipsContext.java +++ b/src/main/java/org/folio/circulation/resources/context/StaffSlipsContext.java @@ -19,7 +19,7 @@ @AllArgsConstructor @Getter @With -public class StuffSlipsContext { +public class StaffSlipsContext { private MultipleRecords locations; private MultipleRecords requests; private MultipleRecords tlrRequests; From 68d3d5c8521fdc8348f619473a034001b5669aca Mon Sep 17 00:00:00 2001 From: SvitlanaKovalova1 Date: Wed, 16 Oct 2024 16:43:01 +0300 Subject: [PATCH 11/18] [CIRC-2156] Upgrade the API version in the ModuleDescriptor-template.json (#1498) --- NEWS.md | 4 ++++ descriptors/ModuleDescriptor-template.json | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/NEWS.md b/NEWS.md index a087e81952..9b58faeb74 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,3 +1,7 @@ +## 24.3.0 + +* [CIRC-2156](https://folio-org.atlassian.net/browse/CIRC-2156) Upgrade "holdings-storage" to 8.0 + ## 24.2.0 2024-03-21 * Update `feesfines` interface version to 19.0 (CIRC-1914) diff --git a/descriptors/ModuleDescriptor-template.json b/descriptors/ModuleDescriptor-template.json index 07dc26013f..06fd6ce50e 100644 --- a/descriptors/ModuleDescriptor-template.json +++ b/descriptors/ModuleDescriptor-template.json @@ -1218,7 +1218,7 @@ }, { "id": "holdings-storage", - "version": "1.3 2.0 3.0 4.0 5.0 6.0 7.0" + "version": "1.3 2.0 3.0 4.0 5.0 6.0 7.0 8.0" }, { "id": "request-storage", From 62a207907aea19a6c22edd9c07f0aa1efb34f47c Mon Sep 17 00:00:00 2001 From: MagzhanArtykov Date: Wed, 23 Oct 2024 12:15:12 +0500 Subject: [PATCH 12/18] CIRC-2163 Upgrade to RMB v35.3.0 --- .github/workflows/postgres.yml | 26 ++++++++++++++++++++++++++ pom.xml | 9 +++++++-- ramls/circulation-rules.raml | 3 --- ramls/circulation-settings.raml | 1 - ramls/circulation.raml | 3 +-- ramls/inventory-reports.raml | 3 --- ramls/requests-reports.raml | 3 --- ramls/staff-slips.raml | 3 --- 8 files changed, 34 insertions(+), 17 deletions(-) create mode 100644 .github/workflows/postgres.yml diff --git a/.github/workflows/postgres.yml b/.github/workflows/postgres.yml new file mode 100644 index 0000000000..1d4d0f80f2 --- /dev/null +++ b/.github/workflows/postgres.yml @@ -0,0 +1,26 @@ +name: postgres +on: + workflow_dispatch: + inputs: + postgres: + description: "List of postgres container images, to be injected as TESTCONTAINERS_POSTGRES_IMAGE" + default: '["postgres:16-alpine", "postgres:18-alpine"]' +jobs: + build: + runs-on: ubuntu-latest + strategy: + matrix: + postgres: ${{ fromJSON(github.event.inputs.postgres) }} + fail-fast: false + steps: + - uses: actions/checkout@v4 + with: + submodules: true + - uses: actions/setup-java@v4 + with: + distribution: 'temurin' + java-version: '17' + cache: maven + - run: mvn --batch-mode verify + env: + TESTCONTAINERS_POSTGRES_IMAGE: ${{ matrix.postgres }} diff --git a/pom.xml b/pom.xml index bb6da6be23..964270022e 100644 --- a/pom.xml +++ b/pom.xml @@ -25,7 +25,7 @@ 4.11.1 7.74.1.Final - 35.2.0 + 35.3.0 4.5.5 2.23.1 UTF-8 @@ -106,7 +106,12 @@ org.apache.commons commons-lang3 - 3.12.0 + 3.17.0 + + + commons-io + commons-io + 2.17.0 org.apache.commons diff --git a/ramls/circulation-rules.raml b/ramls/circulation-rules.raml index ba8b6d26d3..9e74fef840 100644 --- a/ramls/circulation-rules.raml +++ b/ramls/circulation-rules.raml @@ -12,9 +12,6 @@ types: error: !include raml-util/schemas/error.schema errors: !include raml-util/schemas/errors.schema -traits: - language: !include raml-util/traits/language.raml - /circulation: /rules: displayName: Circulation rules diff --git a/ramls/circulation-settings.raml b/ramls/circulation-settings.raml index 731b9187c6..a4de28312d 100644 --- a/ramls/circulation-settings.raml +++ b/ramls/circulation-settings.raml @@ -9,7 +9,6 @@ documentation: content: API for circulation settings traits: - language: !include raml-util/traits/language.raml pageable: !include raml-util/traits/pageable.raml searchable: !include raml-util/traits/searchable.raml validate: !include raml-util/traits/validation.raml diff --git a/ramls/circulation.raml b/ramls/circulation.raml index ff2081a21b..3749bfcdbd 100644 --- a/ramls/circulation.raml +++ b/ramls/circulation.raml @@ -18,7 +18,6 @@ types: errors: !include extended-errors.json traits: - language: !include raml-util/traits/language.raml pageable: !include raml-util/traits/pageable.raml searchable: !include raml-util/traits/searchable.raml validate: !include raml-util/traits/validation.raml @@ -364,4 +363,4 @@ resourceTypes: description: "Internal server error" body: text/plain: - example: "Internal server error" \ No newline at end of file + example: "Internal server error" diff --git a/ramls/inventory-reports.raml b/ramls/inventory-reports.raml index 7a95b32c6d..160034f149 100644 --- a/ramls/inventory-reports.raml +++ b/ramls/inventory-reports.raml @@ -11,9 +11,6 @@ documentation: types: items: !include items-in-transit.json -traits: - language: !include raml-util/traits/language.raml - resourceTypes: collection-get: !include raml-util/rtypes/collection-get.raml diff --git a/ramls/requests-reports.raml b/ramls/requests-reports.raml index 9a7505a6ab..f3a552527d 100644 --- a/ramls/requests-reports.raml +++ b/ramls/requests-reports.raml @@ -11,9 +11,6 @@ documentation: types: requests: !include requests.json -traits: - language: !include raml-util/traits/language.raml - resourceTypes: collection-get: !include raml-util/rtypes/collection-get.raml diff --git a/ramls/staff-slips.raml b/ramls/staff-slips.raml index 45c1a7a023..2b57085e5b 100644 --- a/ramls/staff-slips.raml +++ b/ramls/staff-slips.raml @@ -11,9 +11,6 @@ documentation: types: staff-slips: !include staff-slips-response.json -traits: - language: !include raml-util/traits/language.raml - resourceTypes: collection-get: !include raml-util/rtypes/collection-get.raml From b84102fb0a116d0900973a4eea9ce5b74242d971 Mon Sep 17 00:00:00 2001 From: MagzhanArtykov Date: Wed, 23 Oct 2024 12:38:07 +0500 Subject: [PATCH 13/18] CIRC-2163 Upgrade to RMB v35.3.0 --- ramls/raml-util | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ramls/raml-util b/ramls/raml-util index 9d7fe883b4..f48a63f3e4 160000 --- a/ramls/raml-util +++ b/ramls/raml-util @@ -1 +1 @@ -Subproject commit 9d7fe883b4e08a262ee3cffc69c33f8b7b63710b +Subproject commit f48a63f3e45b9a3b437d21c3a61ed10b8ceb5f25 From 806b19470c29a66b2cf85b454a9710d6f83796ed Mon Sep 17 00:00:00 2001 From: MagzhanArtykov Date: Wed, 23 Oct 2024 12:47:06 +0500 Subject: [PATCH 14/18] CIRC-2163 Upgrade to RMB v35.3.0 --- ramls/circulation.raml | 5 ----- 1 file changed, 5 deletions(-) diff --git a/ramls/circulation.raml b/ramls/circulation.raml index 3749bfcdbd..ea45ea2779 100644 --- a/ramls/circulation.raml +++ b/ramls/circulation.raml @@ -33,7 +33,6 @@ resourceTypes: post: description: Creates a loan by checking out an item to a loanee is: [ - language, validate ] body: @@ -57,7 +56,6 @@ resourceTypes: post: description: Updates the due date of an existing loan is: [ - language, validate ] body: @@ -85,7 +83,6 @@ resourceTypes: post: description: Updates the due date of an existing loan is: [ - language, validate ] body: @@ -113,7 +110,6 @@ resourceTypes: post: description: Updates the status of an existing loan is: [ - language, validate ] body: @@ -295,7 +291,6 @@ resourceTypes: post: description: Creates a request for any item from the given instance ID is: [ - language, validate ] body: From d8dec91c6ddd55060c089db2d8efec4d53c8ebf2 Mon Sep 17 00:00:00 2001 From: MagzhanArtykov Date: Wed, 23 Oct 2024 13:21:02 +0500 Subject: [PATCH 15/18] CIRC-2163 Upgrade to RMB v35.3.0 --- .github/workflows/postgres.yml | 26 -------------------------- 1 file changed, 26 deletions(-) delete mode 100644 .github/workflows/postgres.yml diff --git a/.github/workflows/postgres.yml b/.github/workflows/postgres.yml deleted file mode 100644 index 1d4d0f80f2..0000000000 --- a/.github/workflows/postgres.yml +++ /dev/null @@ -1,26 +0,0 @@ -name: postgres -on: - workflow_dispatch: - inputs: - postgres: - description: "List of postgres container images, to be injected as TESTCONTAINERS_POSTGRES_IMAGE" - default: '["postgres:16-alpine", "postgres:18-alpine"]' -jobs: - build: - runs-on: ubuntu-latest - strategy: - matrix: - postgres: ${{ fromJSON(github.event.inputs.postgres) }} - fail-fast: false - steps: - - uses: actions/checkout@v4 - with: - submodules: true - - uses: actions/setup-java@v4 - with: - distribution: 'temurin' - java-version: '17' - cache: maven - - run: mvn --batch-mode verify - env: - TESTCONTAINERS_POSTGRES_IMAGE: ${{ matrix.postgres }} From f13ace965c775d3936852950306e9d78d7e23ea9 Mon Sep 17 00:00:00 2001 From: Christopher Hellen Date: Tue, 29 Oct 2024 17:02:26 -0500 Subject: [PATCH 16/18] corrected permissions changed in MODCAL-136 (#1504) --- descriptors/ModuleDescriptor-template.json | 24 ++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/descriptors/ModuleDescriptor-template.json b/descriptors/ModuleDescriptor-template.json index 06fd6ce50e..590b2da834 100644 --- a/descriptors/ModuleDescriptor-template.json +++ b/descriptors/ModuleDescriptor-template.json @@ -1133,7 +1133,8 @@ "accounts.item.post", "feefineactions.item.post", "circulation-storage.loans-history.collection.get", - "calendar.endpoint.dates.get" + "calendar.endpoint.calendars.surroundingOpenings.get", + "calendar.endpoint.calendars.allOpenings.get" ], "schedule": { "cron": "1 0 * * *", @@ -1773,7 +1774,8 @@ "proxiesfor.collection.get", "patron-notice.post", "configuration.entries.collection.get", - "calendar.endpoint.dates.get", + "calendar.endpoint.calendars.surroundingOpenings.get", + "calendar.endpoint.calendars.allOpenings.get", "pubsub.publish.post", "circulation-storage.loans-history.collection.get" ], @@ -1784,7 +1786,8 @@ "displayName" : "module permissions for one op", "description" : "to reduce X-Okapi-Token size", "subPermissions": [ - "calendar.endpoint.dates.get", + "calendar.endpoint.calendars.surroundingOpenings.get", + "calendar.endpoint.calendars.allOpenings.get", "circulation-storage.loans.item.post", "circulation-storage.loans.item.get", "circulation-storage.loans.collection.get", @@ -1881,7 +1884,8 @@ "accounts.refund.post", "accounts.cancel.post", "configuration.entries.collection.get", - "calendar.endpoint.dates.get", + "calendar.endpoint.calendars.surroundingOpenings.get", + "calendar.endpoint.calendars.allOpenings.get", "actual-cost-record-storage.actual-cost-records.collection.get", "actual-cost-record-storage.actual-cost-records.item.get", "actual-cost-fee-fine-cancel.post", @@ -1917,7 +1921,8 @@ "users.collection.get", "addresstypes.collection.get", "proxiesfor.collection.get", - "calendar.endpoint.dates.get", + "calendar.endpoint.calendars.surroundingOpenings.get", + "calendar.endpoint.calendars.allOpenings.get", "configuration.entries.collection.get", "scheduled-notice-storage.scheduled-notices.collection.delete", "scheduled-notice-storage.scheduled-notices.item.post", @@ -2099,7 +2104,8 @@ "usergroups.item.get", "proxiesfor.collection.get", "patron-notice.post", - "calendar.endpoint.dates.get", + "calendar.endpoint.calendars.surroundingOpenings.get", + "calendar.endpoint.calendars.allOpenings.get", "scheduled-notice-storage.scheduled-notices.collection.delete", "scheduled-notice-storage.scheduled-notices.item.post", "configuration.entries.collection.get", @@ -2155,7 +2161,8 @@ "displayName": "module permissions for one op", "description": "to reduce X-Okapi-Token size", "subPermissions": [ - "calendar.endpoint.dates.get", + "calendar.endpoint.calendars.surroundingOpenings.get", + "calendar.endpoint.calendars.allOpenings.get", "circulation-storage.loan-policies.item.get", "circulation-storage.loans.item.get", "circulation-storage.loans.item.put", @@ -2259,7 +2266,8 @@ "usergroups.item.get", "proxiesfor.collection.get", "patron-notice.post", - "calendar.endpoint.dates.get", + "calendar.endpoint.calendars.surroundingOpenings.get", + "calendar.endpoint.calendars.allOpenings.get", "configuration.entries.collection.get", "scheduled-notice-storage.scheduled-notices.collection.delete", "scheduled-notice-storage.scheduled-notices.item.post", From 91d09704b64d7d1307f127501a43e4ee746560f3 Mon Sep 17 00:00:00 2001 From: nielserik Date: Thu, 31 Oct 2024 14:37:56 +0100 Subject: [PATCH 17/18] CIRC-2136 set a new location in check-in response, pick slip only if item will float --- .../domain/notice/TemplateContextUtil.java | 3 ++- .../ItemSummaryRepresentation.java | 3 ++- .../loans/scenarios/FloatingItemsTests.java | 18 ++++++++++++++++++ 3 files changed, 22 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/folio/circulation/domain/notice/TemplateContextUtil.java b/src/main/java/org/folio/circulation/domain/notice/TemplateContextUtil.java index 213fe69013..450f745b07 100644 --- a/src/main/java/org/folio/circulation/domain/notice/TemplateContextUtil.java +++ b/src/main/java/org/folio/circulation/domain/notice/TemplateContextUtil.java @@ -24,6 +24,7 @@ import org.folio.circulation.domain.FeeFineAction; import org.folio.circulation.domain.Instance; import org.folio.circulation.domain.Item; +import org.folio.circulation.domain.ItemStatus; import org.folio.circulation.domain.Loan; import org.folio.circulation.domain.Location; import org.folio.circulation.domain.Request; @@ -211,7 +212,7 @@ private static JsonObject createItemContext(Item item) { .put("displaySummary", item.getDisplaySummary()) .put("descriptionOfPieces", item.getDescriptionOfPieces()); - Location location = item.canFloatThroughCheckInServicePoint() ? + Location location = (item.canFloatThroughCheckInServicePoint() && item.isInStatus(ItemStatus.AVAILABLE)) ? item.getFloatDestinationLocation() : item.getLocation(); if (location != null) { diff --git a/src/main/java/org/folio/circulation/domain/representations/ItemSummaryRepresentation.java b/src/main/java/org/folio/circulation/domain/representations/ItemSummaryRepresentation.java index 8f02b05cc3..f18255537b 100644 --- a/src/main/java/org/folio/circulation/domain/representations/ItemSummaryRepresentation.java +++ b/src/main/java/org/folio/circulation/domain/representations/ItemSummaryRepresentation.java @@ -12,6 +12,7 @@ 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.ServicePoint; @@ -78,7 +79,7 @@ public JsonObject createItemSummary(Item item) { final Location location = item.getLocation(); - if (item.canFloatThroughCheckInServicePoint()) { + if (item.canFloatThroughCheckInServicePoint() && item.isInStatus(ItemStatus.AVAILABLE)) { itemSummary.put("location", new JsonObject() .put("name", item.getFloatDestinationLocation().getName())); } else if (location != null) { diff --git a/src/test/java/api/loans/scenarios/FloatingItemsTests.java b/src/test/java/api/loans/scenarios/FloatingItemsTests.java index 669144a8ee..6eccec7cf5 100644 --- a/src/test/java/api/loans/scenarios/FloatingItemsTests.java +++ b/src/test/java/api/loans/scenarios/FloatingItemsTests.java @@ -72,6 +72,15 @@ void willSetFloatingItemsTemporaryLocationToFloatingCollectionAtServicePoint() { itemRepresentation.containsKey("inTransitDestinationServicePointId"), CoreMatchers.is(false)); + assertThat( "The check-in response should display the item's new location.", + itemRepresentation.getJsonObject("location").getString("name"), CoreMatchers.is("Floating collection 2")); + + JsonObject staffSlipContext = checkInResponse.getStaffSlipContext(); + + assertThat( "The staff slip context should display the item's new location.", + staffSlipContext.getJsonObject("item").getString("effectiveLocationSpecific"), CoreMatchers.is("Floating collection 2")); + + JsonObject loanRepresentation = checkInResponse.getLoan(); assertThat("closed loan should be present in response", @@ -266,6 +275,14 @@ void willPutFloatingItemInTransitWhenHoldRequestWasIssuedAtDifferentServicePoint itemRepresentation.containsKey("inTransitDestinationServicePointId"), CoreMatchers.is(true)); + assertThat( "The check-in response should display the item's original location.", + itemRepresentation.getJsonObject("location").getString("name"), CoreMatchers.is("Floating collection")); + + JsonObject staffSlipContext = checkInResponse.getStaffSlipContext(); + + assertThat( "The staff slip context should display the item's original location.", + staffSlipContext.getJsonObject("item").getString("effectiveLocationSpecific"), CoreMatchers.is("Floating collection")); + JsonObject loanRepresentation = checkInResponse.getLoan(); assertThat("closed loan should be present in response", @@ -300,6 +317,7 @@ void willPutFloatingItemInTransitWhenHoldRequestWasIssuedAtDifferentServicePoint assertThat("Checkin Service Point Id should be stored", storedLoan.getString("checkinServicePointId"), is(servicePointTwo.getId())); + } From 07960482a3921b0fb0f62d453e1a173ae9ced2b7 Mon Sep 17 00:00:00 2001 From: nielserik Date: Thu, 31 Oct 2024 15:28:52 +0100 Subject: [PATCH 18/18] CIRC-2136 remove spurious workflows --- .github/workflows/k8s-deploy-latest.yml | 27 ----- .github/workflows/mvn-dev-build-deploy.yml | 132 --------------------- 2 files changed, 159 deletions(-) delete mode 100644 .github/workflows/k8s-deploy-latest.yml delete mode 100644 .github/workflows/mvn-dev-build-deploy.yml diff --git a/.github/workflows/k8s-deploy-latest.yml b/.github/workflows/k8s-deploy-latest.yml deleted file mode 100644 index fde161bd64..0000000000 --- a/.github/workflows/k8s-deploy-latest.yml +++ /dev/null @@ -1,27 +0,0 @@ -# Very simple workflow to deploy the *latest* published container image with the 'latest' tag. -# This does not post updated module descriptors to okapi, update permissions or enable -# new versions of a module with the tenant. If that is needed, it should be done manually -# via the Okapi API. - -name: k8s-deploy-latest - -env: - K8S_NAMESPACE: 'snapshot-dev' - K8S_DEPLOYMENT: 'mod-circulation-dev' - -on: - workflow_dispatch - -jobs: - k8s-deploy-latest: - - runs-on: ubuntu-latest - steps: - - name: Deploy latest to K8s - uses: actions-hub/kubectl@v1.29.3 - env: - KUBE_CONFIG: ${{ secrets.SNAPSHOT_DEV_SA_KUBECONFIG }} - with: - args: - -n ${{ env.K8S_NAMESPACE }} rollout restart deployment ${{ env.K8S_DEPLOYMENT }} - diff --git a/.github/workflows/mvn-dev-build-deploy.yml b/.github/workflows/mvn-dev-build-deploy.yml deleted file mode 100644 index a05e2db06f..0000000000 --- a/.github/workflows/mvn-dev-build-deploy.yml +++ /dev/null @@ -1,132 +0,0 @@ -name: mvn-dev-build-deploy - -on: - push: - pull_request: - types: [opened, synchronize, reopened] - workflow_dispatch: - -env: - PUBLISH_BRANCH: 'deployment' - OKAPI_URL: 'https://snapshot-dev-okapi.folio-dev.indexdata.com' - OKAPI_SECRET_USER: "${{ secrets.SNAPSHOT_DEV_OKAPI_USER }}" - OKAPI_SECRET_PASSWORD: "${{ secrets.SNAPSHOT_DEV_OKAPI_PASSWORD }}" - OK_SESSION: 'session1' - -jobs: - mvn-dev-build-deploy: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - with: - fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis - submodules: recursive - - - name: Set up JDK 17 - uses: actions/setup-java@v4 - with: - java-version: 17 - distribution: 'temurin' # Alternative distribution options are available. - - - name: Prepare okclient - run: git clone https://github.com/indexdata/okclient - - - name: Ensure OK and FOLIO login - # So do not proceed with other workflow steps if not available. - run: | - source okclient/ok.sh - OK -S ${{ env.OK_SESSION }} \ - -h ${{ env.OKAPI_URL }} \ - -t "supertenant" \ - -u ${{ env.OKAPI_SECRET_USER }} \ - -p ${{ env.OKAPI_SECRET_PASSWORD }} - OK -S ${{ env.OK_SESSION }} -x - - - name: Gather some variables - run: | - echo "MODULE_NAME=$(mvn help:evaluate -Dexpression=project.artifactId -q -DforceStdout)" >> $GITHUB_ENV - echo "SHA_SHORT=$(git rev-parse --short HEAD)" >> $GITHUB_ENV - echo "CURRENT_BRANCH=$(echo ${GITHUB_REF#refs/heads/})" >> $GITHUB_ENV - - - name: Set module version - run: | - echo "MODULE_VERSION=$(mvn help:evaluate -Dexpression=project.version -q -DforceStdout)-${SHA_SHORT}" >> $GITHUB_ENV - - - name: Cache SonarCloud packages - uses: actions/cache@v4 - with: - path: ~/.sonar/cache - key: ${{ runner.os }}-sonar - restore-keys: ${{ runner.os }}-sonar - - - name: Cache Maven packages - uses: actions/cache@v4 - with: - path: ~/.m2 - key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }} - restore-keys: ${{ runner.os }}-m2 - - - name: Maven build - run: mvn clean org.jacoco:jacoco-maven-plugin:prepare-agent install org.jacoco:jacoco-maven-plugin:report - - - name: SQ analyze - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Needed to get PR information, if any - SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} - run: mvn -B org.sonarsource.scanner.maven:sonar-maven-plugin:sonar -Dsonar.host.url=https://sonarcloud.io -Dsonar.organization=indexdata -Dsonar.projectKey=indexdata_${{ github.event.repository.name }} - - - name: Update ModuleDescriptor Id - run: | - if test -f "$MOD_DESCRIPTOR"; then - echo "Found $MOD_DESCRIPTOR" - cat <<< $(jq '.id = "${{ env.MODULE_NAME}}-${{ env.MODULE_VERSION }}"' $MOD_DESCRIPTOR) > $MOD_DESCRIPTOR - echo "MODULE_DESCRIPTOR=$MOD_DESCRIPTOR" >> $GITHUB_ENV - else - echo "Could not find $MOD_DESCRIPTOR" - exit 1 - fi - env: - MOD_DESCRIPTOR: './target/ModuleDescriptor.json' - - - name: Read ModuleDescriptor - id: moduleDescriptor - uses: juliangruber/read-file-action@v1 - with: - path: ${{ env.MODULE_DESCRIPTOR }} - - - name: Login to Index Data Docker Hub account - if: ${{ env.CURRENT_BRANCH == env.PUBLISH_BRANCH }} - uses: docker/login-action@v3 - with: - username: ${{ secrets.DOCKER_USER }} - password: ${{ secrets.DOCKER_PASSWORD }} - - - name: Build and publish Docker image - if: ${{ env.CURRENT_BRANCH == env.PUBLISH_BRANCH }} - uses: docker/build-push-action@v5 - with: - context: . - push: true - tags: indexdata/${{ env.MODULE_NAME }}:${{ env.MODULE_VERSION }},indexdata/${{ env.MODULE_NAME }}:latest - - - name: Publish ModuleDescriptor to Okapi - if: ${{ env.CURRENT_BRANCH == env.PUBLISH_BRANCH }} - run: | - source okclient/ok.sh - echo "Do login ..." - OK -S ${{ env.OK_SESSION }} \ - -h ${{ env.OKAPI_URL }} \ - -t "supertenant" \ - -u ${{ env.OKAPI_SECRET_USER }} \ - -p ${{ env.OKAPI_SECRET_PASSWORD }} - echo "Post the MD and report the response status ..." - OK -S ${{ env.OK_SESSION }} _/proxy/modules \ - -X post -f ${{ env.MODULE_DESCRIPTOR }} - declare -n NAMEREF_STATUS=${{ env.OK_SESSION }}_HTTPStatus - echo "Response status: $NAMEREF_STATUS" - echo "Do logout ..." - OK -S ${{ env.OK_SESSION }} -x - - - name: Print module version to job summary - run: | - echo "#### Module Version: ${{ env.MODULE_VERSION }}" >> $GITHUB_STEP_SUMMARY