diff --git a/descriptors/ModuleDescriptor-template.json b/descriptors/ModuleDescriptor-template.json index 4223f70edf..f2e3bb42e7 100644 --- a/descriptors/ModuleDescriptor-template.json +++ b/descriptors/ModuleDescriptor-template.json @@ -185,7 +185,7 @@ }, { "id": "circulation", - "version": "14.3", + "version": "14.4", "handlers": [ { "methods": [ @@ -2065,7 +2065,9 @@ "users.collection.get", "addresstypes.collection.get", "usergroups.collection.get", - "usergroups.item.get" + "usergroups.item.get", + "print-events-storage.print-events-status.item.post", + "circulation-storage.circulation-settings.collection.get" ], "visible": false }, diff --git a/ramls/examples/request.json b/ramls/examples/request.json index efeca2d28b..228c97f1b7 100644 --- a/ramls/examples/request.json +++ b/ramls/examples/request.json @@ -45,5 +45,14 @@ "callNumber": "F16.H37 A2 9001" }, "pickupServicePointName": "Circ Desk 1" + }, + "printDetails": { + "count": 4, + "lastPrintedDate": "2024-07-29T11:54:07.000Z", + "lastPrintRequester": { + "lastName": "lastName", + "firstName": "firstName", + "middleName": "middleName" + } } } diff --git a/ramls/request.json b/ramls/request.json index 9e38fe6c7f..1b80913dbb 100644 --- a/ramls/request.json +++ b/ramls/request.json @@ -358,6 +358,46 @@ } } }, + "printDetails": { + "description": "The print details of the request", + "type": "object", + "readonly": true, + "properties": { + "count": { + "description": "Total no of times the request is printed", + "type": "integer", + "readOnly": true + }, + "lastPrintedDate": { + "description": "Recent printed time of the request", + "type": "string", + "format": "date-time", + "readOnly": true + }, + "lastPrintRequester": { + "description": "Details of the User who printed the request recently", + "readonly": true, + "type": "object", + "properties": { + "firstName": { + "description": "first name of the user", + "type": "string", + "readonly": true + }, + "lastName": { + "description": "last name of the user", + "type": "string", + "readonly": true + }, + "middleName": { + "description": "middle name of the user", + "type": "string", + "readonly": true + } + } + } + } + }, "tags": { "type": "object", "description": "Tags", diff --git a/src/main/java/org/folio/circulation/domain/PrintEventDetail.java b/src/main/java/org/folio/circulation/domain/PrintEventDetail.java new file mode 100644 index 0000000000..b52a440c30 --- /dev/null +++ b/src/main/java/org/folio/circulation/domain/PrintEventDetail.java @@ -0,0 +1,48 @@ +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 0774075a5b..d1cecb8085 100644 --- a/src/main/java/org/folio/circulation/domain/Request.java +++ b/src/main/java/org/folio/circulation/domain/Request.java @@ -102,13 +102,15 @@ public class Request implements ItemRelatedRecord, UserRelatedRecord { private boolean changedPosition; private Integer previousPosition; private boolean changedStatus; + @With + private PrintEventDetail printEventDetail; public static Request from(JsonObject representation) { // TODO: make sure that operation and TLR settings don't matter for all processes calling // this constructor return new Request(null, null, representation, null, null, new ArrayList<>(), new HashMap<>(), null, null, null, null, null, null, false, null, - false); + false, null); } public static Request from(TlrSettingsConfiguration tlrSettingsConfiguration, Operation operation, @@ -116,7 +118,7 @@ public static Request from(TlrSettingsConfiguration tlrSettingsConfiguration, Op return new Request(tlrSettingsConfiguration, operation, representation, null, null, new ArrayList<>(), new HashMap<>(), null, null, null, null, null, null, false, - null, false); + null, false, null); } public JsonObject asJson() { @@ -223,7 +225,7 @@ public Request withItem(Item newItem) { cancellationReasonRepresentation, instance, instanceItems, instanceItemsRequestPolicies, newItem, requester, proxy, addressType, loan == null ? null : loan.withItem(newItem), pickupServicePoint, changedPosition, - previousPosition, changedStatus); + previousPosition, changedStatus, printEventDetail); } @Override diff --git a/src/main/java/org/folio/circulation/domain/RequestRepresentation.java b/src/main/java/org/folio/circulation/domain/RequestRepresentation.java index f7bb0af944..6a51004c10 100644 --- a/src/main/java/org/folio/circulation/domain/RequestRepresentation.java +++ b/src/main/java/org/folio/circulation/domain/RequestRepresentation.java @@ -33,9 +33,9 @@ public JsonObject extendedRepresentation(Request request) { addAdditionalProxyProperties(requestRepresentation, request.getProxy()); addAdditionalServicePointProperties(requestRepresentation, request.getPickupServicePoint()); addDeliveryAddress(requestRepresentation, request, request.getRequester()); + addPrintEventProperties(requestRepresentation, request.getPrintEventDetail()); removeSearchIndexFields(requestRepresentation); - return requestRepresentation; } @@ -258,5 +258,29 @@ 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 1a179857ff..8622357131 100644 --- a/src/main/java/org/folio/circulation/infrastructure/storage/PrintEventsRepository.java +++ b/src/main/java/org/folio/circulation/infrastructure/storage/PrintEventsRepository.java @@ -1,26 +1,46 @@ 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) { - printEventsStorageClient = clients.printEventsStorageClient(); + this.printEventsStorageClient = clients.printEventsStorageClient(); + this.printEventsStorageStatusClient = clients.printEventsStorageStatusClient(); + this.circulationSettingsRepository = new CirculationSettingsRepository(clients); } public CompletableFuture> create(PrintEventRequest printEventRequest) { @@ -32,4 +52,54 @@ 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 208c2bf0da..c17aa06dc5 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,6 +30,7 @@ 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; @@ -66,6 +67,7 @@ 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 @@ -76,7 +78,8 @@ 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)); + loanRepository, servicePointRepository, patronGroupRepository, new InstanceRepository(clients), + new PrintEventsRepository(clients)); } /** @@ -87,9 +90,10 @@ public RequestRepository(org.folio.circulation.support.Clients clients) { } private RequestRepository(Clients clients, ItemRepository itemRepository, - UserRepository userRepository, LoanRepository loanRepository, - ServicePointRepository servicePointRepository, - PatronGroupRepository patronGroupRepository, InstanceRepository instanceRepository) { + UserRepository userRepository, LoanRepository loanRepository, + ServicePointRepository servicePointRepository, + PatronGroupRepository patronGroupRepository, InstanceRepository instanceRepository, + PrintEventsRepository printEventsRepository) { this.requestsStorageClient = clients.getRequestsStorageClient(); this.requestsBatchStorageClient = clients.getRequestsBatchStorageClient(); @@ -100,6 +104,7 @@ private RequestRepository(Clients clients, ItemRepository itemRepository, this.servicePointRepository = servicePointRepository; this.patronGroupRepository = patronGroupRepository; this.instanceRepository = instanceRepository; + this.printEventsRepository = printEventsRepository; } public static RequestRepository using( @@ -113,12 +118,14 @@ public static RequestRepository using( itemRepository, userRepository, loanRepository, new ServicePointRepository(clients), new PatronGroupRepository(clients), - new InstanceRepository(clients)); + new InstanceRepository(clients), + new PrintEventsRepository(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)); } @@ -142,6 +149,14 @@ 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 f7ba66c68a..8e8256f990 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 @@ -23,6 +23,7 @@ import java.util.stream.Collectors; 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; @@ -271,6 +272,10 @@ private ArrayList getUsersFromRequest(Request request) { usersToFetch.add(request.getProxyUserId()); } + if (request.getPrintEventDetail() != null && request.getPrintEventDetail().getUserId() != null) { + usersToFetch.add(request.getPrintEventDetail().getUserId()); + } + return usersToFetch; } @@ -282,7 +287,14 @@ private Request matchUsersToRequests( return request .withRequester(userMap.getOrDefault(request.getUserId(), null)) - .withProxy(userMap.getOrDefault(request.getProxyUserId(), 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; } 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 72ba78fcd1..b530e20110 100644 --- a/src/main/java/org/folio/circulation/resources/PrintEventsResource.java +++ b/src/main/java/org/folio/circulation/resources/PrintEventsResource.java @@ -13,7 +13,7 @@ import org.folio.circulation.infrastructure.storage.requests.RequestRepository; import org.folio.circulation.support.Clients; import org.folio.circulation.support.RouteRegistration; -import org.folio.circulation.support.http.server.JsonHttpResponse; +import org.folio.circulation.support.http.server.NoContentResponse; import org.folio.circulation.support.http.server.WebContext; import org.folio.circulation.support.results.Result; @@ -24,16 +24,17 @@ import static org.folio.circulation.support.ValidationErrorFailure.singleValidationError; import static org.folio.circulation.support.json.JsonPropertyFetcher.getProperty; +import static org.folio.circulation.support.results.MappingFunctions.toFixedValue; import static org.folio.circulation.support.results.Result.ofAsync; import static org.folio.circulation.support.results.Result.succeeded; public class PrintEventsResource extends Resource { private static final Logger log = LogManager.getLogger(MethodHandles.lookup().lookupClass()); - private static final String PRINT_EVENT_FLAG_QUERY = "query=name=printEventLogFeature"; + public 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"; - private static final String PRINT_EVENT_FLAG_PROPERTY_NAME = "enablePrintLog"; + public static final String PRINT_EVENT_FLAG_PROPERTY_NAME = "enablePrintLog"; public PrintEventsResource(HttpClient client) { super(client); @@ -61,7 +62,7 @@ void create(RoutingContext routingContext) { .thenCompose(r -> r.after(validatePrintEventFeatureFlag(circulationSettingsRepository))) .thenCompose(r -> r.after(validateRequests(requestRepository))) .thenCompose(r -> r.after(printEventsRepository::create)) - .thenApply(r -> r.map(response -> JsonHttpResponse.created(null, null))) + .thenApply(r -> r.map(toFixedValue(NoContentResponse::noContent))) .thenAccept(context::writeResultToHttpResponse); } diff --git a/src/main/java/org/folio/circulation/support/Clients.java b/src/main/java/org/folio/circulation/support/Clients.java index 99be88183c..9ff7028965 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,6 +388,10 @@ public CollectionResourceClient printEventsStorageClient() { return printEventsStorageClient; } + public CollectionResourceClient printEventsStorageStatusClient() { + return printEventsStorageStatusClient; + } + private static CollectionResourceClient getCollectionResourceClient( OkapiHttpClient client, WebContext context, String path) @@ -829,6 +833,13 @@ 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/printEvents/PrintEventsTests.java b/src/test/java/api/printEvents/PrintEventsTests.java index 0d8b03bce7..17e4b5d9c3 100644 --- a/src/test/java/api/printEvents/PrintEventsTests.java +++ b/src/test/java/api/printEvents/PrintEventsTests.java @@ -14,7 +14,7 @@ import static api.support.http.InterfaceUrls.printEventsUrl; import static api.support.matchers.ResponseStatusCodeMatcher.hasStatus; -import static org.folio.HttpStatus.HTTP_CREATED; +import static org.folio.HttpStatus.HTTP_NO_CONTENT; import static org.folio.HttpStatus.HTTP_UNPROCESSABLE_ENTITY; import static org.hamcrest.MatcherAssert.assertThat; @@ -28,9 +28,8 @@ void postPrintEventsTest() { .withValue(new JsonObject().put("enablePrintLog", true))); JsonObject printRequest = getPrintEvent(); printRequest.put("requestIds", createOneHundredRequests()); - System.out.println(printRequest.getString("requestIds")); Response response = restAssuredClient.post(printRequest, printEventsUrl("/print-events-entry"), "post-print-event"); - assertThat(response, hasStatus(HTTP_CREATED)); + assertThat(response, hasStatus(HTTP_NO_CONTENT)); } @Test diff --git a/src/test/java/api/requests/RequestsAPICreationTests.java b/src/test/java/api/requests/RequestsAPICreationTests.java index 0595792bd6..24a3bc3a9d 100644 --- a/src/test/java/api/requests/RequestsAPICreationTests.java +++ b/src/test/java/api/requests/RequestsAPICreationTests.java @@ -16,6 +16,7 @@ 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; @@ -107,6 +108,7 @@ 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; @@ -194,6 +196,7 @@ public class RequestsAPICreationTests extends APITests { public void afterEach() { mockClockManagerToReturnDefaultDateTime(); configurationsFixture.deleteTlrFeatureConfig(); + circulationSettingsClient.deleteAll(); } @Test @@ -4859,6 +4862,123 @@ 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()); + } private void setUpNoticesForTitleLevelRequests(boolean isNoticeEnabledInTlrSettings, boolean isNoticeEnabledInNoticePolicy) { @@ -5190,4 +5310,52 @@ 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/support/fakes/FakeOkapi.java b/src/test/java/api/support/fakes/FakeOkapi.java index 1ed5b8f5b0..c146982ce8 100644 --- a/src/test/java/api/support/fakes/FakeOkapi.java +++ b/src/test/java/api/support/fakes/FakeOkapi.java @@ -427,6 +427,8 @@ 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 new file mode 100644 index 0000000000..aaeced3434 --- /dev/null +++ b/src/test/java/api/support/fakes/FakePrintEventStatusModule.java @@ -0,0 +1,83 @@ +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/org/folio/circulation/domain/RequestQueueTests.java b/src/test/java/org/folio/circulation/domain/RequestQueueTests.java index 345472b75e..b6c02f33df 100644 --- a/src/test/java/org/folio/circulation/domain/RequestQueueTests.java +++ b/src/test/java/org/folio/circulation/domain/RequestQueueTests.java @@ -102,7 +102,7 @@ private static Request buildRequest(int position, RequestStatus status, String r .put("position", position); return new Request(null, null, json, null, null, null, null, null, null, null, null, null, - null, false, null, false); + null, false, null, false, null); } private static String randomId() {