diff --git a/descriptors/ModuleDescriptor-template.json b/descriptors/ModuleDescriptor-template.json
index 194061617..e26debc38 100644
--- a/descriptors/ModuleDescriptor-template.json
+++ b/descriptors/ModuleDescriptor-template.json
@@ -1003,6 +1003,19 @@
}
]
},
+ {
+ "id": "orders.routing-list",
+ "version": "1.0",
+ "handlers": [
+ {
+ "methods": ["POST"],
+ "pathPattern": "/orders/routing-lists/{id}/process-template",
+ "permissionsRequired": [
+ "orders.routing-list.item.post"
+ ]
+ }
+ ]
+ },
{
"id": "_jsonSchemas",
"version": "1.0",
diff --git a/ramls/routing-lists.raml b/ramls/routing-lists.raml
index 2f2aa02c1..3593957bf 100644
--- a/ramls/routing-lists.raml
+++ b/ramls/routing-lists.raml
@@ -8,8 +8,8 @@ documentation:
content: CRUD API to manage routing lists.
types:
- title: !include acq-models/mod-orders-storage/schemas/routing_list.json
- title_collection: !include acq-models/mod-orders-storage/schemas/routing_list_collection.json
+ routing_list: !include acq-models/mod-orders-storage/schemas/routing_list.json
+ routing_list_collection: !include acq-models/mod-orders-storage/schemas/routing_list_collection.json
UUID:
type: string
pattern: ^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-5][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$
@@ -19,8 +19,8 @@ traits:
searchable: !include raml-util/traits/searchable.raml
resourceTypes:
- collection: !include raml-util/rtypes/collection.raml
- collection-item: !include raml-util/rtypes/item-collection.raml
+ collection: !include rtypes/collection-with-json-response.raml
+ collection-item: !include rtypes/item-collection-with-json-response.raml
/orders/routing-lists:
type:
@@ -40,9 +40,5 @@ resourceTypes:
id:
description: The UUID of a Title
type: UUID
- type:
- collection-item:
- exampleItem: !include acq-models/mod-orders-storage/examples/routing_list_get.sample
- schema: routing_list
post:
description: Execute mod-template-engine to process templates with replaced token placeholders [update]
diff --git a/src/main/java/org/folio/models/template/TemplateProcessingRequest.java b/src/main/java/org/folio/models/template/TemplateProcessingRequest.java
new file mode 100644
index 000000000..51dcd575f
--- /dev/null
+++ b/src/main/java/org/folio/models/template/TemplateProcessingRequest.java
@@ -0,0 +1,66 @@
+package org.folio.models.template;
+
+import java.util.List;
+import java.util.UUID;
+
+import lombok.Getter;
+
+@Getter
+public class TemplateProcessingRequest {
+ private UUID templateId;
+ private String lang;
+ private String outputFormat;
+ private Context context;
+
+ public TemplateProcessingRequest withTemplateId(UUID templateId) {
+ this.templateId = templateId;
+ return this;
+ }
+
+ public TemplateProcessingRequest withLang(String lang) {
+ this.lang = lang;
+ return this;
+ }
+
+ public TemplateProcessingRequest withOutputFormat(String outputFormat) {
+ this.outputFormat = outputFormat;
+ return this;
+ }
+
+ public TemplateProcessingRequest withContext(Context context) {
+ this.context = context;
+ return this;
+ }
+
+ @Getter
+ public static class Context {
+ private List users;
+ private List- items;
+
+ public Context withUsers(List users) {
+ this.users = users;
+ return this;
+ }
+
+ }
+
+ @Getter
+ public static class User {
+ private String name;
+ public User withName(String name) {
+ this.name = name;
+ return this;
+ }
+ }
+
+ @Getter
+ public static class Item {
+ private String name;
+
+ public Item setName(String name) {
+ this.name = name;
+ return this;
+ }
+ }
+}
+
diff --git a/src/main/java/org/folio/orders/utils/ResourcePathResolver.java b/src/main/java/org/folio/orders/utils/ResourcePathResolver.java
index 50b3da277..c03ffcdfc 100644
--- a/src/main/java/org/folio/orders/utils/ResourcePathResolver.java
+++ b/src/main/java/org/folio/orders/utils/ResourcePathResolver.java
@@ -28,6 +28,9 @@ private ResourcePathResolver() {
public static final String PAYMENT_STATUS = "paymentStatus";
public static final String ORDER_TEMPLATES = "orderTemplates";
public static final String TITLES = "titles";
+ public static final String ROUTING_LISTS = "routing-lists";
+ public static final String TEMPLATE_REQUEST = "template-request";
+ public static final String USERS = "users";
public static final String FUNDS = "finance.funds";
public static final String BUDGETS = "finance.budgets";
public static final String LEDGERS = "finance.ledgers";
@@ -66,6 +69,8 @@ private ResourcePathResolver() {
apis.put(RECEIVING_HISTORY, "/orders-storage/receiving-history");
apis.put(PO_LINE_NUMBER, "/orders-storage/po-line-number");
apis.put(ORDER_TEMPLATES, "/orders-storage/order-templates");
+ apis.put(ROUTING_LISTS, "/orders-storage/routing-lists");
+ apis.put(TEMPLATE_REQUEST, "/template-request");
apis.put(FUNDS, "/finance/funds");
apis.put(BUDGETS, "/finance/budgets");
apis.put(LEDGERS, "/finance-storage/ledgers");
@@ -86,6 +91,7 @@ private ResourcePathResolver() {
apis.put(ORDER_INVOICE_RELATIONSHIP, "/orders-storage/order-invoice-relns");
apis.put(EXPORT_HISTORY, "/orders-storage/export-history");
apis.put(TAGS, "/tags");
+ apis.put(USERS, "/users");
SUB_OBJECT_COLLECTION_APIS = Collections.unmodifiableMap(apis);
SUB_OBJECT_ITEM_APIS = Collections.unmodifiableMap(
diff --git a/src/main/java/org/folio/rest/core/RestClient.java b/src/main/java/org/folio/rest/core/RestClient.java
index 17a787c98..14d846ca5 100644
--- a/src/main/java/org/folio/rest/core/RestClient.java
+++ b/src/main/java/org/folio/rest/core/RestClient.java
@@ -10,13 +10,6 @@
import java.util.Map;
-import org.apache.logging.log4j.LogManager;
-import org.apache.logging.log4j.Logger;
-import org.folio.okapi.common.WebClientFactory;
-import org.folio.rest.core.exceptions.HttpException;
-import org.folio.rest.core.models.RequestContext;
-import org.folio.rest.core.models.RequestEntry;
-
import io.vertx.core.Context;
import io.vertx.core.Future;
import io.vertx.core.MultiMap;
@@ -29,6 +22,12 @@
import io.vertx.ext.web.client.WebClientOptions;
import io.vertx.ext.web.client.predicate.ErrorConverter;
import io.vertx.ext.web.client.predicate.ResponsePredicate;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.folio.okapi.common.WebClientFactory;
+import org.folio.rest.core.exceptions.HttpException;
+import org.folio.rest.core.models.RequestContext;
+import org.folio.rest.core.models.RequestEntry;
public class RestClient {
diff --git a/src/main/java/org/folio/rest/impl/RoutingListAPI.java b/src/main/java/org/folio/rest/impl/RoutingListAPI.java
new file mode 100644
index 000000000..145bf7234
--- /dev/null
+++ b/src/main/java/org/folio/rest/impl/RoutingListAPI.java
@@ -0,0 +1,39 @@
+package org.folio.rest.impl;
+
+import javax.ws.rs.core.Response;
+import java.util.Map;
+
+import io.vertx.core.AsyncResult;
+import io.vertx.core.Context;
+import io.vertx.core.Handler;
+import org.apache.commons.lang.NotImplementedException;
+import org.folio.rest.core.models.RequestContext;
+import org.folio.rest.jaxrs.model.RoutingList;
+import org.folio.rest.jaxrs.resource.OrdersRoutingLists;
+import org.folio.service.routinglist.RoutingListService;
+import org.springframework.beans.factory.annotation.Autowired;
+
+public class RoutingListAPI extends BaseApi implements OrdersRoutingLists {
+
+ @Autowired
+ private RoutingListService routingListService;
+
+ @Override
+ public void getOrdersRoutingLists(String query, String totalRecords, int offset, int limit, Map okapiHeaders,
+ Handler> asyncResultHandler, Context vertxContext) {
+ throw new NotImplementedException();
+ }
+
+ @Override
+ public void postOrdersRoutingLists(RoutingList entity, Map okapiHeaders,
+ Handler> asyncResultHandler, Context vertxContext) {
+ throw new NotImplementedException();
+ }
+
+ @Override
+ public void postOrdersRoutingListsProcessTemplateById(String id, Map okapiHeaders,
+ Handler> asyncResultHandler, Context vertxContext) {
+ routingListService.processTemplateEngine(id, new RequestContext(vertxContext, okapiHeaders))
+ .onFailure(t -> handleErrorResponse(asyncResultHandler, t));
+ }
+}
diff --git a/src/main/java/org/folio/service/UserService.java b/src/main/java/org/folio/service/UserService.java
index d19d99f65..017c8e355 100644
--- a/src/main/java/org/folio/service/UserService.java
+++ b/src/main/java/org/folio/service/UserService.java
@@ -1,12 +1,37 @@
package org.folio.service;
+import static org.folio.orders.utils.HelperUtils.convertIdsToCqlQuery;
+import static org.folio.orders.utils.ResourcePathResolver.USERS;
+import static org.folio.orders.utils.ResourcePathResolver.resourcesPath;
import static org.folio.rest.RestVerticle.OKAPI_USERID_HEADER;
+import java.util.List;
import java.util.Map;
+import io.vertx.core.Future;
+import io.vertx.core.json.JsonObject;
+import org.folio.rest.core.RestClient;
+import org.folio.rest.core.models.RequestContext;
+import org.folio.rest.core.models.RequestEntry;
+
public class UserService {
+ private static final String ENDPOINT = resourcesPath(USERS);
+ private final RestClient restClient;
+
+ public UserService(RestClient restClient) {
+ this.restClient = restClient;
+ }
public static String getCurrentUserId(Map okapiHeaders) {
return okapiHeaders.get(OKAPI_USERID_HEADER);
}
+
+ public Future getUsersByIds(List userIds, RequestContext requestContext) {
+ var requestEntry = new RequestEntry(USERS)
+ .withOffset(0)
+ .withLimit(Integer.MAX_VALUE)
+ .withQuery(convertIdsToCqlQuery(userIds, "sourceInvoiceId"));
+
+ return restClient.get(requestEntry, JsonObject.class, requestContext);
+ }
}
diff --git a/src/main/java/org/folio/service/routinglist/RoutingListService.java b/src/main/java/org/folio/service/routinglist/RoutingListService.java
new file mode 100644
index 000000000..c6e10fd29
--- /dev/null
+++ b/src/main/java/org/folio/service/routinglist/RoutingListService.java
@@ -0,0 +1,76 @@
+package org.folio.service.routinglist;
+
+import static org.folio.orders.utils.ResourcePathResolver.ROUTING_LISTS;
+import static org.folio.orders.utils.ResourcePathResolver.TEMPLATE_REQUEST;
+import static org.folio.orders.utils.ResourcePathResolver.resourcesPath;
+
+import java.util.List;
+import java.util.UUID;
+
+import io.vertx.core.Future;
+import io.vertx.core.json.JsonObject;
+import lombok.extern.log4j.Log4j2;
+import org.folio.models.template.TemplateProcessingRequest;
+import org.folio.rest.core.RestClient;
+import org.folio.rest.core.models.RequestContext;
+import org.folio.rest.core.models.RequestEntry;
+import org.folio.rest.jaxrs.model.RoutingList;
+import org.folio.service.UserService;
+
+@Log4j2
+public class RoutingListService {
+
+ private static final String ENDPOINT = resourcesPath(ROUTING_LISTS);
+ private static final String BY_ID_ENDPOINT = ENDPOINT + "/{id}";
+ private final RestClient restClient;
+ private final UserService userService;
+
+ public RoutingListService(RestClient restClient, UserService userService) {
+ this.restClient = restClient;
+ this.userService = userService;
+ }
+
+ public Future processTemplateEngine(String id, RequestContext requestContext) {
+ return getRoutingListById(id, requestContext)
+ .compose(routingList -> fetchUsersAndCreateTemplate(routingList, requestContext))
+ .compose(templateProcessingRequest -> postTemplateRequest(templateProcessingRequest, requestContext));
+ }
+
+ public Future getRoutingListById(String routingListId, RequestContext requestContext) {
+ var requestEntry = new RequestEntry(BY_ID_ENDPOINT).withId(routingListId);
+ return restClient.get(requestEntry, RoutingList.class, requestContext);
+ }
+
+ private Future fetchUsersAndCreateTemplate(RoutingList routingList, RequestContext requestContext) {
+ return userService.getUsersByIds(routingList.getUserIds(), requestContext)
+ .map(users -> createTemplateRequest(routingList, users));
+ }
+
+ private TemplateProcessingRequest createTemplateRequest(RoutingList routingList, JsonObject users) {
+ var templateRequest =createBaseTemplateRequest();
+ var userListForContext = createUserListForContext(users);
+ var context = new TemplateProcessingRequest.Context().withUsers(userListForContext);
+ templateRequest.withContext(context);
+ return templateRequest;
+ }
+
+ private TemplateProcessingRequest createBaseTemplateRequest() {
+ return new TemplateProcessingRequest()
+ .withTemplateId(UUID.randomUUID())
+ .withLang("en")
+ .withOutputFormat("text/plain");
+ }
+
+ private List createUserListForContext(JsonObject users) {
+ return users.getJsonArray("users").stream()
+ .map(JsonObject.class::cast)
+ .map(user -> new TemplateProcessingRequest.User()
+ .withName(user.getJsonObject("personal").getString("firstName"))
+ )
+ .toList();
+ }
+
+ private Future postTemplateRequest(TemplateProcessingRequest templateProcessingRequest, RequestContext requestContext) {
+ return restClient.post(TEMPLATE_REQUEST, JsonObject.mapFrom(templateProcessingRequest), JsonObject.class, requestContext);
+ }
+}
diff --git a/src/test/java/org/folio/rest/impl/MockServer.java b/src/test/java/org/folio/rest/impl/MockServer.java
index 58626f43a..dc4c9220b 100644
--- a/src/test/java/org/folio/rest/impl/MockServer.java
+++ b/src/test/java/org/folio/rest/impl/MockServer.java
@@ -242,6 +242,8 @@ public class MockServer {
private static final String HOLDINGS_SOURCE_MOCK_DATA_PATH = BASE_MOCK_DATA_PATH + "holdingsSources/";
public static final String PIECE_RECORDS_MOCK_DATA_PATH = BASE_MOCK_DATA_PATH + "pieces/";
public static final String PO_LINES_MOCK_DATA_PATH = BASE_MOCK_DATA_PATH + "lines/";
+ public static final String ROUTING_LIST_MOCK_DATA_PATH = BASE_MOCK_DATA_PATH + "routingLists/";
+ public static final String USERS_MOCK_DATA_PATH = BASE_MOCK_DATA_PATH + "users/";
public static final String TITLES_MOCK_DATA_PATH = BASE_MOCK_DATA_PATH + "titles/";
private static final String ACQUISITIONS_UNITS_MOCK_DATA_PATH = BASE_MOCK_DATA_PATH + "acquisitionsUnits/units";
private static final String ORDER_TEMPLATES_MOCK_DATA_PATH = BASE_MOCK_DATA_PATH + "orderTemplates/";
diff --git a/src/test/java/org/folio/service/routinglist/RoutingListServiceTest.java b/src/test/java/org/folio/service/routinglist/RoutingListServiceTest.java
new file mode 100644
index 000000000..23b620f77
--- /dev/null
+++ b/src/test/java/org/folio/service/routinglist/RoutingListServiceTest.java
@@ -0,0 +1,66 @@
+package org.folio.service.routinglist;
+
+import static io.vertx.core.Future.succeededFuture;
+import static org.folio.TestUtils.getMockData;
+import static org.folio.rest.impl.MockServer.ROUTING_LIST_MOCK_DATA_PATH;
+import static org.folio.rest.impl.MockServer.USERS_MOCK_DATA_PATH;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doReturn;
+
+import java.io.IOException;
+
+import io.vertx.core.Future;
+import io.vertx.core.json.JsonObject;
+import io.vertx.junit5.VertxExtension;
+import io.vertx.junit5.VertxTestContext;
+import org.folio.rest.core.RestClient;
+import org.folio.rest.core.models.RequestContext;
+import org.folio.rest.core.models.RequestEntry;
+import org.folio.rest.jaxrs.model.RoutingList;
+import org.folio.service.UserService;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@ExtendWith(VertxExtension.class)
+public class RoutingListServiceTest {
+
+ private static final String ROUTING_LIST_ID = "eee951de-ea49-400a-96e8-705ae5a1e1e8";
+ @InjectMocks
+ RoutingListService routingListService;
+ @Mock
+ private RestClient restClient;
+ @Mock
+ private UserService userService;
+ @Mock
+ private RequestContext requestContextMock;
+
+ @BeforeEach
+ public void initMocks() {
+ MockitoAnnotations.openMocks(this);
+ }
+
+ @Test
+ void processTemplate(VertxTestContext vertxTestContext) throws IOException {
+ var routingList = new JsonObject(getMockData(ROUTING_LIST_MOCK_DATA_PATH + ROUTING_LIST_ID + ".json")).mapTo(RoutingList.class);
+ var users = new JsonObject(getMockData(USERS_MOCK_DATA_PATH + "user_collection.json"));
+
+ doReturn(succeededFuture(routingList)).when(restClient).get(any(RequestEntry.class), eq(RoutingList.class), any(RequestContext.class));
+ doReturn(succeededFuture(users)).when(userService).getUsersByIds(eq(routingList.getUserIds()), any(RequestContext.class));
+ doReturn(succeededFuture(new JsonObject())).when(restClient).post(anyString(), any(), eq(JsonObject.class), any());
+
+ Future future = routingListService.processTemplateEngine(ROUTING_LIST_ID, requestContextMock);
+
+ vertxTestContext.assertComplete(future)
+ .onComplete(result -> {
+ Assertions.assertTrue(result.succeeded());
+ vertxTestContext.completeNow();
+ });
+ }
+}
diff --git a/src/test/resources/mockdata/routingLists/eee951de-ea49-400a-96e8-705ae5a1e1e8.json b/src/test/resources/mockdata/routingLists/eee951de-ea49-400a-96e8-705ae5a1e1e8.json
new file mode 100644
index 000000000..d89742eb8
--- /dev/null
+++ b/src/test/resources/mockdata/routingLists/eee951de-ea49-400a-96e8-705ae5a1e1e8.json
@@ -0,0 +1,11 @@
+{
+ "id": "c0d13648-347b-4ac9-8c2f-5bc47248b87e",
+ "_version": 1,
+ "name": "Mars expedition",
+ "notes": "Future note",
+ "userIds": [
+ "d926d900-e27d-46d6-bba8-31e9d5c2cf44",
+ "077274ad-6b4f-4c28-9779-6d381e7a1ca1"
+ ],
+ "poLineId": "8343e5a0-fed8-11e8-8eb2-f2801f1b9fd1"
+}
diff --git a/src/test/resources/mockdata/users/user_collection.json b/src/test/resources/mockdata/users/user_collection.json
new file mode 100644
index 000000000..2473888ff
--- /dev/null
+++ b/src/test/resources/mockdata/users/user_collection.json
@@ -0,0 +1,41 @@
+{
+ "users": [
+ {
+ "username": "mockuser8",
+ "id": "d926d900-e27d-46d6-bba8-31e9d5c2cf44",
+ "active": true,
+ "type": "staff",
+ "meta": {
+ "creation_date": "2016-11-05T0723",
+ "last_login_date": ""
+ },
+ "personal": {
+ "firstName": "Albert",
+ "lastName": "Einstein",
+ "email": "albert@si.edu",
+ "phone": "927-306-2327"
+ }
+ },
+ {
+ "username": "mockuser9",
+ "id": "077274ad-6b4f-4c28-9779-6d381e7a1ca1",
+ "active": true,
+ "type": "staff",
+ "meta": {
+ "creation_date": "2016-11-05T0723",
+ "last_login_date": ""
+ },
+ "personal": {
+ "lastName": "Mockerson",
+ "middleName": "M.",
+ "firstName": "Mickey",
+ "email": "mock@biglibrary.org",
+ "phone": "2125551212",
+ "mobilePhone": "112233"
+ },
+ "externalSystemId": "123",
+ "barcode": "test123"
+ }
+ ],
+ "totalRecords": 2
+}