# API GraphQL de GO!Manage La API GraphQL de GO!Manage provee una interfaz de acceso a datos más precisa y flexible que la API REST. Para desarrollar contra la API GraphQL necesitará tener un [conocimiento básico de GraphQL](https://graphql.org/learn/). El servidor GraphQL de GO!Manage implementa la [versión de octubre de 2021 de la especificación de GraphQL](https://spec.graphql.org/October2021/). En este documento se explica como acceder a la API y se dan algunos consejos y ejemplos para su buen uso. ### Entornos de ejecución Todas las instalaciones de GO!Manage disponen de la API GraphQL en el servidor PASOE, ejecutándose junto al resto de APIs. Si en su instalación así se ha dispuesto también puede tener un entorno independiente basado en IIS, en el que solo estará la API GraphQL documentada en esta página. A pesar de que la funcionalidad de GraphQL es, a grandes rasgos, la misma en ambos entornos, existen algunas diferencias en cuanto a las URLs y al sistema de autenticación. ### Obtención del token El acceso a la API requiere autentificación por medio de un token enviado en la cabecera de las peticiones. Dicho token se debe obtener en el siguiente endpoint: > **PASOE:** POST _{host}_/gomanage/static/auth/j_spring_security_check > **IIS:** POST _{host}_/token Para las cabeceras y cuerpo de la petición de autenticación en entorno **PASOE**, ver [las instrucciones generales de la API](https://apidoc.telematel.com/doc/gomanage). El mismo token de la API general sirve para GraphQL. En **IIS** hay que realizar una petición POST al endpoint indicado, con las siguientes cabeceras: Content-Type: application/json Accept: application/json En el cuerpo de la petición enviar lo siguiente: ```json { "name": "{Nombre del usuario}", "password": "{Contraseña del usuario}" } ``` La respuesta tendrá la siguiente forma: ```json { "access_token": "{Token de acceso}", "token_type": "Bearer", "expires_in": 3600, "expires": "2022-10-18T09:23:16.0127919Z" } ``` En las siguientes peticiones a los endpoints de GraphQL hay que añadir la cabecera: Authorization: Bearer {token recibido} Los tokens de PASOE y IIS no son intercambiables entre ellos. ## Endpoint GraphQL > **PASOE:** _{host}_/gomanage/web/data/graphql > **IIS:** _{host}_/graphql El endpoint GraphQL es el que recibe y procesa las consultas. Es a este endpoint al que hay que apuntar desde la herramienta que usemos para diseñar las consultas. Requiere autentificación con el token antes obtenido. El formato de transporte para las consultas es el recomendado en las [guidelines oficiales](https://graphql.org/learn/serving-over-http/#post-request). ### Entornos gráficos Desde el servidor se puede acceder a dos entornos gráficos como herramienta para el diseño de consultas. En ambos entornos graficos se puede consultar **el modelo de datos de las tablas publicadas en Go!Manage** accediendo desde el botón de la barra lateral **Explorer**: **GraphiQL:** > **PASOE:** {host}/gomanage/web/data/graphql/graphiql > **IIS:** {host}/ui/graphiql **Apollo Sandbox:** > **PASOE:** _{host}_/gomanage/web/data/graphql/apollo > **IIS:** _{host}_/ui/apollo Ambas URLs requieren autentificación, aunque el envío del token se gestiona de forma automática. Es posible usar cualquier otra herramienta compatible con GraphQL, como Postman o Insomnia, apuntándolo al endpoint general de GraphQL. En este caso habrá que solicitar el token de forma manual. El endpoint de autenticación también devuelve una cookie que estas herramientas pueden consumir de forma automática. ## Documentación general del modelo de datos ### ¿Cómo se pueden consultar las tablas publicadas en Go!Manage? Para consultar las tablas publicadas deberán acceder a la url del entorno gráfico de GraphiQL o Apollo Sandbox comentado anteriormente. El esquema GraphQL publicado permite acceder a la información mediante consultas que devuelven un único registro, o varios. Las que devuelven un solo registro suelen tener su nombre en singular. Con la introspección del modelo de GraphQL o con una herramienta gráfica puede determinar exactamente el tipo de dato devuelto. Para conocer la relación de tablas y campos de GO!Manage accesibles por GraphQL, puede consultar este <a href="https://s3.eu-west-1.amazonaws.com/updates.telematel.com/documentacion/TablasGrahpQL.xlsx" download><u>**fichero**</u></a>. ### Vistas de un solo registro En las vistas de un solo registro, el filtrado se realiza por el atributo _by_. En este atributo pueden haber definidos varios criterios de filtrado. Hay que informar solo uno de los criterios, informando obligatoriamente todos los subcampos. Por ejemplo, al localizar un albarán de venta dispone de los siguientes criterios: ``` ▾ delivery_note ▾ by*: ▾ key: ⚬ company_id*: ⚬ branch_id*: ⚬ sales_delivery_note_type*: ⚬ sales_delivery_note_id*: ▾ reference: ⚬ company_id*: ⚬ branch_id*: ⚬ reference*: ⚬ unique_id: ``` Puede filtrar: * Por la clave primaria de la tabla (_key_), informando el código de la empresa, delegación, tipo y número de albarán. * Por referencia (_reference_), informando la empresa, delegación y referencia del documento. * Por identificador único (*unique_id*). Tendrá que seleccionar uno de los tres criterios e informar todos los subcampos en el mismo. ### Vistas de múltiples registros En las vistas que pueden devolver múltiples registros se puede filtrar por el atributo _where_. En este caso es posible usar varios criterios de filtrado a la vez. Por ejemplo, al consultar albaranes de venta dispone de los siguientes criterios: ``` ▾ delivery_notes ▾ where: ▾ customer: ▸ customer_ambit: ▸ customer_id: ▸ date: ▸ delivery_terms: ▾ key: ▸ company_id: ▸ branch_id: ▸ sales_delivery_note_type: ▸ sales_delivery_note_id: ▸ reference: ▸ sales_representative_id: ▸ vat_number: ``` Puede filtrar libremente por ninguno, uno, o varios de estos criterios. Por cada campo puede seleccionar el operador que le aplica. Dentro de cada criterio de filtrado es obligatorio informar los subcampos por el orden en el que están definidos. Por ejemplo, si decide filtrar por cliente (_customer_) podrá filtrar solo por ámbito (*customer_ambit*), porque es el primer campo, pero si quiere filtrar también por código de cliente (*customer_id*) tendrá que informar obligatoriamente el ámbito. Las vistas de múltiples registros de tablas grandes devuelven un objeto de tipo _Connection_ en el que se indica el total de registros, con el campo _totalCount_. Este concepto está basado en la [especificación de conexiones de Relay](https://relay.dev/graphql/connections.htm), aunque no está implantada toda la especificación. Existe un elemento _nodes_ que contiene los registros. El atributo _first_ determina cuantos registros hay que leer. El atributo _offset_ determina a partir de qué registro se lee. El atributo _sort_ permite ordenar la consulta por varios campos. Por ejemplo, una consulta que devuelve albaranes de venta, la página 6 paginando de 10 en 10, ordenando por fecha, del cliente en el ámbito 0 y código 5, y cuya referencia empieza por "GQL": ```graphql sales { delivery_notes( first: 10 offset: 50 order: {date: ASC} where: {customer: {customer_ambit: {equals: 0}, customer_id: {equals: 5}}, reference: {begins: "GQL"}} ) { totalCount nodes { sales_delivery_note_id sales_delivery_note_type amount } } } ``` ### Ámbito de la información La mayoría de documentos del ERP pertenecen a una empresa y delegación en concreto. Al filtrar esas tablas, por norma general, será necesario filtrar por empresa y delegación antes de poder filtrar por código. El argumento _key_ del filtro ya suele contener este tipo de filtro. Si intenta localizar un único registro por el valor del campo *unique_id*, no será necesario que filtre por empresa y delegación. Otros tipos de registro están acotados por ámbito, como es el caso de los artículos. Esto permite compartir el mismo registro en varias empresas y delegaciones. Al filtrar estas tablas también será necesario usar el ámbito, excepto cuando se localice un único registro por el campo *unique_id*. Es posible obtener el valor del ámbito a la que pertenece una cierta empresa/delegación a través de la consulta en *general.company_branches*. ```graphql general { company_branch(by: {key: {company_id: 1, branch_id: 0}}) { customer_ambit product_ambit } } ``` El maestro de artículos y el de clientes están divididos cada uno en dos tablas: existe una tabla por ámbito, compartida por todas las empresas/delegaciones en ese ámbito, y una tabla con propiedades por empresa/delegación. Estas tablas son: | Tabla | Descripción | | --------------- | -------------------------------- | | product | Artículos por ámbito | | product_branch | Artículos por empresa/delegación | | customer | Clientes por ámbito | | customer_branch | Clientes por empresa/delegación | Para obtener un registro completo tendremos que consultar primero la tabla por ámbito y luego los datos de la empresa/delegación que queramos. Por ejemplo: ```graphql warehouse { product(by: {unique_id: $unique_id}) { product_id reference product_branch(by: {key: {company_id: 1, branch_id: 0}}) { packing_unit_sales stock_reserved } } } ``` ### Control de cambios Si se ha activado dicha funcionalidad en su instalación, es posible realizar control de cambios en los registros publicados en la API. Para ello hay que realizar tres pasos: 1) **Obtener los identificadores de los registros que han cambiado**. Para ello existe la vista *general.pending* en la que se puede filtrar por tabla con control de cambios y devuelve una lista con los identificadores de los registros que han cambiado. 2) **Leer los registros que han cambiado**. Por cada registro devuelto en el paso 1, tendremos que buscar el registro por identificador único (*unique_id*) en la consulta que corresponda. 3) **Confirmar los cambios**. Existe un endpoint específico para confirmar los cambios. Los registros confirmados no volverán a aparecer en el listado del paso 1 hasta que no sufran otros cambios. Si el registro ha cambiado entre que se obtuvo su identificador en el paso 1 y se confirma en el paso 3, seguirá apareciendo como modificado. El endpoint es el siguiente: > **PASOE:** PUT _{host}_/gomanage/web/data/graphql/confirm > **IIS:** PUT _{host}_/confirm Cabeceras: Content-Type: application/json Accept: application/json Hay que enviar en el cuerpo una array JSON con los registros a confirmar: ```json [ { "company_id": 1, // Empresa "branch_id": 0, // Delegación "table": "PRODUCT", // Tabla afectada "unique_id": "736800007362715005", // Id del registro "success": true, // Si se ha incorporado correctamente "error": "" // Posible error si success = false }, { // ... Otros registros } ] ``` En la confirmación se puede indicar si la integración ha sido correcta o no con el campo _success_, e informar un posible mensaje de error en el campo _error_. Estos datos se guardan en la base de datos del ERP. Independientemente de si es correcta o no, una vez confirmado el registro deja de aparecer en el listado de registros pendientes. Como respuesta se devuelve la misma array JSON del cuerpo, cambiando los campos _success_ y _error_ para indicar si se ha confirmado correctamente. ## Mejores prácticas 1. **Pedir solo lo necesario.** Pedir más campos de los que realmente necesitamos provoca carga innecesaria en la base de datos y en la red. En especial, algunos campos requieren hacer consultas adicionales en otras tablas. El campo _totalCount_ en las consultas paginadas también provoca la ejecución de una consulta más para resolver la petición. 2. **Usar variables.** Para las consultas que se repiten muchas veces es muy recomendable usar una consulta estática con variables en los filtros. De esta forma el servidor puede mantener en caché el query plan de la consulta. Por ejemplo, en vez de hacer: ```graphql query GetProduct { warehouse { product(by: {product_id: {product_ambit: 0, product_id: "gql-1007"}}) { product_id reference } } } ``` Es mejor poner una variable en los campos que vamos a usar como parámetros: ```graphql query GetProduct($product_id: String!) { warehouse { product(by: {product_id: {product_ambit: 0, product_id: $product_id}}) { product_id reference } } } ``` Y repetir la consulta con el mismo cuerpo pero cambiando el valor de las variables: ```json { "product_id": "gql-1007" } ``` Para más información sobre variables, ver la [documentación oficial](https://graphql.org/learn/queries/#variables). 3. **Referenciar registros por el código nativo:** Aunque hay tablas en las que hay varios criterios disponibles para localizar un registro, algunos son más lentos que otros. Por normal general, usar el filtro por el atributo _key_ o *unique_id* es el que resulta en un menor tiempo de respuesta. Por ejemplo, en el caso de los artículos esto significa que localizar un artículo por _key_: ámbito (*product_ambit*) y código (*product_id*) o por *unique_id* es más rápido que hacerlo por marca y referencia. ## Consultas de ejemplo ### Consultas paginadas Una forma de paginar los resultados de una consulta es la siguiente. Primero, obtenemos el total de registros: ```graphql query GetTotalCustomers { master_files { customers(where: {key: {customer_ambit: {equals: 0}}}) { totalCount } } } ``` Y a continuación usamos una consulta paginada para leer los registros en bloques: ```graphql query GetCustomersPage($first: Int!, $offset: Int!) { master_files { customers( where: {key: {customer_ambit: {equals: 0}}} first: $first offset: $offset ) { nodes { customer_id name } } } } ``` Por ejemplo los valores de las variables para paginar de 10 en 10 serían: | Página | $first | $offset | | :--: | :--: | :---: | | **1** | 10 | 0 | | **2** | 10 | 10 | | **3** | 10 | 20 | | **4** | 10 | 30 | etc. ### Albaranes por cliente La siguiente consulta devuelve los albaranes de un cliente, obteniendo algunos campos de la cabecera, líneas e impuestos: ```graphql query GetDeliveryNotesByCustomer($first: Int = 10, $offset: Int, $customer_id: Int, $customer_ambit: Int) { sales { delivery_notes( first: $first offset: $offset where: {customer: {customer_ambit: {equals: $customer_ambit}, customer_id: {equals: $customer_id}}} order: {date: ASC} ) { nodes { sales_delivery_note_type sales_delivery_note_id date reference reference_2 amount lines { product_id product_reference brand_name quantity price discount_1 amount } taxes { tax_id tax_base tax_rate tax_amount } } } } } ``` ### Consultas de stocks Los datos de stock de los artículos se encuentran en la misma ficha del artículo por delegación *product_branch*. #### Por código de artículo ```graphql query GetStockOfProduct($key: warehouse_product_branch_InputFilter_key) { warehouse { product_branch(by: {key: $key}) { stock_real stock_reserved } } } ``` Nótese que en este caso estamos pasando el filtro como objeto compuesto. En las variables un posible valor sería: ```json { "key": { "company_id": 1, "branch_id": 0, "product_ambit": 0, "product_id": "test1" } } ``` #### Por marca/referencia Localizar por otros criterios requiere añadir una tabla más a la consulta. En la siguiente consulta estamos devolviendo los datos de stock de todas las delegaciones de un producto localizado por marca y referencia. ```graphql query GetStocksOfProduct($product_ambit: Int!, $brand_name: String!, $reference: String!) { warehouse { product( by: {reference: {product_ambit: $product_ambit, brand_name: $brand_name, reference: $reference}} ) { product_branches { company_id branch_id stock_real } } } } ``` #### Múltiples consultas con fragmentos GraphQL permite hacer más de una consulta a la vez. Podemos unificar los campos que se devuelven mediante el uso de [fragmentos](https://graphql.org/learn/queries/#fragments). ```graphql query GetMultipleProducts($company_id: Int!, $branch_id: Int!, $product_ambit: Int!, $product_id_1: String!, $product_id_2: String!, $product_id_3: String!, $product_id_4: String!, $product_id_5: String!) { warehouse { stock_1: product_branch( by: {key: {company_id: $company_id, branch_id: $branch_id, product_ambit: $product_ambit, product_id: $product_id_1}} ) { ...stock_fields } stock_2: product_branch( by: {key: {company_id: $company_id, branch_id: $branch_id, product_ambit: $product_ambit, product_id: $product_id_2}} ) { ...stock_fields } stock_3: product_branch( by: {key: {company_id: $company_id, branch_id: $branch_id, product_ambit: $product_ambit, product_id: $product_id_3}} ) { ...stock_fields } stock_4: product_branch( by: {key: {company_id: $company_id, branch_id: $branch_id, product_ambit: $product_ambit, product_id: $product_id_4}} ) { stock_real } stock_5: product_branch( by: {key: {company_id: $company_id, branch_id: $branch_id, product_ambit: $product_ambit, product_id: $product_id_5}} ) { ...stock_fields } } } fragment stock_fields on product_branch { stock_real stock_reserved stock_pending_issue stock_pending_receive } ``` ### Artículos por clasificación La siguiente petición pide artículos de una clasificación en concreto, obteniendo datos de precios, embalajes, códigos de barras e imágenes del mismo. ```graphql query ProductsByClassification($first: Int = 10, $offset: Int = 0, $product_ambit: Int!, $classification_id: Int!, $company_id: Int!, $branch_id: Int!, $customer_b2b_id: Int!, $customer_b2c_id: Int!, $image_document_type: String) { warehouse { products_by_classification( first: $first where: {product_classification_id: {equals: $classification_id}, key: {product_ambit: {equals: $product_ambit}}} order: {product_id: ASC} offset: $offset ) { nodes { level_1 { description } level_2 { description } product { unique_id product_id brand_name reference description_short barcodes { barcode code_type } product_branch(by: {key: {company_id: $company_id, branch_id: $branch_id}}) { sales_packing_unit { packing_unit_id minimum_quantity multiple_quantity } vendor_id pricing_b2c: pricing(by: {key: {customer_id: $customer_b2c_id}}) { ...pricing_fields } pricing_b2b: pricing(by: {key: {customer_id: $customer_b2b_id}}) { ...pricing_fields } } attached_files(where: {document_type: {equals: $image_document_type}}) { file_name file_blob } } } } } } fragment pricing_fields on sales_pricing { price price_quantity equivalent_discount currency_id net_price } ``` ### Precio de artículo por código de barras La siguiente consulta recupera el precio de un artículo buscando por código de barras: ```graphql query PriceByBarcode($product_ambit: Int!, $barcode: String!, $company_id: Int!, $branch_id: Int!, $customer_id: Int!) { warehouse { product_barcode( by: {barcode: {barcode: $barcode, product_ambit: $product_ambit}} ) { product_branch(by: {key: {company_id: $company_id, branch_id: $branch_id}}) { pricing(by: {key: {customer_id: $customer_id}}) { net_price } } } } } ``` ### Pedido y documentos relacionados La siguiente consulta recupera, a partir del identificador único de un pedido, el número y fecha de las facturas generadas a partir del mismo. Nótese que habrá que agrupar (con un método similar a un DISTINCT) los datos resultantes. ```graphql query OrderAndInvoices($unique_id: ID!) { sales { order(by: {unique_id: $unique_id}) { lines { line_number sales_document_lines { sales_invoice_id sales_invoice { date } } } } } } ```