diff --git a/descriptors/ModuleDescriptor-template.json b/descriptors/ModuleDescriptor-template.json index f4f17887..f3cf1ef2 100644 --- a/descriptors/ModuleDescriptor-template.json +++ b/descriptors/ModuleDescriptor-template.json @@ -179,7 +179,7 @@ }, { "id": "request-storage", - "version": "6.0", + "version": "6.1", "handlers": [ { "methods": ["GET"], diff --git a/ramls/request.json b/ramls/request.json index aa582e03..17b591a4 100644 --- a/ramls/request.json +++ b/ramls/request.json @@ -221,6 +221,30 @@ "description": "Tags", "$ref": "raml-util/schemas/tags.schema" }, + "printDetails": { + "type": "object", + "description": "PrintDetails", + "properties": { + "printCount": { + "type": "integer", + "description": "Number of times print slip generated." + }, + "requesterId": { + "type": "string", + "description": "UUID of print slip requester." + }, + "isPrinted": { + "type": "boolean", + "description": "Whether print slip was printed in past." + }, + "printEventDate": { + "type": "string", + "format": "date-time", + "description": "Date and time when print slip was generated last time." + } + }, + "additionalProperties": false + }, "awaitingPickupRequestClosedDate": { "description": "A date when the request with awaiting pickup status was closed", "type": "string", diff --git a/src/main/java/org/folio/rest/impl/PrintEventsApi.java b/src/main/java/org/folio/rest/impl/PrintEventsApi.java index 023de7d0..fc707b13 100644 --- a/src/main/java/org/folio/rest/impl/PrintEventsApi.java +++ b/src/main/java/org/folio/rest/impl/PrintEventsApi.java @@ -13,8 +13,6 @@ import javax.ws.rs.core.Response; import java.util.Map; -import static io.vertx.core.Future.succeededFuture; - public class PrintEventsApi implements PrintEventsStorage { private static final Logger LOG = LoggerFactory.getLogger(PrintEventsApi.class); @@ -22,9 +20,7 @@ public class PrintEventsApi implements PrintEventsStorage { public void postPrintEventsStoragePrintEventsEntry(PrintEventsRequest printEventsRequest, Map okapiHeaders, Handler> asyncResultHandler, Context vertxContext) { LOG.info("postPrintEventsStoragePrintEvents:: save print events {}", printEventsRequest); new PrintEventsService(vertxContext, okapiHeaders) - .create(printEventsRequest) - .onSuccess(response -> asyncResultHandler.handle(succeededFuture(response))) - .onFailure(throwable -> asyncResultHandler.handle(succeededFuture(PostPrintEventsStoragePrintEventsEntryResponse.respond500WithTextPlain(throwable.getMessage())))); + .create(printEventsRequest, asyncResultHandler); } @Override diff --git a/src/main/java/org/folio/service/PrintEventsService.java b/src/main/java/org/folio/service/PrintEventsService.java index be083823..d1e6b634 100644 --- a/src/main/java/org/folio/service/PrintEventsService.java +++ b/src/main/java/org/folio/service/PrintEventsService.java @@ -2,7 +2,6 @@ import io.vertx.core.AsyncResult; import io.vertx.core.Context; -import io.vertx.core.Future; import io.vertx.core.Handler; import io.vertx.sqlclient.Row; import io.vertx.sqlclient.RowSet; @@ -14,28 +13,31 @@ import org.folio.rest.model.PrintEvent; import org.folio.rest.persist.PgUtil; import org.folio.rest.persist.PostgresClient; +import org.folio.rest.tools.utils.MetadataUtil; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.ws.rs.core.Response; +import java.text.SimpleDateFormat; import java.time.ZoneOffset; import java.util.ArrayList; import java.util.Date; import java.util.List; import java.util.Map; +import java.util.TimeZone; import java.util.stream.Collectors; import static io.vertx.core.Future.succeededFuture; import static org.folio.rest.persist.PgUtil.postgresClient; import static org.folio.rest.persist.PostgresClient.convertToPsqlStandard; import static org.folio.support.ModuleConstants.PRINT_EVENTS_TABLE; +import static org.folio.support.ModuleConstants.REQUEST_TABLE; public class PrintEventsService { private static final Logger LOG = LoggerFactory.getLogger(PrintEventsService.class); - private static final int MAX_ENTITIES = 10000; - private final Context vertxContext; private final Map okapiHeaders; + private final PostgresClient postgresClient; private static final String PRINT_EVENT_FETCH_QUERY = """ WITH cte AS ( @@ -53,16 +55,44 @@ ORDER BY (jsonb->>'printEventDate')::timestamptz DESC) AS rank rank = 1; """; + private static String requestPrintSyncQueryString = """ + WITH print_counts AS ( + SELECT + jsonb->>'requestId' AS request_id, + COUNT(*) AS print_count + FROM %s + WHERE jsonb->>'requestId' IN (%s) + GROUP BY jsonb->>'requestId' + ) + UPDATE %s + SET jsonb = + (jsonb + || jsonb_build_object( + 'printDetails', + jsonb_build_object( + 'printCount', print_counts.print_count, + 'requesterId', %s, + 'isPrinted', true, + 'printEventDate', %s + ) + ) + ) + FROM print_counts + WHERE id = print_counts.request_id::uuid; + """; + public PrintEventsService(Context vertxContext, Map okapiHeaders) { - this.vertxContext = vertxContext; this.okapiHeaders = okapiHeaders; + this.postgresClient = PgUtil.postgresClient(vertxContext, okapiHeaders); } - public Future create(PrintEventsRequest printEventRequest) { + public void create(PrintEventsRequest printEventRequest, + Handler> asyncResultHandler) { LOG.info("create:: save print events {}", printEventRequest); - List printEvents = printEventRequest.getRequestIds().stream().map(requestId -> { + List printEvents = + printEventRequest.getRequestIds().stream().map(requestId -> { PrintEvent event = new PrintEvent(); event.setRequestId(requestId); event.setRequesterId(printEventRequest.getRequesterId()); @@ -70,14 +100,55 @@ public Future create(PrintEventsRequest printEventRequest) { event.setPrintEventDate(printEventRequest.getPrintEventDate()); return event; }).toList(); - return PgUtil.postSync(PRINT_EVENTS_TABLE, printEvents, MAX_ENTITIES, false, okapiHeaders, vertxContext, - PrintEventsStorage.PostPrintEventsStoragePrintEventsEntryResponse.class); + try { + MetadataUtil.populateMetadata(printEvents, okapiHeaders); + } catch (Exception e) { + String msg = + "Cannot populate metadata of printEvents list elements: " + e.getMessage(); + LOG.error(msg, e); + asyncResultHandler.handle(succeededFuture(PrintEventsStorage.PostPrintEventsStoragePrintEventsEntryResponse.respond500WithTextPlain(msg))); + return; + } + + postgresClient.withTrans(conn -> conn.saveBatch(PRINT_EVENTS_TABLE, + printEvents) + .compose(printEventsResult -> conn.execute( + buildRequestSyncQuery(printEventRequest, okapiHeaders) + ))).onFailure(handler -> + asyncResultHandler.handle( + succeededFuture(PrintEventsStorage.PostPrintEventsStoragePrintEventsEntryResponse.respond500WithTextPlain(handler.getMessage())) + ) + ).onSuccess(handler -> + asyncResultHandler.handle( + succeededFuture(PrintEventsStorage.PostPrintEventsStoragePrintEventsEntryResponse.respond201()) + )); + } + + private String buildRequestSyncQuery(PrintEventsRequest printEventRequest, + Map okapiHeaders) { + SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ"); + df.setTimeZone(TimeZone.getTimeZone(ZoneOffset.UTC)); + + String tenantId = okapiHeaders.get(RestVerticle.OKAPI_HEADER_TENANT); + String printEventTableName = + convertToPsqlStandard(tenantId) + "." + PRINT_EVENTS_TABLE; + String requestTableName = + convertToPsqlStandard(tenantId) + "." + REQUEST_TABLE; + String requestIds = printEventRequest.getRequestIds().stream() + .map(requestId -> "'" + requestId + "'") + .collect(Collectors.joining(", ")); + String requesterId = "'" + printEventRequest.getRequesterId() + "'"; + String printEventDate = + "'" + df.format(printEventRequest.getPrintEventDate()) + "'"; + + return requestPrintSyncQueryString.formatted(printEventTableName, + requestIds, requestTableName, requesterId, + printEventDate); } public void getPrintEventRequestDetails(List requestIds, Handler> asyncResultHandler) { LOG.debug("getPrintEventRequestDetails:: Fetching print event details for requestIds {}", requestIds); String tenantId = okapiHeaders.get(RestVerticle.OKAPI_HEADER_TENANT); - PostgresClient postgresClient = postgresClient(vertxContext, okapiHeaders); postgresClient.execute(formatQuery(tenantId, requestIds), handler -> { try { if (handler.succeeded()) { diff --git a/src/test/java/org/folio/rest/api/PrintEventsAPITest.java b/src/test/java/org/folio/rest/api/PrintEventsAPITest.java index c7f93248..2ea8445b 100644 --- a/src/test/java/org/folio/rest/api/PrintEventsAPITest.java +++ b/src/test/java/org/folio/rest/api/PrintEventsAPITest.java @@ -15,6 +15,7 @@ import static org.folio.rest.support.http.InterfaceUrls.printEventsUrl; import static org.folio.rest.support.matchers.HttpResponseStatusCodeMatchers.isCreated; +import static org.folio.rest.support.matchers.HttpResponseStatusCodeMatchers.isInternalServerError; import static org.folio.rest.support.matchers.HttpResponseStatusCodeMatchers.isOk; import static org.folio.rest.support.matchers.HttpResponseStatusCodeMatchers.isUnprocessableEntity; import static org.hamcrest.core.Is.is; @@ -180,6 +181,18 @@ public void getPrintEventStatusWithInvalidRequestIds() throws MalformedURLExcept assertThat(jsonObject.getJsonArray("printEventsStatusResponses").size(), is(0)); } + @Test + public void createPrintEventLogAndValidate5XX() throws MalformedURLException, + ExecutionException, InterruptedException { + JsonObject printEventsJson = getPrintEvent(); + final CompletableFuture postCompleted = new CompletableFuture<>(); + client.post(printEventsUrl("/print-events-entry"), printEventsJson, + "INVALID_TENANT_ID", + ResponseHandler.json(postCompleted)); + final JsonResponse postResponse = postCompleted.get(); + assertThat(postResponse, isInternalServerError()); + } + private JsonObject getPrintEvent() { List requestIds = List.of("5f5751b4-e352-4121-adca-204b0c2aec43", "5f5751b4-e352-4121-adca-204b0c2aec44"); return new JsonObject()