Business NXT API /businessnxtapi section This is a guide for integrators that want to build integrations towards Business NXT. 2025-04-15T14:20:59+03:00 # Business NXT API This is a guide for integrators that want to build integrations towards Business NXT. ## Introduction GraphQL is a query an manipulation language for API as well as a runtime for fulfilling queries. Business NXT is providing a GraphQL server-side runtime to execute queries using a type system based on the data model. Although this document explains how to use the Business NXT API it does not provide learning materials for the GraphQL language. If you are not already familiar with GraphQL, we recommend you first take the [Introduction to GraphQL](https://graphql.org/learn/) tutorial (from the official [GraphQL](https://graphql.org/) page) or other learning materials. Business NXT supports Visma Connect Webhooks. With webhooks, your application gets a notification whenever there is a change in a companies database. The notifications will only contain a table identifier and a primary key - so it can be used in combination with graphql API to get the latest version of the changed row. > [!TIP] > You can learn more about using Business NXT from the documentation. See [Business NXT documentation](https://doc.visma.net/userdoc/businessnxt/en/). ## Endpoints The Business NXT GraphQL endpoints are: | URL | Description | | --- | ----------- | | | For querying from a **Visma.net user context** (authentication using *Authentication Code with PKCE*). See [Web applications](/businessnxtapi/authentication/web/) for more. | | | For querying from a **service context** (authentication using *Client credentials*). See [Services](/businessnxtapi/authentication/service/) for more. | ## Quick start - [Getting started with GraphQL queries](/businessnxtapi/gettingstarted/) - [Release notes](/businessnxtapi/releasenotes/) - [Best practices](/businessnxtapi/howto/bestpractices/) ## Resources We recommend the following resources for learning more about GraphQL: - [Introduction to GraphQL](https://graphql.org/learn/) - [GraphQL Tutorials](https://www.graphql.com/tutorials/) - [Full Stack GraphQL Tutorial Series](https://hasura.io/learn/) Getting started with GraphQL queries /businessnxtapi/gettingstarted section We will explore the schema shortly. However, to get started with the API and understand how easy it is to query data from Business NXT, we will look at several simple examples. 2025-04-15T09:48:42+02:00 # Getting started with GraphQL queries We will explore the schema shortly. However, to get started with the API and understand how easy it is to query data from Business NXT, we will look at several simple examples. In the first example, we fetch the general ledger account numbers and names from the company with the ID `9112233`. To access a company table you need a company context. To get one, you have to query from `useCompany(no: ...)`. ```graphql { title = "Query" } { useCompany(no: 9112233) { # get general ledger accounts generalLedgerAccount { items { accountNo name } } } } ``` ```graphql { title = "Result" } { "data": { "useCompany": { "generalLedgerAccount": { "items": [ { "accountNo": 1000, "name": "Forskning og utvikling" }, { "accountNo": 1020, "name": "Konsesjoner" }, ... ] } } } } ``` GraphQL allows you to combine multiple table queries in a single request. In the following example, we fetch both the general ledger accounts and the tax codes from the company with the ID `9112233`. ```graphql { title = "Query" } { useCompany(no: 9112233) { # get general ledger accounts generalLedgerAccount { items { accountNo name } } # get tax codes taxCode { items { taxCode description } } } } ``` ```graphql { title = "Result" } { "data": { "useCompany": { "generalLedgerAccount": { "items": [ { "accountNo": 1000, "name": "Forskning og utvikling" }, { "accountNo": 1020, "name": "Konsesjoner" }, ... ] }, "taxCode": { "items": [ { "taxCode": 1, "description": "Inngående MVA - høy sats" }, { "taxCode": 3, "description": "Utgående MVA - høy sats" }, ... ] } } } } ``` In these examples we read data from company tables. But you can also perform operations on system tables. Such a table is `Company` that stores information about all the companies of a customer. In the next example, we ask for the list of all the companies in the system, and, for each company, the `companyNo` and the `name` fields. To access a company table you need a customer context. To get one, you have to query from `useCustomer(no: ...)`, as follows: ```graphql { title = "Query" } { # get all companies useCustomer(no: 1111111) { company { items { companyNo name companyBusinessNo vismaNetCompanyId } } } } ``` ```graphql { title = "Result" } { "data": { "useCustomer" : { "company": { "items": [ { "companyNo": 1, "name": "Visma Test AS" "companyBusinessNo": "91421001", "vismaNetCompanyId": 9112233 }, { "companyNo": 2, "name": "Visma Demo AS" "companyBusinessNo": "91421002", "vismaNetCompanyId": 9445566 } ] } } } } ``` With the Business NXT GraphQL API you can execute both queries to read data and mutations to create, update, and delete data, as the API is supporting the full range of CRUD operations (create, read, update, and delete). In addition, you are able to execute Business NXT specific operations such as processings, reports, and aggregates. All these operations will be detailed later in this tutorial. Exploring the API /businessnxtapi/exploring_api section This section introduces testing GraphQL endpoint queries using tools like GraphiQL and Postman, and details the POST request structure with a "GetCustomers" query example. 2024-09-24T15:57:29+02:00 # Exploring the API This section introduces testing GraphQL endpoint queries using tools like GraphiQL and Postman, and details the POST request structure with a "GetCustomers" query example. For testing, you can run requests to the GraphQL endpoint with your preferred tools. We recommend: - [GraphiQL](https://github.com/graphql/graphiql) - a web-based GraphQL IDE - [Postman](https://www.postman.com/) - the widely used collaboration platform for API development, with built-int GraphQL support - [Insomnia](https://insomnia.rest/) - similar to Postman, also with built-in support for GraphQL - [Curl](https://curl.se/) - a widely used command line tool for transfering data using a variety of network protocols, including HTTP/HTTPs The general form of a GraphQL query is as follows: - the request is a POST - the content type is `application/json` - the body is a JSON with the form shown below, where the value of `query` is a serialized JSON (properly escaped) and `variables` is a JSON object. The `variables` property can be skipped or could be set to `null` if there are no GraphQL variables in the query. ```json { "query" : "...", "variables" : {} } ``` The following snippet shows a query example: ```json { "query":"query GetCustomers($companyNo: Int, $pageSize: Int, $after :String)\n{\n useCompany(no: $companyNo)\n {\n associate(first: $pageSize, after: $after)\n {\n totalCount\n pageInfo\n {\n hasNextPage\n hasPreviousPage\n startCursor\n endCursor\n }\n items\n {\n associateNo\n customerNo\n name\n }\n }\n }\n}", "variables":{"companyNo":9112233,"pageSize":5}, "operationName":"GetCustomers" } ``` In this example, you can also see the property `operationName` that contains the name of the operation (that follows the `query` or `mutation` keyword). > [!NOTE] > > The topic of GraphQL variables is addressed later in the tutorial in section [Named queries and parameters](/businessnxtapi/features/parameters/). GraphiQL /businessnxtapi/exploring_api/graphiql page Explore and test GraphQL queries using GraphiQL, a web IDE with features like schema auto-fetch, query auto-complete, and documentation exploration. 2024-09-24T15:57:29+02:00 # GraphiQL Explore and test GraphQL queries using GraphiQL, a web IDE with features like schema auto-fetch, query auto-complete, and documentation exploration. You can explore the API and execute queries using GraphiQL, which is an web-based integrated development environment (IDE) for GraphQL. GraphiQL enables you to explore the GraphQL schema as well as building and executing queries. This is a very useful tool for getting familiar with our API, preparing your queries, and testing them. For these reasons, GraphiQL provides a better experience than the tools mentioned earlier, enabling you to familiazize yourself with the GraphQL schema and become productive in a short period. ![](../graphiql.png) GraphiQL features include the following: * automatic fetching of schema * exploration of documentation (built from the schema) * navigation from the query to documentation by hovering the cursor over a node in the query * auto completion for query and variables (`CTRL` + `SPACE`) * history of most recent queries (allowing you to run previous queries) * multiple tabs and multiple queries per tab * query prettyfier * syntax coloring * editing of HTTP headers ![graphiql_autocomplete](../graphiql_autocomplete.png) ![graphiql_history](../graphiql_history.png) > [!TIP] > You can learn more about GraphiQL from the project documentation. See [GraphiQL IDE](https://github.com/graphql/graphiql). ## Endpoint The Business NXT GraphiQL endpoint is . > [!IMPORTANT] > You must be authenticated with your Visma Connect user in order to access GraphiQL. Postman /businessnxtapi/exploring_api/postman page Postman supports GraphQL requests with built-in tools or raw POST requests, using application/json content type for queries and variables. 2024-09-24T15:57:29+02:00 # Postman Postman supports GraphQL requests with built-in tools or raw POST requests, using application/json content type for queries and variables. Postman has built-in support for GraphQL. You can make requests selecting GraphQL for the body type. There are two fields, **QUERY** and **GRAPHQL VARIABLES** where you can provide the query and variables. ![GraphQL query in Postman](../postman1.png) You can achieve the same making a raw POST request, with the content type set to `application/json` and the body with the form explained in the beginning of this section. ![GraphQL raw query in Postman](../postman2.png) > [!TIP] > You can learn more about Postman from its official [Learning center](https://learning.postman.com/docs/getting-started/introduction/). Insomnia /businessnxtapi/exploring_api/insomnia page Insomnia offers superior GraphQL support with schema fetching, autocomplete, and an interactive schema explorer for efficient API management. 2024-09-24T15:57:29+02:00 # Insomnia Insomnia offers superior GraphQL support with schema fetching, autocomplete, and an interactive schema explorer for efficient API management. Insomnia is an application that has many similarities with Postman, although it provides better support for working with GraphQL schemas. ![GraphQL query in Insomnia](../insomnia1.png) You can use the schema button in the query window to fetch or refresh the schema and then you get auto complete suport in the query editor. ![GraphQL schema support in Insomnia](../insomnia2.png) Insomnia allows you to explore the schema documentation by using the *Show Documentation* option from the schema button. This is shown in the following image: ![GraphQL schema options in Insomnia](../insomnia3.png) The schema explorer can also be opened by hovering the cursor over any part of the query and then clickin on the links in the tooltip window. ![GraphQL schema tooltip in Insomnia](../insomnia4.png) The schema explorer allows you to navigate through the schema and understand its structure. ![GraphQL schema explorer in Insomnia](../insomnia5.png) > [!TIP] > You can learn more about Insomnia from its official documentation. See [Insomnia Docs](https://docs.insomnia.rest/). Making requests from code /businessnxtapi/exploring_api/code page Learn how to make programmatic requests in C#/.NET 5 to execute GraphQL queries and deserialize JSON responses. 2025-04-15T09:48:42+02:00 # Making requests from code Learn how to make programmatic requests in C#/.NET 5 to execute GraphQL queries and deserialize JSON responses. In this section, we will consider making requests programatically to execute the following query: ```graphql { title = "Query" } query read_glas($cid : Int, $pagesize : Int){ useCompany(no: $cid) { generalLedgerAccount(first: $pagesize) { totalCount pageInfo { hasNextPage hasPreviousPage startCursor endCursor } items { accountNo name } } } } ``` ```graphql { title = "Result" } { "data": { "useCompany": { "generalLedgerAccount": { "totalCount": 344, "pageInfo": { "hasNextPage": true, "hasPreviousPage": false, "startCursor": "MA==", "endCursor": "MjAw" }, "items": [ { "accountNo": 1000, "name": "Forskning og utvikling" }, { "accountNo": 1020, "name": "Konsesjoner" }, { "accountNo": 1030, "name": "Patenter" } ] } } } } ``` > [!IMPORTANT] > The code for the example described here is available on GitHub at [GraphQLSamples/SimpleQuery](https://github.com/Visma-Business/GraphQLSamples/tree/main/SimpleQuery). ## C# / .NET 5 Example If you need to make requests programatically and are using C# and .NET 5, you can do the following: 1. Create a class to contain the actual query and potential variables. ```cs class QueryRequest { [JsonPropertyName("query")] public string query { get; set; } [JsonPropertyName("variables")] public Dictionary variables { get; set; } } ``` **Note**: The name of the C# class and its properties are not that important. However, the JSON text that is being sent in the body of the POST request must have the form shown in the following snippet (that contains a query for reading a first page of 100 records from the general ledger account table). Notice that `query` and `variable` must be in lower-case. ```json { "query": "query read_glas($cid : Int, $pagesize : Int){\r\n useCompany(no: $cid) {\r\n generalLedgerAccount(first: $pagesize) {\r\n totalCount\r\n pageInfo {\r\n hasNextPage\r\n hasPreviousPage\r\n startCursor\r\n endCursor\r\n }\r\n items {\r\n accountNo\r\n name\r\n }\r\n }\r\n }\r\n}", "variables": { "cid" : 9112233, "pagesize" : 100 } } ``` You can use JSON attributes (they are similar whether you use *Json.NET* or *System.Text.Json*) to ensure the serialized JSON document has the correct shape regarless of the names of the C# classes, properties, or fields. 2. Create classes to model the expected response, so that you can deserialize the JSON object returned by the server. ```cs public class GeneralLedgerAccountResponse { public Data data { get; set; } } public class Data { public Usecompany useCompany { get; set; } } public class Usecompany { public GeneralLedgerAccountConnection generalLedgerAccount { get; set; } } public class GeneralLedgerAccountConnection { public int totalCount { get; set; } public PageInfo pageInfo { get; set; } public GeneralLedgerAccount[] items { get; set; } } public class PageInfo { public bool hasNextPage { get; set; } public bool hasPreviousPage { get; set; } public string startCursor { get; set; } public string endCursor { get; set; } } public class GeneralLedgerAccount { public int accountNo { get; set; } public string name { get; set; } } ``` 3. Create a generic method that can execute a request and return the deserialized result. ```cs class Program { private static readonly HttpClient _client = new(); private static async Task ExecuteQuery(QueryRequest request, string url, string accessToken) { try { _client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken); using var response = await _client.PostAsJsonAsync(url, request); response.EnsureSuccessStatusCode(); var body = await response.Content.ReadAsStringAsync(); var content = await response.Content.ReadFromJsonAsync(); return content; } catch (HttpRequestException ex) { Console.Error.WriteLine($"{ex.Message} (code: {ex.StatusCode.Value})"); } catch (Exception ex) { Console.Error.WriteLine(ex.Message); } return default; } } ``` 4. Obtain an access token programatically. [This is covered in Examples for setting up authorization: .NET Code Sample](/businessnxtapi/authentication/web/examples/code/). 5. Create a request string, execute it, and process the result. ```cs class Program { static async Task Main(string[] args) { var url = "https://business.visma.net/api/graphql"; var accessToken = "..."; var companyId = ...; var pageSize = 100; var request = new QueryRequest() { query = @"query read_glas($cid : Int, $pagesize : Int){ useCompany(no: $cid) { generalLedgerAccount(first: $pagesize) { totalCount pageInfo { hasNextPage hasPreviousPage startCursor endCursor } items { accountNo name } } } }", variables = new Dictionary { {"cid", companyId}, {"$pagesize", pageSize} } }; var result = await ExecuteQuery(request, url, accessToken); if (result?.data?.useCompany?.generalLedgerAccount?.items is object) { foreach (var gla in result.data.useCompany.generalLedgerAccount.items) { Console.WriteLine($"{gla.accountNo} - {gla.name}"); } } } } ``` Authentication /businessnxtapi/authentication section Authentication to Business NXT API via Visma Connect involves multi-tenant identity providers. Requires creating an app in Visma Developer Portal. 2024-09-24T15:57:29+02:00 # Authentication Authentication to Business NXT API via Visma Connect involves multi-tenant identity providers. Requires creating an app in Visma Developer Portal. Authentication to Business NXT API is done through Visma Connect. Visma Connect is a multi-tenant identity provider. Tenants are organizations (businesses) that provide an API-context. A given user may have access to multiple tenants and will choose which to connect to your application upon each single sign-on flow. > [!TIP] > You'll need to create an application in the Visma Developer Portal, available at , before getting started. > [!TIP] > To learn more about this process see this document . Please explore Visma Developer Portal documentation. ## Endpoints Visma Connect endpoint details: | Endpoint | URL | | -------- | --- | | Discovery | | | Authorization | | | Token | | | Userinfo | | | Endsession | | ## Visma Connect Token Your application requests specific permission scopes and is granted an access token upon a user's approval. There are two scenarious that are supported by Visma Connect: - Interactive authentication of users, that must provide their credentials in a login form. That requires the authentication flow to use the *Authorization Code with PKCE* grant type. - Authentication of a service, using a client ID and a client secret. That requires the authentication flow to use the *Client credentials* grant type. Once the authentication process has completed successfully, you can make API calls by providing the valid access token with each API request using the `authorization` header. ## Visma Connect Webhooks Rather than authenticating yourself against connect, your application will have to validate that the webhooks comes from Visma Connect. You will have to make a public endpoint that can take a POST from Visma Connect. The validation Understanding OAuth /businessnxtapi/authentication/oauth page Understanding OAuth2 for Visma API integration, including roles, tokens, scopes, grants, and necessary endpoints for secure resource access. 2024-09-24T15:57:29+02:00 # Understanding OAuth Understanding OAuth2 for Visma API integration, including roles, tokens, scopes, grants, and necessary endpoints for secure resource access. Access to resouces and APIs in Visma is done through Visma Connect. This is an identity provider. The OAuth2 flow is used to authorize users and services to access the desired resources. You need to know the basics of OAuth in order to understand the authorization process and build your integration with Business NXT. ## What is OAuth2? OAuth is a standard that allows websites or applications to access, on behalf of an user, resources hosted by other apps (servers). The term OAuth stands for "Open Autorization" and the number 2 refers to the 2.0 version of the standard. The standard allows users to grant client applications consented access to what resources they can access without sharing credentials. OAuth is not intended to authenticate users (that is verifying that a user is who he claims to be) but to authorize them. That means granting access to resources such as remove APIs. > [!NOTE] > > You can read more about OAuth2 here: > > - [OAuth 2.0](https://oauth.net/2/) > - [What is OAuth 2.0?](https://auth0.com/intro-to-iam/what-is-oauth-2/) ## Roles The OAuth standard defines a set of roles that represent components of an authorization system. These roles/components are the following: - **Resource owner**: the system/user that owns a resource and that grants access to that resouce. - **Client**: the system that requires access to a protected resource. - **Authorization server**: a server that receives requests from the **Client** for access tokens and issues them after authenticating the **Resource owner** and obtaining consent for the requested access. - **Resource server**: a server that protects the resources and receives access requests to them from the **Client**. The interactions between these componets are illustrated in the following image: ![OAuth roles](../oauthroles.png) ## Access tokens In order to grant access to resources, OAuth2 uses *access tokens*. An access token is basically a document that describes the authorization to access resouces on behalf of a user. The OAuth2 standard does not define a format. The JSON Web Token (JWT) is the widely used format. Visma Connect uses the JWT format for access tokens. The JWT token is a base64 encoded document. Typically, you should not be concerned with the content of a token. For debugging purposes, however, you can use the [jwt.io](https://jwt.io) website to inspect the content of a JWT access token. Its payload looks as follows: ```json { "nbf": 1648744531, "exp": 1648748131, "iss": "https://connect.visma.com", "aud": "https://business.visma.net/api/graphql", "client_id": "...", "sub": "42fd1681...", "auth_time": 1648744530, "idp": "Visma Connect", "llt": 1648641021, "acr": "3", "jti": "0271242A...", "sid": "203b1c01...", "iat": 1648744531, "scope": [ "openid", "email", "profile", "business-graphql-api:access-group-based" ], "amr": [ "pwd", "otp" ] } ``` An access token contains information such as: - issuer (`iss`) - the destination of the token (`aud`) - issued time (`iat`) - expiration time (`exp`) - subject - whom the token refers to (`sub`) - scopes - list of granted scopes (`scope`) The one what you need to know about are the scopes. >[!NOTE] > > You can read more about access tokens here: > > - [OpenID Connect specifications](https://openid.net/specs/openid-connect-core-1_0.html) > - [Introduction to JSON Web Tokens](https://jwt.io/introduction) ## ID token vs Access token vs Refresh token When you read about OAuth tokens, there are three terms that come up and can be confusing: - **ID token**: this is a document that proves that a user has been authenticated and can carry information about the user. The ID token was introduced by OpenID Connect (OIDC) which is an open standard for authentication used by many identity providers, including Google, Facebook, and Apple. - **Access token**: this is a document that carries information about the authorization to a resource. - **Refresh token**: this is a special kind of token that is used to obtain additional access tokens. An ID or access token is valid for a specific period of time (like 30 seconds or 60 minutes for instance). A refresh token is used to retrieve new access tokens without the need to re-authenticate the user. A refresh token is requested alongside the initial request for an ID and access token and must be stored (securely) by the application. >[!NOTE] > > You can read more about these kinds of tokens here: > > - [ID Tokens vs Access Tokens](https://oauth.net/id-tokens-vs-access-tokens/) > - [ID Token and Access Token: What's the Difference?](https://auth0.com/blog/id-token-access-token-what-is-the-difference/) > - [What Are Refresh Tokens and How to Use Them Securely](https://auth0.com/blog/refresh-tokens-what-are-they-and-when-to-use-them/) ## Scopes Scopes are reasons for which access to a resource is requested. For instance, there can be a scope for reading data, one for inserting and updating, one for deleting. Scopes depend on the resource server. >[!TIP] > > Business NXT defines a single scope, called `business-graphql-api:access-group-based` for web applications and `business-graphql-service-api:access-group-based` for services. > > However, in case of a web application, when you register it with Visma Developer Portal, you need to set the following scopes: `email`, `openid`, and `profile`. Upon reqesting an access token, you need to specify the following scopes: `business-graphql-api:access-group-based` `openid` `email`. These extra scopes are not necessary for services. > > Read more about this here: > > [Web applications registration](./web/setup_web.md) and [GraphQL API Integration](./web/integrations_web.md). > > [Services registration](/businessnxtapi/authentication/service/application_registration/) and [GraphQL API Integration](./service/integrations_service.md). ## Grants Grants are steps a client performs to get authorization for accessing a resource. The authorization framework defines several grant types for different scenarios: - Authorization Code - Implicit - Authorization Code with Proof Key for Code Exchange (PKCE) - Resource Owner Credentials - Client Credentials - Device Authorization Flow - Refresh Token Grant The grant type used for Business NXT is **Authorization Code with PKCE**. This flow consists of the following steps: 1. The user clicks a login button/link in the application. 2. The application creates a random `code_verifier` and from it generates a `code_challenge`. 3. The application makes an authorization request to the `/authorize` endpoint of the authorization server with the generated `code_challenge`. 4. The authorization server redirects the user to the login and authorization page. 5. The user authenticates (using a selected option) and may be displayed a consent page that lists the permissions the application is requesting. 6. The authorization server stores the `code_challenge` and redirects the user back to the application, providing a one-time-use `authorization_code`. 7. The application sends the `code_verifier` and the `authorization_code` to the `/token` endpoint of the authorization server. 8. The authorization server verifies the `code_challenge` and the `code_verifier`. 9. The authorization server returns an *ID token*, an *access token*, and optionally, a *refresh token*. 10. The application uses the access token to call an API. 11. The API responds with the requested data. ![OAuth roles](../oauthauthcodepkce.png) >[!NOTE] > > You can read more about grants here: > > - [Authentication and Authorization Flows](https://auth0.com/docs/get-started/authentication-and-authorization-flow) > - [OAuth Grant Types: Explained](https://frontegg.com/blog/oauth-grant-types) > - [OpenID Connect & OAuth 2.0 API](https://developer.okta.com/docs/reference/api/oidc/) ## Endpoints and redirect URLs Endpoints and a redirect URL are key parts of the OAuth flow. An authorization server exposes several endpoints that are used at various steps in the authorization flows: | Endpoint | Description | | -------- | ----------- | | `/authorize` | Interact with the resource owner and obtain an authorization grant. | | `/token` | Obtain an access token and/or an ID token by providing an authorization grant or refresh token. | | `/endsession` | End the session associated with the given ID token. | | `/userinfo` | Return claims about the authenticated end user. | >[!TIP] > > To get the URLs of the Visma Connect endpoints check [this page](/businessnxtapi/authentication/). A *redirect URL* is a URL to which the authorization server will redirect the user after the user successfully authorizes an application. A redirect URL contains sensitive information. The authorization server must not redirect the user to arbitrary (unsecure) locations. In order to ensure the user is redirected to an apppropriate location, the developers of the application are required to register one or more redirect URL when they create the application. >[!TIP] > > Visma Developer Portal requires you to provide a redirect URL when you register your application in the system. You can read more about that here: [Web Application registration](/businessnxtapi/authentication/web/). Services /businessnxtapi/authentication/service section Guide for integrating services with Business NXT via GraphQL, including registration, authentication, and setup instructions on Visma Developer Portal. 2024-09-24T15:57:29+02:00 # Services Guide for integrating services with Business NXT via GraphQL, including registration, authentication, and setup instructions on Visma Developer Portal. In order to integrate with Business NXT you must register an application in the Visma Developer Portal and add an integration to Business NXT GraphQL. Here, you will learn how to do this. Services are application do not require user interaction. They enable a machine-to-machine integration. If you do not require user interaction, this is the type of application you should use. To integrate your service with Business NXT using GraphQL, you must do the following: 1. Register your service with the Visma Developer Portal. 2. Register an integration with the Business NXT GraphQL API. 3. Register a user to impersonate your service in Business NXT. 4. Perform authentication with the *client credentials* grant type. 5. Use the URL for queries. In this section, we discuss each of these points. >[!TIP] > > If you are not familiar with the OAuth authorization flows, we recommend that you first read carefully the page [Understanding OAuth](../oauth.md) to familiarize yourself with concepts such as authentication and authorization, access tokens, scopes, grants, endpoints and redirect URLs. You need to have a basic understanding of these concepts in order to properly register your application and setup an integration with Business NXT GraphQL Service API. Application registration /businessnxtapi/authentication/service/application_registration page Guidance on registering a service application in the Visma Developer Portal, covering steps, approval, credentials, and local development setup. 2024-09-24T15:57:29+02:00 # Application registration Guidance on registering a service application in the Visma Developer Portal, covering steps, approval, credentials, and local development setup. Registering a service application in the Visma Developer Portal is very similar to registering a web application, although some options are different. You must follow these steps: 1. Logon to [Visma Developer Portal](https://oauth.developers.visma.com). 2. From the menu, select My **Applications** and then press the **Add applications** button. ![Add application](../app_setup01.png) 3. Select the **Service** application type. ![alt text](../srv_setup01.png) 4. Fill in the registration form. You must select an application name, a unique client identifier, and a description of your application. When completed, you must press the **Save as draft** button and then **Send for approval**. ![Registration form](../srv_setup02.png) 5. Your application must be approve by Visma before you can start using it. Before approval, it will display the *Pending approval* status. > [!WARNING] > > Because the **client ID** needs to be stored in the **User** table (see [User setup](vbnxt_service.md) page) and there is a limitation of 24 characters for names in this table, please make sure the value for the **client ID** does not exceed this limit. After the approval process complete, you can add integrations to your application. See [GraphQL API integration](integrations_service.md) to learn how to do that. ## Application Credentials In order to perform the authentication flow using the client credentials grant type, you must have both a *client identifier* and a *client secret*. Client secrets can be generated (and deleted) from the **Credentials** tab in Visma Developer Portal. ![Pending approval](../srv_setup03.png) > [!NOTE] > > A secret is only visible when it is generated after which you can no longer read it. You must copy and preserve it in a secure place. ## Local App Development For the development of your application on local machines, you must register a local URL for login and redirect. The use of `localhost` is not supported in the Visma Developer Portal but `app.local` can be used instead. Assuming your application runs locally on the 12345 port, use the following configuration: ``` Initiate Login URI: https://app.local:12345/ Frontchannel Logout URI: https://app.local:12345/logout Redirect URIs: https://app.local:12345/signin-oidc ``` GraphQL API integration /businessnxtapi/authentication/service/integrations_service page Guide for integrating Visma's Business NXT GraphQL API, including selecting APIs, confirming integrations, and initiating OAuth flow post-approval. 2024-09-24T15:57:29+02:00 # GraphQL API integration Guide for integrating Visma's Business NXT GraphQL API, including selecting APIs, confirming integrations, and initiating OAuth flow post-approval. After the approval of the registration of your service application, you can add integrations to Visma APIs. To integrate with Business NXT GraphQL API do the following: 1. Select your application from the **My Applications** main tab and go to **Integrations**. 2. Press the **New integration** button. 3. Search and select the **Business NXT GraphQL Service API** in the combo with available APIs and press **Continue**. ![Select API](../srv_setup04.png) 4. Select the `business-graphql-service-api:access-group-based` and press **Continue**. ![Select scope](../srv_setup05.png) 5. Review the selection and confirm the integration by pressing the **Confirm Integration** button. ![Confirm integration](../srv_setup06.png) 6. Wait for the integration to be approved by Visma. Once approved, the integrations become active. ![Application overview](../srv_setup07.png) Once your application is integrated with **Business NXT GraphQL Service API**, you can initiate the OAuth flow and create tokens. However, access to a company requires additional steps that must be performed in the Business NXT application. This is described next. User Setup /businessnxtapi/authentication/service/user_setup page API documentation for setting up a new user in Business NXT, including creating layouts and assigning application access roles. 2024-09-24T15:57:29+02:00 # User Setup API documentation for setting up a new user in Business NXT, including creating layouts and assigning application access roles. In order to perform operations in company in Business NXT using GraphQL from a service (using an access token with *client credentials* grant type), you must perform in Business NXT the registration steps described below. > [!TIP] > > In order to be able to add users to the `User` table, your Visma.net user must have the `Application access` and `System supervisor` roles assign in `Visma.net Admin`. > > ![Application access role](../vbuser7.png) The setup consists of the following steps: 1. Create a new layout and add the **User** table. ![Open the User table](../vbuser1.png) 2. Unhide the **Connect Application** column from this table. ![Unhide the Connect Application column](../vbuser2.png) 3. Add a new user having the same name as the client identifier (*client_id*) of your newly registered service. You must check the *Connect application* checkbox before saving the new user. ![Add a new user](../vbuser3.png) 4. Either create a new layout or use the same layout and add the **Connect Application Access** table. ![Open the Connect Application Access table](../vbuser4.png) 5. Add the newly created user to this table. Select the company to which you want to grant access to your service by entering its number in the **Column no.** field. ![Add the new user](../vbuser5.png) 6. Select the desired access group that defines the access rights for this company. ![Select an access group](../vbuser6.png) 7. Save the changes to the table. You must repeat the steps 5 and 6 for each company to which you want to grant access for your service. Examples for setting up authorization /businessnxtapi/authentication/service/examples section Here you can find some examples for how to set up authorization. 2024-09-24T15:57:29+02:00 # Examples for setting up authorization Here you can find some examples for how to set up authorization. Click the links below to look at examples for how to set up authorization for either Postman or Insomnia. Postman /businessnxtapi/authentication/service/examples/postman page Set up OAuth2.0 with Client Credentials grant type in Postman for authenticating your service via Visma Connect. 2024-09-24T15:57:29+02:00 # Postman Set up OAuth2.0 with Client Credentials grant type in Postman for authenticating your service via Visma Connect. In Postman, you can set the authorization method and details either per request or collection. To authenticate your service, perform the following setup: 1. Choose the **OAuth2.0** authorization type. 2. Select the **Client credentials** grant type. 3. Use the following access token URL: . 4. Fill in the client ID and the client secret. 5. Enter the scope `business-graphql-service-api:access-group-based`. 6. Select **Send as Basic Auth header** option for *Client Authentication*. These settings are shown in the following image: ![Configure Postman](../postman5.png) > [!TIP] > > You can learn more about about authentication in Postman from its official documentation. See [Authorizing requests](https://learning.postman.com/docs/sending-requests/authorization/). Insomnia /businessnxtapi/authentication/service/examples/insomnia page Guide to setting up OAuth2 Client Credentials in Insomnia for authenticating your service. 2024-09-24T15:57:29+02:00 # Insomnia Guide to setting up OAuth2 Client Credentials in Insomnia for authenticating your service. In Insomnia, you can set the authorization method and details per request. To authenticate your service, perform the following setup: 1. Choose the **OAuth2** authorization type. 2. Select the **Client Credentials** grant type. 3. Use the following access token URL: . 4. Fill in the client ID and the client secret. 5. Enter the scope `business-graphql-service-api:access-group-based`. 6. Select **As Basic Auth Header** option for *Credentials* combo. These settings are shown in the following image: ![Configure Insomnia](../insomnia8.png) > [!TIP] > > You can read more about authentication in Insomnia [here](https://docs.insomnia.rest/insomnia/authentication). Web applications /businessnxtapi/authentication/web section Learn how to set up web applications for integrating with Business NXT, including OAuth authorization essentials. 2024-09-24T15:57:29+02:00 # Web applications Learn how to set up web applications for integrating with Business NXT, including OAuth authorization essentials. For integrating with Business NXT, you can choose one of these two type of applications: - Service: suitable for web services that require a machine-to-machine integration (with no user interation). - Web: suitable for web applications that require user interaction. In this section, you will learn about the setup required for web applications. > [!TIP] > > If you are not familiar with the OAuth authorization flows, we recommend that you first read carefully the page [Understanding OAuth](../oauth.md) to familiarize yourself with concepts such as authentication and authorization, access tokens, scopes, grants, endpoints and redirect URLs. You need to have a basic understanding of these concepts in order to properly register your application and setup an integration with Business NXT GraphQL API. Application registration /businessnxtapi/authentication/web/setup_web page Learn how to register a web application on Visma Developer Portal to integrate with Business NXT GraphQL API. 2024-09-24T15:57:29+02:00 # Application registration Learn how to register a web application on Visma Developer Portal to integrate with Business NXT GraphQL API. In order to integrate with Business NXT you must register an application in the Visma Developer Portal and add an integration to Business NXT GraphQL. Here, you will learn how to do this. This section describes how to register a web application. > [TIP] > > If you are not familiar with the OAuth authorization flows, we recommend that you first read carefully the page [Understanding OAuth](../oauth.md) to familiarize yourself with concepts such as authentication and authorization, access tokens, scopes, grants, endpoints and redirect URLs. You need to have a basic understanding of these concepts in order to properly register your application and setup an integration with Business NXT GraphQL API. You must follow these steps: 1. Logon to [Visma Developer Portal](https://oauth.developers.visma.com). 2. From the menu, select My **Applications** and then press the **Add applications** button. ![alt text](../app_setup001.png) 3. Select the **Web** application type. ![Select type of application](../app_setup002.png) 4. Fill in the registration form. You must select an application name, a unique client identifier, a description of your application, as well as URLs for the authentication workflow. From this form, you can customize application policies and branding. When completed, you must press the **Save as draft** button and then **Send for approval**. ![Registration form](../app_setup003.png) 5. Your application must be approve by Visma before you can start using it. Before approval, it will display the *Pending approval* status. ![Pending approval](../app_setup005.png) After the approval process complete, you can add integrations to your application. See [GraphQL API integration](integrations_web.md) to learn how to do that. ## Application Credentials If you plan to use an web application with client credentials (performing the OAuth flow using the client credentials grant type), this requires both a *client ID* and a *client secret*. In this case, you also need to generate a client secret. Secrets can be generated and deleted from the **Credentials** tab in Visma Developer Portal. ![Pending approval](../app_setup004.png) > [!NOTE] > > A secret is only visible when it is generated after which you can no longer read it. You must copy and preserve it in a secure place. ## Local App Development For the development of your application on local machines, you must register a local URL for login and redirect. The use of `localhost` is not supported in the Visma Developer Portal but `app.local` can be used instead. Assuming your application runs locally on the 12345 port, use the following configuration: ``` Initiate Login URI: https://app.local:12345/ Frontchannel Logout URI: https://app.local:12345/logout Redirect URIs: https://app.local:12345/signin-oidc ``` GraphQL API integration /businessnxtapi/authentication/web/integrations_web page Step-by-step guide to integrate a web application with Business NXT GraphQL API and initiate the OAuth flow for creating tokens 2024-09-24T15:57:29+02:00 # GraphQL API integration Step-by-step guide to integrate a web application with Business NXT GraphQL API and initiate the OAuth flow for creating tokens After the approval of the registration of your web application, you can add integrations to Visma APIs. To integrate with Business NXT GraphQL API do the following: 1. Select your application from the **My Applications** main tab and go to **Integrations**. ![Integrations tab](../app_setup06.png) 2. Press the **New integration** button. 3. Search and select the **Business NXT Graphql API** in the combo with available APIs and press **Continue**. ![Select API](../app_setup07.png) If you plan to use the client credentials grant type, then you need search for **Business NXT GraphQL API** in the combo with available APIs and press **Continue**. ![Select API](../app_setup07s.png) 4. Select the `business-graphql-api:access-group-based` scope (or `business-graphql-service-api:access-group-based` if you seleced **Business NXT Graphql Service API** in the previous step) and press **Continue**. ![Select scope](../app_setup08.png) 5. Review the selection and confirm the integration by pressing the **Confirm Integration** button. ![Confirm integration](../app_setup09.png) 6. Wait for the integration to be approved by Visma. ![Wait for approval](../app_setup10.png) Once approved, the integrations become active. ![Application overview](../app_setup11.png) Once your application is integrated with **Business NXT Graphql API**, you can initiate the OAuth flow and create tokens. The minimum required claims for a valid OAuth 2.0 JWT token are: ```text Default OAuth 2.0 Claim required: sub Scope Claims required: email openid business-graphql-api:access-group-based ``` Company access is determined by the corresponding user for whom the OAuth JWT token was created. The rights for the user are managed in Visma.net. Setting up user nightly jobs with unattended access /businessnxtapi/authentication/web/unattendedaccess page Guide to setting up unattended nightly jobs using OIDC refresh tokens for data import from Business NXT in web applications. 2024-09-24T15:57:29+02:00 # Setting up user nightly jobs with unattended access Guide to setting up unattended nightly jobs using OIDC refresh tokens for data import from Business NXT in web applications. When using a web application, setting up a nightly job for importing data from Business NXT can be done with the help of OIDC *refresh tokens*. > [!TIP] > > For this kind of scenario, the best approach is to use a service application. To learn more about this see [Services](/businessnxtapi/authentication/service/). Only web applications in the Visma Developer Portal can have a refresh token and they require an OIDC-enabled backend to store the refresh token. You will also need to include the `offline_access` scope for the application. After checking offline access, you will gain access to more settings related to the refresh token: ![GraphQL query in Insomnia](../app_setup12.png) You can create a **job user** in Visma.net and assign its correct privileges to certain companies and company groups. It can even be your currently logged in user under which the job will run. After loging in with the user under which the unattended job will run, you will need to store this refresh token to refresh tokens and use it after the regular tokens expire, without requiring the user (that the nightly job will run under) to login again. > [!TIP] > > See more information about refresh tokens in the [Visma Developer Portal documentation](https://oauth.developers.visma.com/service-registry/documentation/authentication#offlineAccess). Examples for setting up authorization /businessnxtapi/authentication/web/examples section Here you can find some examples for how to set up authorization. 2024-09-24T15:57:29+02:00 # Examples for setting up authorization Here you can find some examples for how to set up authorization. Click the links below to look at examples for how to set up authorization for either .NET, Postman, or Insomnia. .NET Code Sample /businessnxtapi/authentication/web/examples/code page .NET sample app for integrating with Business NXT using OAuth flow. Clone or create ASP.NET app, setup configs, run to authenticate. 2024-09-24T15:57:29+02:00 # .NET Code Sample .NET sample app for integrating with Business NXT using OAuth flow. Clone or create ASP.NET app, setup configs, run to authenticate. If you're using .NET (3.1/5/6) to build your application that integrates with Business NXT, you can set it up as described below to perform the oauth flow. > [!NOTE] > > The code for the application described here is available on GitHub at [GraphQLSamples/MvcCode](https://github.com/Visma-Business/GraphQLSamples/tree/main/MvcCode). > > This application is a modified version of the [MvcCode sample](https://github.com/IdentityServer/IdentityServer4/tree/main/samples/Clients/src/MvcCode) from the [IdentityServer](https://github.com/IdentityServer) project. ## Getting started You can either clone the MvcCode sample repository or create an Asp.Net application from scratch. The sample application described here has the following structure: ![Project structure](../codesample1.png) ## Application setup The `Program.cs` source file has the following content: ``` using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Hosting; namespace MvcCode { public class Program { public static void Main(string[] args) { CreateHostBuilder(args).Build().Run(); } public static IHostBuilder CreateHostBuilder(string[] args) => Host.CreateDefaultBuilder(args) .ConfigureWebHostDefaults(webBuilder => { webBuilder.UseStartup(); }); } } ``` The `Setup.cs` source file contains the following: ``` using IdentityModel; using Microsoft.AspNetCore.Authentication.Cookies; using Microsoft.AspNetCore.Builder; using Microsoft.Extensions.DependencyInjection; using Microsoft.IdentityModel.Tokens; using System.IdentityModel.Tokens.Jwt; using System.Net.Http; using IdentityModel.Client; using Microsoft.IdentityModel.Logging; using System.Threading.Tasks; using Microsoft.Extensions.Configuration; namespace MvcCode { public class Startup { public Startup(IConfiguration configuration) { Configuration = configuration; } public IConfiguration Configuration { get; } public void ConfigureServices(IServiceCollection services) { JwtSecurityTokenHandler.DefaultMapInboundClaims = false; services.AddControllersWithViews(); services.AddHttpClient(); services.AddOptions(); services.Configure(Configuration); services.AddSingleton(r => { var factory = r.GetRequiredService(); return new DiscoveryCache(Configuration.GetValue("Authority"), () => factory.CreateClient()); }); services.AddAuthentication(options => { options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme; options.DefaultChallengeScheme = "oidc"; }) .AddCookie(options => { options.Cookie.Name = "mvccode"; }) .AddOpenIdConnect("oidc", options => { options.Events.OnTokenResponseReceived = (tokenResponse) => { var accessToken = tokenResponse.TokenEndpointResponse.AccessToken; return Task.CompletedTask; }; options.Events.OnRemoteFailure = (err) => { return Task.CompletedTask; }; options.Events.OnMessageReceived = (msg) => { return Task.CompletedTask; }; options.Authority = Configuration.GetValue("Authority"); options.RequireHttpsMetadata = false; options.ClientId = Configuration.GetValue("ClientId"); options.ClientSecret = Configuration.GetValue("ClientSecret"); options.ResponseType = "code id_token"; options.UsePkce = true; options.Scope.Clear(); options.Scope.Add("openid"); options.Scope.Add("profile"); options.Scope.Add("email"); options.Scope.Add("business-graphql-api:access-group-based"); options.Scope.Add("offline_access"); options.GetClaimsFromUserInfoEndpoint = true; options.SaveTokens = true; options.TokenValidationParameters = new TokenValidationParameters { NameClaimType = JwtClaimTypes.Name, RoleClaimType = JwtClaimTypes.Role, }; }); } public void Configure(IApplicationBuilder app) { IdentityModelEventSource.ShowPII = true; app.UseDeveloperExceptionPage(); app.UseHttpsRedirection(); app.UseStaticFiles(); app.UseRouting(); app.UseAuthentication(); app.UseAuthorization(); app.UseEndpoints(endpoints => { endpoints.MapDefaultControllerRoute() .RequireAuthorization(); }); } } } ``` From this snippet, you should notice the following: - Authority, client ID, and client secret are read from the application settings. - The response type is `code id_token`. - The requested scopes are `openid`, `profile`, `email`, `business-graphql-api:access-group-based`, and `offline_access`. The latter is only needed when offline access is required. ## Application Settings The JSON file with the application settings (`appsettings.json`) looks as follows: ``` { "ClientId": "...", "ClientSecret": "...", "Authority": "https://connect.visma.com", "SampleApi": "https://localhost:5005/" } ``` The client ID is the one you specified when you registered your application with the Visma Developer Portal. The client secret is a value generated in the **Credentials** tab of the application propertys in the Visma Developer Portal. > [!NOTE] > > Make sure you do not upload the content of this file to a public repository such as on GitHub. The `AppSettings.cs` file contains the following class definition used to help with accessing the settings: ``` namespace MvcCode { public class AppSettings { public string ClientId { get; set; } public string ClientSecret { get; set; } public string Authority { get; set; } public string SampleApi { get; set; } } } ``` ## Controllers The `HomeController` class is implemented as follows: ``` using System; using System.Globalization; using System.Net.Http; using System.Threading.Tasks; using IdentityModel.Client; using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Options; using Newtonsoft.Json.Linq; namespace MvcCode.Controllers { public class HomeController : Controller { private readonly IHttpClientFactory _httpClientFactory; private readonly IDiscoveryCache _discoveryCache; private readonly AppSettings _authSettings; public HomeController(IHttpClientFactory httpClientFactory, IDiscoveryCache discoveryCache, IOptions authSettings) { _httpClientFactory = httpClientFactory; _discoveryCache = discoveryCache; _authSettings = authSettings.Value; } [AllowAnonymous] public IActionResult Index() => View(); public IActionResult Secure() => View(); public IActionResult Logout() => SignOut("oidc"); public async Task CallApi() { var token = await HttpContext.GetTokenAsync("access_token"); var client = _httpClientFactory.CreateClient(); client.SetBearerToken(token); var response = await client.GetStringAsync(_authSettings.SampleApi + "identity"); ViewBag.Json = JArray.Parse(response).ToString(); return View(); } public async Task RenewTokens() { var disco = await _discoveryCache.GetAsync(); if (disco.IsError) throw new Exception(disco.Error); var rt = await HttpContext.GetTokenAsync("refresh_token"); var tokenClient = _httpClientFactory.CreateClient(); var tokenResult = await tokenClient.RequestRefreshTokenAsync(new RefreshTokenRequest { Address = disco.TokenEndpoint, ClientId = _authSettings.ClientId, ClientSecret = _authSettings.ClientSecret, RefreshToken = rt }); if (!tokenResult.IsError) { var oldIdToken = await HttpContext.GetTokenAsync("id_token"); var newAccessToken = tokenResult.AccessToken; var newRefreshToken = tokenResult.RefreshToken; var expiresAt = DateTime.UtcNow + TimeSpan.FromSeconds(tokenResult.ExpiresIn); var info = await HttpContext.AuthenticateAsync("Cookies"); info.Properties.UpdateTokenValue("refresh_token", newRefreshToken); info.Properties.UpdateTokenValue("access_token", newAccessToken); info.Properties.UpdateTokenValue("expires_at", expiresAt.ToString("o", CultureInfo.InvariantCulture)); await HttpContext.SignInAsync("Cookies", info.Principal, info.Properties); return Redirect("~/Home/Secure"); } ViewData["Error"] = tokenResult.Error; return View("Error"); } } } ``` In this snippet: - `CallApi()` is a function that makes an HTTP request to (or whatever base URL is specified in the `SampleApi` property in the application settings). - `RenewTokens()` is a function that runs the oauth flow to refesh the access token. ## Views The view files are as follows: - `Index.cshtml` ``` @{ ViewData["Title"] = "Home Page"; } ``` - `CallApi.cshtml` ```

API Response

@ViewBag.Json
``` - `Secure.cshtml` ```

API Response

@ViewBag.Json
``` ## Running the Application When you run the application (for instance on port 44303 at or ) you get the following welcome page: ![Home Page](../codesample2.png) When you click on **Secure** you are redirected to Visma Connect for authentication: ![Authentication](../codesample3.png) After completing the authentication, the **Secure** page lists your user identity claims: ![Claims](../codesample4.png) You can also find, on the same page, the authentication properties, such as the ID token, the access token, and the refresh token: ![Properties](../codesample5.png)
Postman /businessnxtapi/authentication/web/examples/postman page Configure Postman authorization using Bearer tokens per request or collection. Learn more in the official Postman documentation. 2024-09-24T15:57:29+02:00 # Postman Configure Postman authorization using Bearer tokens per request or collection. Learn more in the official Postman documentation. In Postman, you can set the authorization method and details either per request or collection. You must use the **Bearer token** authorization type and specify an access token. You can fetch an access token in different ways. A possible way is using the .NET sample application described [here](code.md). ![Configure Authorization - Postman](../postman4.png) > [!TIP] > > You can learn more about about authentication in Postman from its official documentation. See [Authorizing requests](https://learning.postman.com/docs/sending-requests/authorization/). Insomnia /businessnxtapi/authentication/web/examples/insomnia page Set up Insomnia with Bearer Token authorization, fetch access tokens via .NET application, and learn more about authentication techniques. 2024-09-24T15:57:29+02:00 # Insomnia Set up Insomnia with Bearer Token authorization, fetch access tokens via .NET application, and learn more about authentication techniques. In Insomnia, you can set the authorization method and details per request. You must use the **Bearer Token** authorization type and specify an access token. You can fetch an access token in different ways. A possible way is using the .NET sample application described [here](code.md). ![Configure Authorization - Insomnia](../insomnia7.png) > [!TIP] > > You can read more about authentication in Insomnia [here](https://docs.insomnia.rest/insomnia/authentication). GraphQL Schema /businessnxtapi/schema section Details the Business NXT GraphQL schema, Relay-like, exposing system and company databases with types named after tables, supporting queries and mutations. 2024-09-24T15:57:29+02:00 # GraphQL Schema Details the Business NXT GraphQL schema, Relay-like, exposing system and company databases with types named after tables, supporting queries and mutations. The Business NXT data model is exposed through a GraphQL schema. The schema defines the set of types that describe the set of possible data you can query from the service. Schemas are built with types (scalar, enumerations, union, input), objects, lists, arguments, intefaces, and other concepts. You can learn more about schemas and types [here](https://graphql.org/learn/schema/). Business NXT GraphQL uses a [Relay](https://relay.dev/)-like schema, although not fully compliant. Relay is a specification for defining the GraphQL schema used at Facebook (where GraphQL was created) and open sourced. You can read about it [here]( https://itnext.io/improve-your-graphql-schema-with-the-relay-specification-8952d06998eb). The VBNXT GraphQL implementation differs from the Relay specification by not providing the `Node` base type and the `edges` field for a type. The Business NXT data model is formed of two database types: - a system database, containing information such as companies and company groups, users and user groups, folders, windows, active session, and various other data that applies to the whole application - a company database, containing company specific information (such as associates, general ledger accounts, orders, vouchers, and many others) Every table in the database model, regardless it's a system or company table, is exposed in the schema with a type that has the same name as the table (the name that appears in the model, not the actual SQL name in the database). This type includes the table columns and their type, as well as relations to other columns. For instance, the table for the general leger accounts is available through the type called `GeneralLedgerAccount`. However, the schema defines different sets of types for queries and mutations. Queries /businessnxtapi/schema/queries section Learn how to read data using the query request in the Business NXT schema. For data modifications, refer to Mutation Type documentation. 2024-09-24T15:57:29+02:00 # Queries Learn how to read data using the query request in the Business NXT schema. For data modifications, refer to Mutation Type documentation. The Business NXT schema supports reading data (with a `query` request) as well as modifying data (with a `mutation` request). These two operations are exposed at the top of the schema with the `query` and `mutation` fields, as shown in the following image: ![Company table](./schema0.png) Here, you will learn about reading data. For inserts, updates, and deletes, see [The Mutation Type](../mutations/mutation.md). The Query Type /businessnxtapi/schema/queries/query page GraphQL Query type enables querying various tables with fields like useCompany for company data and useCustomer for system information. 2025-04-15T09:48:42+02:00 # The Query Type GraphQL Query type enables querying various tables with fields like useCompany for company data and useCustomer for system information. ## Overview The `Query` type is the root for all types used when you do a query (read) operation. This type contains the following fields: | Field | Description | | ----- | ----------- | | `useCompany` | The entry point for accessing company tables. Has a single argument, the Visma.net company ID. | | `useCustomer` | The entry point for accessing system tables. Has a single argument, the Visma.net customer ID. | | `useModel` | The entry point for accessing model information. See [Model information](modelinfo.md). | | `asyncRequests` and `asyncResult` | See [Async queries](../async.md). | | `availableCustomers` | See [Fetching user's available companies](companies.md). | | `availableCompanies` | See [Fetching user's available customers](customers.md).| Accessing company tables requires specifying a company number. Similarly, accessing a system table requires specifying a customer number. These are the Visma.net identifiers for the company and the customer and can be either found in the Visma.net portal or can be retrieved with a query (see the links mentioned above). The following images show (partially), side-by-side, the `Query_UseCustomer` type, containing the system table connections, and the `Query_UseCompany` type containing company table connections. These are the types for the fields `useCustomer` and `useCompany`, that are top level fields of the `Query` type. | System table | Company table | | ----------- | --- | | ![System table](../schema1.png) | ![Company table](../schema2.png) | ## Understanding connection types If you look at the types in these images, you will see they are named `Query_UseCustomer_ServiceAccount_Connection`, `Query_UseCompany_Associate_Connection`, `Query_UseCompany_GeneralLedgedAccount_Connection`, etc. These names have the following pattern: `__Connection`. Here is how the `Query_UseCompany_GeneralLedgedAccount_Connection` type looks in the GraphiQL document explorer: ![GeneralLedgerAccount type](../schema3.png) So, *what is a connection?* To understand this concept, let's consider the following example. When you want to query for records in this table, you do the following in GraphQL: ```graphql generalLedgerAccount { items { accountNo name # more columns } } ``` What you can see here is that the table columns are available under a node called `items`, that is an array of values (in the example above of the type `GeneralLedgerAccount`). This makes it possible to add add other relevant information for a query, appart from the table columns. This also protects the schema against possible future changes; adding a new field would not break existing queries. Every type representing a table has two more fiels: `totalCount` (representing the total number of records in the table that match the filter but ignoring pagination) and `pageInfo` that provides information to aid in pagination. These topics are addressed later in this document. Therefore, the shape of a table type is the following: ```graphql generalLedgerAccount { totalCount pageInfo { hasNextPage hasPreviousPage startCursor endCursor } items { accountNo name # more columns } } ``` > [!TIP] > > Keep in mind that all of these fields are optional when making a query. The result will contain only the information that has been requested. This is a key feature of GraphQL, which makes it preferable to other web service architectures. A GraphQL type that has this particular shape, with the fields `totalCount`, `pageInfo`, and `items`, is called a *connection*. The following image shows how the type `Query_UseCompany_GeneralLedgerAccount_Connection` looks in the GraphiQL Explorer: ![UseCompany_GeneralLedgerAccount_Connection type](../schema4.png) The name of this type is prefixed with `Query_UseCompany` because that is the name of the parent type, and is suffixed with `_Connection` to indicate this is a connection type. ## Understanding column descriptions When you look at the schema documentation in the Documentation Explorer in GraphiQL, for instance, you will see that each table field has a description. This description contains the SQL column name and the column identifier to help those already familiar with desktop Visma Business programming model that build integrations with the Business NXT API. The description can be preceeded by some tags, such as in `[PK] AcNo/1480`. These tags have the following meaning: | Tag | Description | | --- | ----------- | | `[PK]` | Primary key | | `[M]` | Memory column (not physically present in the database) | | `[Fn/a]` | Formula not available for a memory column | | `[B]` | Blob field whose value is returned as a base64-encoded string | | `[S(n)]` | Limited string of maximum `n` characters | For insert/update operations, for limited string fields, if the value exceeds the defined maximum size, the value is truncated. ## Accessing company tables The general ledger account that we saw previously is a company table, therefore, when querying for it, you must specify the company first, which is done with the `useCompany` field. This field's type is `Query_UseCompany` and this is the parent for all company table connection types. In order to query a company table, you must specify its Visma.net identifier. Let's look at an example: ```graphql { title = "Query" } query { useCompany(no: 9112233) { generalLedgerAccount { totalCount pageInfo { hasNextPage hasPreviousPage startCursor endCursor } items { accountNo name } } } } ``` ```graphql { title = "Result" } { "data": { "useCompany": { "generalLedgerAccount": { "totalCount": 340, "pageInfo": { "hasNextPage": false, "hasPreviousPage": false, "startCursor": "MA==", "endCursor": "MzQw" }, "items": [ { "accountNo": 1000, "name": "Forskning og utvikling" }, { "accountNo": 1020, "name": "Konsesjoner" }, { "accountNo": 1030, "name": "Patenter" }, { "accountNo": 9999, "name": "Observasjonskonto" } ] } } } } ``` ## Accessing system tables Tables such as `User` and `Company`, which are system tables, are available from the `useCustomer` field, whose type is `Query_UseCustomer`. This is the parent type for all the system table connection types. The connection type name for the `User` table is `Query_UseCustomer_User_Connection` and for the `Company` table is `Query_UseCustomer_Company_Connection`. The format for all system tables type names is `Query_UseCustomer__Connection`. In order to query a system table, you must specify a customer's Visma.net identifier. Let's look at an example: ```graphql { title = "Query" } query { useCustomer(no: 1111111) { user { items { userName } } } } ``` ```graphql { title = "Result" } { "data": { "useCustomer": { "user": { "items": [ { "userName": "john.doe" }, { "userName": "jane.doe" }, { "userName": "boaty.bcboatface" } ] } } } } ``` ## Joining tables Tables, regardless they are system or company tables, have relationships with other tables. These could be either one-to-one or one-to-many. For example, the window (from the `Win` table) has an one-to-one relation to a folder (from the `Fldr` table). The field `FldrNo` (2495) in the `Win` table, appearing as `folderNo` in the schema, is a foreign key to the `FldrNo` (2482) in the `Fldr` table, appearing as `folderNo` in the GraphQL schema. The schema makes it possible to easily join these related tables based on these relationships. Such a relationship has an *upwards* direction and in our GraphQL API are available through nodes prefixed with `joinup`. The opposite are one-to-many relations, such as from an order to order lines. These have a *downwards* direction and are available through nodes prefixed with `joindown`. An example for such a upwards join to a related table is show below. We fetch for the first three windows, the layout name and the number and name of the related folder. ```graphql { title = "Query" } query { useCustomer(no: 1111111) { window(first: 3) { items { layoutName joinup_Folder { folderNo name } } } } } ``` ```graphql { title = "Result" } { "data": { "useCustomer": { "window": { "items": [ { "layoutName": "10201. Purrebrev 1*", "joinup_Folder": { "folderNo": 18, "name": "102. Utskriftsoppsett" } }, { "layoutName": "10202. Purrebrev 2*", "joinup_Folder": { "folderNo": 18, "name": "102. Utskriftsoppsett" } }, { "layoutName": "10203. Rentenota 1*", "joinup_Folder": { "folderNo": 18, "name": "102. Utskriftsoppsett" } } ] } } } } ``` In this example, the node name was `joinup_Folder`, there `Folder` is the name of the table that we joined from `Window`. Sometimes, a table can be joined from another via multiple relations. An example is the `Associate` table that can be joined via a customer, supplier, employee, or other relations. In this case, when multiple relations are available the node name has the form `joinup__via_`. For the mentioned example, these names are `joinup_Associate_via_Customer`, `joinup_Associate_via_Supplier`, `joinup_Associate_via_Employee`, etc. The example with `joinup_Folder` is a particular case of this general form, when the table and relation have the same name. For such cases the node name has the form `joinup_` (to avoid namings such as `joinup_Folder_via_Folder` which is unnecessarily repetitive). In the next example, the `Associate` table is joined upwards twice from the `Order` table, once via the `Customer` relation, and once via the `Employee` relation. ```graphql { title = "Query" } { useCompany(no:9112233) { order(first: 2) { items { orderNo customerNo employeeNo joinup_Associate_via_Customer { associateNo customerNo name } joinup_Associate_via_Employee { associateNo employeeNo name } } } } } ``` ```graphql { title = "Result" } { "data": { "useCompany": { "order": { "items": [ { "orderNo": 1, "customerNo": 10010, "employeeNo": 101, "joinup_Associate_via_Customer": { "associateNo": 11, "customerNo": 10010, "name": "Nordic Designers" }, "joinup_Associate_via_Employee": { "associateNo": 967, "employeeNo": 101, "name": "Erika Karlson" } }, { "orderNo": 2, "customerNo": 10123, "employeeNo": 114, "joinup_Associate_via_Customer": { "associateNo": 336, "customerNo": 10123, "name": "Lars Erikson" }, "joinup_Associate_via_Employee": { "associateNo": 425, "employeeNo": 114, "name": "Daniel Gunnarson" } } ] } } } } ``` For one-to-many relations, that have a downwards direction from parent to child, the prefixed, as mentioned above is `joindown`. The rest of the name format is the same, the general form being `joindown__via_`. When the table and the relation name are the same, the form is simplified to `joindown_`. However, the relation being one-to-many, the result is not a single record but an array of them. This is modeled in the API through connections. Therefore, a node such as `joindown_Translation_via_Window` or `joindown_OrderLine_via_Order` are *connections*. We can see this in the following example, where we fetch the layout name of the first three windows, as well as the tag and Norwegian text from the translations table. ```graphql { title = "Query" } query { useCustomer(no: 1111111) { window(first: 3) { items { layoutName joindown_Translation_via_Window { items { tag norwegian } } } } } } ``` ```graphql { title = "Result" } { "data": { "useCustomer": { "window": { "items": [ { "layoutName": "10201. Purrebrev 1*", "joindown_Translation_via_Window": { "items": [ { "tag": "10201. Purrebrev 1*", "norwegian": "10201. Purrebrev 1*" }, { "tag": "", "norwegian": "10201. Purrebrev 1*" } ] } }, { "layoutName": "10202. Purrebrev 2*", "joindown_Translation_via_Window": { "items": [ { "tag": "10202. Purrebrev 2*", "norwegian": "10202. Purrebrev 2*" }, { "tag": "", "norwegian": "10202. Purrebrev 2*" } ] } }, { "layoutName": "10203. Rentenota 1*", "joindown_Translation_via_Window": { "items": [ { "tag": "10203. Rentenota 1*", "norwegian": "10203. Rentenota 1*" }, { "tag": "", "norwegian": "10203. Rentenota 1*" } ] } } ] } } } } ``` A second example shows a query to the order table that is joined with the oder line table. We retrieve the first 10 orders and for each order the first three lines. We also fetch the total number of orders and for each order, the total number of lines. ```graphql { title = "Query" } { useCompany(no: 9112233) { order(first: 10) { totalCount items { orderNo joindown_OrderLine_via_Order(first: 3) { items { lineNo amountDomestic } } } } } } ``` ```graphql { title = "Result" } { "data": { "useCompany": { "order": { "totalCount": 1340, "items": [ { "orderNo": 1, "joindown_OrderLine_via_Order": { "items": [ { "lineNo": 1, "amountDomestic": 100 }, { "lineNo": 2, "amountDomestic": 250 } ] } }, { "orderNo": 2, "joindown_OrderLine_via_Order": { "items": [ { "lineNo": 1, "amountDomestic": 980 }, { "lineNo": 2, "amountDomestic": 1500 }, { "lineNo": 3, "amountDomestic": 1999 } ] } }, ... ] } } } } ``` > [!WARNING] > > Because the execution of queries for joined tables is optimized for performance, the `totalCount` and pagination does not work as described in this document. To understand the problem and the workaround see [Unoptimized queries](../../features/unoptimized.md). ## Parameters In the previous examples we have seen the use of the `first` parameter when querying various tables. This is not the only available argument. Each connection type has several parameters, which are as follows: - `first` and `after` (and optionally `skip`) for forward pagination (see [Pagination](../../features/pagination.md)) - `last` and `before` (and optionally `skip`) for backward pagination (see [Pagination](../../features/pagination.md)) - `filter` for filtering data (see [Filtering](../../features/filtering.md)) - `distinct` for returning only distinct values (see [Distinct](./distinct.md)) - `sortOrder` for sorting the results (see [Sorting](../../features/sorting.md)) - `groupBy` for grouping the results (see [Grouping](./grouping.md)) - `having` for filtering the results after grouping (see [Grouping](./grouping.md)) - `unoptimized` for performing unoptimized request for some category of queries for joined tables that could potentially return incorrent results otherwise (see [Unoptimized queries](../../features/unoptimized.md)) These parameters will be described in the *GraphQL Features* sections. Grouping /businessnxtapi/schema/queries/grouping page API documentation on grouping data, including SQL examples, GraphQL equivalents, and parameters for filtering, sorting, paginating, and applying aggregate functions. 2025-04-15T09:48:42+02:00 # Grouping API documentation on grouping data, including SQL examples, GraphQL equivalents, and parameters for filtering, sorting, paginating, and applying aggregate functions. ## Overview An important scenario when working with data is grouping. Grouping allows you to aggregate data based on a specific column or columns. This enables fetching summary of data based on various aggregate functions. For example, you may want to group orders by the customer column and then count the number of orders and their total value in each group. In SQL, this can be done using the `GROUP BY` clause as follows: ```sql SELECT CustNo, Count([OrdNo]), SUM(DNOrdSum) FROM [Ord] GROUP BY CustNo ``` When grouping data, you can select: - The columns that you want to group by. The rows with the same value in the selected columns are grouped together. - The aggregate functions that you want to apply to the grouped data. The aggregate functions are applied to the columns that are not included in the `GROUP BY` clause. Because the `WHERE` clause is evaluated before the `GROUP BY` clause, you can use the `WHERE` clause to filter the rows that are included in the grouping. For instance, if we want to take into account only the orders in a specific interval (let's say the current month) we can apply a filter as follows: ```sql SELECT CustNo, Count(OrdNo), SUM(DNOrdSum) FROM Ord WHERE ChDt > 20231101 GROUP BY CustNo ``` However, filtering the rows in the `WHERE` clause is not always possible. For instance, if we want to retrieve data only for customers that made purchases over some specific amount, we cannot use the `WHERE` clause because the aggregated sum is not a table column. In this case, we can use the `HAVING` clause to filter the groups. The `HAVING` clause is evaluated after the `GROUP BY` clause and it can be used to filter the groups based on aggregate functions. An example is shown here: ```sql SELECT CustNo, Count(OrdNo), SUM(DNOrdSum) FROM Ord WHERE ChDt > 20231101 GROUP BY CustNo HAVING SUM(DNOrdSum) > 1000 ``` You can also specify a sorting order for the resulting data using the `SORT BY` clause. The `SORT BY` clause is evaluated after the `HAVING` clause. In the following example, data is sorted ascending by the customer number. ```sql SELECT CustNo, Count(OrdNo), SUM(DNOrdSum) FROM Ord WHERE ChDt > 20231101 GROUP BY CustNo HAVING SUM(DNOrdSum) > 1000 SORT BY CustNo ASC ``` The capabilities to group data and filter based on aggregate functions in available in BNXT GraphQL as well. The following sections describe how to use the `groupBy` and `having` clauses in BNXT GraphQL. ## The connection type In the previous section, we learned about the connection types and how to perform queries using them. Every connection types has a set of parameters that allows to filter, sort, paginate, but also group. An example is shown in the following image for the `order` table: ![Order table](../schema15.png) The following parameters can be used to group data: | Parameter | Description | | --------- | ----------- | | `filter` | Specifies the filter expression. The filter expression is evaluated before the `groupBy` clause. | | `groupBy` | Specifies the columns that you want to group by. The rows with the same value in the selected columns are grouped together. | | `having` | Specifies the aggregate functions that you want to apply to the grouped data. The aggregate functions are applied to the columns that are not included in the `groupBy` clause. The `having` clause is applied to the data after grouping. | | `sortOrder` | Specifies the sort order of the grouped data. | ## Grouping data The following example shows how to group data using the `groupBy` parameter. The example groups the orders by the customer number and then counts the number of orders and their total value in each group. ```graphql { title = "Query" } query read_grouped_orders($cid : Int) { useCompany(no : $cid) { order( groupBy : [{customerNo : DEFAULT}]) { items { customerNo aggregates { count { orderNo } sum { orderSumNetDomestic } } } } } } ``` ```graphql { title = "Result" } { "data": { "useCompany": { "order": { "items": [ { "customerNo": 0, "aggregates": { "count": { "orderNo": 111 }, "sum": { "orderSumNetDomestic": 3195 } } }, { "customerNo": 10125, "aggregates": { "count": { "orderNo": 1 }, "sum": { "orderSumNetDomestic": 0 } } }, ... ] } } } } ``` To filter the date, you can use the `filter` parameter. The following example groups the purchase orders by the customer number and then counts the number of orders and their total value in each group. The orders are filtered by amount, which must be positive, and by the order date. ```graphql { title = "Query" } query read_grouped_orders($cid : Int) { useCompany(no : $cid) { order( filter : {_and : [ {orderSumNetDomestic : {_gt : 0}}, {orderDate : {_gt : 20210101}}, {customerNo : {_not_eq : 0}} ]}, groupBy : [{customerNo : DEFAULT}]) { items { customerNo aggregates { count { orderNo } sum { orderSumNetDomestic } } } } } } ``` ```graphql { title = "Result" } { "data": { "useCompany": { "order": { "items": [ { "customerNo": 10000, "aggregates": { "count": { "orderNo": 289 }, "sum": { "orderSumNetDomestic": 305400 } } }, { "customerNo": 10001, "aggregates": { "count": { "orderNo": 3 }, "sum": { "orderSumNetDomestic": 58047 } } }, ... ] } } } } ``` You can also filter the aggregated data by using the `having` parameter. The following example groups the purchase orders by the customer number and then counts the number of orders and their total value in each group. The orders are filtered by amount, which must be positive, and by the order date. The aggregated data is filtered by the total value of the orders, which must be greater than 100000. ```graphql { title = "Query" } query read_grouped_orders($cid : Int) { useCompany(no : $cid) { order( filter : {_and : [ {orderSumNetDomestic : {_gt : 0}}, {orderDate : {_gt : 20210101}}, {customerNo : {_not_eq : 0}} ]}, groupBy : [{customerNo : DEFAULT}], having : { _sum : { orderSumNetDomestic : {_gt : 100000} } }) { items { customerNo aggregates { count { orderNo } sum { orderSumNetDomestic } } } } } } ``` ```graphql { title = "Result" } { "data": { "useCompany": { "order": { "items": [ { "customerNo": 10000, "aggregates": { "count": { "orderNo": 289 }, "sum": { "orderSumNetDomestic": 305400 } } }, { "customerNo": 10285, "aggregates": { "count": { "orderNo": 186 }, "sum": { "orderSumNetDomestic": 312975 } } } ] } } } } ``` Finally, you can also sort the data using the `sortOrder` parameter. The following example, expands the previous one by retrieving the data sorted by descending customer number. ```graphql { title = "Query" } query read_grouped_orders($cid : Int) { useCompany(no : $cid) { order( filter : {_and : [ {orderSumNetDomestic : {_gt : 0}}, {orderDate : {_gt : 20210101}}, {customerNo : {_not_eq : 0}} ]}, groupBy : [{customerNo : DEFAULT}], having : { _sum : { orderSumNetDomestic : {_gt : 100000} } }, sortOrder : {customerNo : DESC}) { items { customerNo aggregates { count { orderNo } sum { orderSumNetDomestic } } } } } } ``` ```graphql { title = "Result" } { "data": { "useCompany": { "order": { "items": [ { "customerNo": 10285, "aggregates": { "count": { "orderNo": 186 }, "sum": { "orderSumNetDomestic": 312975 } } }, { "customerNo": 10000, "aggregates": { "count": { "orderNo": 289 }, "sum": { "orderSumNetDomestic": 305400 } } } ] } } } } ``` ## The `groupBy` argument The type of the group by argument is an array of non-null objects of the type `_GroupByType`. An example is `Order_GroupByType` for the `Order` table. Each element of the array specifies one column for grouping. Their order in the `GROUP BY` clause is the one in which they appear in the array. Let's take the following example: ```graphql groupBy : [{customerNo : DEFAULT}, [orderDate : DEFAULT]] ``` This groups the data by the `customerNo` and the `orderDate` columns. When you specify a column for grouping, you must also specify the kind of grouping that you want to perform. The possible values are: | Value | Description | | ----- | ----------- | | `DEFAULT` | The default grouping. The rows with the same value in the column are grouped together. | | `ROLLUP` | The `ROLLUP` grouping. This creates a group for each combination of column expressions and also rolls up the results into subtotals and grand totals. | While the column order is not important for the `DEFAULT` grouping, it is important for the `ROLLUP` grouping. For instance, `GROUP BY ROLLUP (A, B, C)` creates groups for each combination of column expressions as show in the following list: ``` A, B, C A, B, NULL A, NULL, NULL NULL, NULL, NULL ``` To learn more about the `ROLLUP` grouping, see the [SELECT - GROUP BY- Transact-SQL](https://docs.microsoft.com/en-us/sql/t-sql/queries/select-group-by-transact-sql?view=sql-server-ver15#rollup) documentation. To show how this work, lets take the following example: select transactions grouped by account number and period. First, we use `DEFAULT` for account number and `ROLLUP` for `period`: ```graphql { title = "Query" } query read_gla_transactions_grouped($cid : Int) { useCompany(no : $cid) { generalLedgerTransaction( filter : { _and: [ {accountNo : {_gte : 3000}}, {year : {_gte : 2020}} ]}, groupBy: [ {accountNo : DEFAULT}, {period : ROLLUP}, ], having : { _sum : {postedAmountDomestic : {_not_eq : 0}} }) { items { accountNo period aggregates { sum { postedAmountDomestic } } } } } } ``` ```graphql { title = "Result" } { "data": { "useCompany": { "generalLedgerTransaction": { "items": [ { "accountNo": 3000, "period": 12, "aggregates": { "sum": { "postedAmountDomestic": -4950 } } }, { "accountNo": 3000, "period": 0, "aggregates": { "sum": { "postedAmountDomestic": -4950 } } }, { "accountNo": 4000, "period": 12, "aggregates": { "sum": { "postedAmountDomestic": 450 } } }, { "accountNo": 4000, "period": 0, "aggregates": { "sum": { "postedAmountDomestic": 450 } } }, { "accountNo": 4410, "period": 12, "aggregates": { "sum": { "postedAmountDomestic": 13035 } } }, { "accountNo": 4410, "period": 0, "aggregates": { "sum": { "postedAmountDomestic": 13035 } } } ] } } } } ``` Due to the applied filter, the result includes the account numbers 3000, 4000, and 4410 and the period 12. The result is a series of groups for the following combinations: | Account number | Period | | -------------- | ------ | | 3000 | 12 | | 3000 | 0 | | 4000 | 12 | | 4000 | 0 | | 4410 | 12 | | 4410 | 0 | Next, we change the grouping type use `ROLLUP` for account number and `DEFAULT` for `period`: ```graphql { title = "Query" } query read_gla_transactions_grouped($cid : Int) { useCompany(no : $cid) { generalLedgerTransaction( filter : { _and: [ {accountNo : {_gte : 3000}}, {year : {_gte : 2020}} ]}, groupBy: [ {accountNo : ROLLUP}, {period : DEFAULT}, ], having : { _sum : {postedAmountDomestic : {_not_eq : 0}} }) { items { accountNo period aggregates { sum { postedAmountDomestic } } } } } } ``` ```graphql { title = "Result" } { "data": { "useCompany": { "generalLedgerTransaction": { "items": [ { "accountNo": 3000, "period": 12, "aggregates": { "sum": { "postedAmountDomestic": -4950 } } }, { "accountNo": 4000, "period": 12, "aggregates": { "sum": { "postedAmountDomestic": 450 } } }, { "accountNo": 4410, "period": 12, "aggregates": { "sum": { "postedAmountDomestic": 13035 } } }, { "accountNo": 0, "period": 12, "aggregates": { "sum": { "postedAmountDomestic": 8535 } } } ] } } } } ``` The result is a series of groups for the following combinations: | Account number | Period | | -------------- | ------ | | 3000 | 12 | | 4000 | 12 | | 4410 | 12 | | 0 | 12 | Finally, let's use `ROLLUP` for both account number and period: ```graphql { title = "Query" } query read_gla_transactions_grouped($cid : Int) { useCompany(no : $cid) { generalLedgerTransaction( filter : { _and: [ {accountNo : {_gte : 3000}}, {year : {_gte : 2020}} ]}, groupBy: [ {accountNo : ROLLUP}, {period : ROLLUP}, ], having : { _sum : {postedAmountDomestic : {_not_eq : 0}} }) { items { accountNo period aggregates { sum { postedAmountDomestic } } } } } } ``` ```graphql { title = "Result" } { "data": { "useCompany": { "generalLedgerTransaction": { "items": [ { "accountNo": 3000, "period": 12, "aggregates": { "sum": { "postedAmountDomestic": -4950 } } }, { "accountNo": 3000, "period": 0, "aggregates": { "sum": { "postedAmountDomestic": -4950 } } }, { "accountNo": 4000, "period": 12, "aggregates": { "sum": { "postedAmountDomestic": 450 } } }, { "accountNo": 4000, "period": 0, "aggregates": { "sum": { "postedAmountDomestic": 450 } } }, { "accountNo": 4410, "period": 12, "aggregates": { "sum": { "postedAmountDomestic": 13035 } } }, { "accountNo": 4410, "period": 0, "aggregates": { "sum": { "postedAmountDomestic": 13035 } } }, { "accountNo": 0, "period": 0, "aggregates": { "sum": { "postedAmountDomestic": 8535 } } } ] } } } } ``` This time, we get the following combinations: | Account number | Period | | -------------- | ------ | | 3000 | 12 | | 3000 | 0 | | 4000 | 12 | | 4000 | 0 | | 4410 | 12 | | 4410 | 0 | | 0 | 0 | ## The `having` argument The type of the `having` argument is an object of the type `_HavingType`. An example is `Order_HavingType` for the `Order` table. The `having` clause is applied to the data after grouping and defines filters on aggregated data. The `_HavingType` type has the following fields: - all the columns in the table - all the available aggregate functions, such as `SUM` and `COUNT`, but prefixed with an underscore: `_sum`, `_count`, etc. - `_and` and `_or` operators that allow to combine multiple conditions The available aggregate functions are listed in the following table: | Aggregate | Column types | Description | | --------- | ------------ | ----------- | | `sum` | numerical | The sum of all the values. | | `sumDistinct` | numerical | The sum of all distinct values. | | `average` | numerical | The average of all the values. | | `averageDistinct` | numerical | The averate of all distinct values. | | `count` | numerical | The number of the items. | | `countDistinct` | numerical | The number of distinct items. | | `minimum` | numerical, date, time | The minimum value. | | `maximum` | numerical, date, time | The maximum value. | | `variance` | numerical | The statistical variance of all the values. | | `varianceDistinct` | numerical | The statistical variance of all the distinct values. | | `variancePopulation` | numerical | The statistical variance for the population of all the values. | | `variancePopulationDistinct` | numerical | The statistical variance for the population of all the distinct values. | | `standardDeviation` | numerical | The statistical standard deviation of all the values. | | `standardDeviationDistinct` | numerical | The statistical standard deviation of all the distinct values. | | `standardDeviationPopulation` | numerical | The statistical standard deviation for the population of all the values. | | `standardDeviationPopulationDistinct` | numerical | The statistical standard deviation for the population of all the distinct values. | In the previous example we have used this condition: ```graphql having : {_sum : {orderSumNetDomestic : {_gt : 100000}}} ``` This is equivalent to the following SQL clause: ```sql HAVIG SUM(DNOrdSum) > 100000 ``` Because of the nature of GraphQL, the order of fields is: 1. aggregate function (AF) 2. column name (COL) 3. operator (OP) 4. value Therefore, the specification `{AF : {COL : {OP : value}}}` is translated to `AF(COL) OP value` and not `AF(COL OP value)`. You can build complex filters such as the following where we select all the groups either have the count of orders greater than 100 or the sum of the order values is between 100000 and 200000: ```graphql having : { _or: [ {_count :{orderNo : {_gt : 100}}}, { _and : [ {_sum : {orderSumNetDomestic : {_gt : 100000}}}, {_sum : {orderSumNetDomestic : {_lt : 200000}}}, ] } ] } ``` > [!TIP] > > The operators used in `having` clauses are the same used for filter expressions (for the `filter` argument). To learn more about these, see [Filtering](../../features/filtering.md). ## Limitations There are also some limitations when grouping data that you must be aware of: - You cannot use pagination. The `first`/`after` and `before`/`last` arguments do not work. - You cannot group by or filter data from a joined table. - Sorting does not include the aggregated data. Aggregates /businessnxtapi/schema/queries/aggregates page Aggregate functions in GraphQL for computing values in table fields. Supports sum, average, count, min, max, variance, and more. 2025-04-15T09:48:42+02:00 # Aggregates Aggregate functions in GraphQL for computing values in table fields. Supports sum, average, count, min, max, variance, and more. > [!WARNING] > > This schema section is obsolete and will be removed in the future. > > Use grouping for fetching aggregated data. See [Grouping](./grouping.md) for more information. Business NXT GraphQL supports computing aggregate values for table fields. Aggregate functions ignore null values in the tables. These are deterministic and return the same value each time that they are called. They are similar to the aggregate functions available in SQL. The supported aggregates are listed in the following table. The returned values are computed from the selection determined by provided filter. The filter is optional and if not specified all the records in the table are included. | Aggregate | Column types | Description | | --------- | ------------ | ----------- | | `sum` | numerical | The sum of all the values. | | `sumDistinct` | numerical | The sum of all distinct values. | | `average` | numerical | The average of all the values. | | `averageDistinct` | numerical | The averate of all distinct values. | | `count` | numerical | The number of the items. | | `countDistinct` | numerical | The number of distinct items. | | `minimum` | numerical, date, time | The minimum value. | | `maximum` | numerical, date, time | The maximum value. | | `variance` | numerical | The statistical variance of all the values. | | `varianceDistinct` | numerical | The statistical variance of all the distinct values. | | `variancePopulation` | numerical | The statistical variance for the population of all the values. | | `variancePopulationDistinct` | numerical | The statistical variance for the population of all the distinct values. | | `standardDeviation` | numerical | The statistical standard deviation of all the values. | | `standardDeviationDistinct` | numerical | The statistical standard deviation of all the distinct values. | | `standardDeviationPopulation` | numerical | The statistical standard deviation for the population of all the values. | | `standardDeviationPopulationDistinct` | numerical | The statistical standard deviation for the population of all the distinct values. | Aggregates can be computed on all the tables that can be querried from GraphQL. Every field in the schema used to query a table (having the same name as the table) has a companion field used to execute aggregate functions. This field has the name format `_aggregate`. For example, for reading data from the orders table, there is a field called `order` and for reading aggregate values there is a field called `order_aggregate`. ![System table](../aggregate1.png) The `
_aggregate` field has a query argument that represents a filter. This is the same filter object used to query data from the table. This is documented in the [Filtering](../../features/filtering.md) page. The type of the `
_aggregate` field has the name format `Query__
Aggregate_Node`. For instance, for the `order_aggregate` field the type name is `Query_UseCompany_OrderAggregate_Node`. This type contains one field for each aggregate function listed in the above table. This can be seen in the GraphiQL document explorer as follows: ![System table](../aggregate2.png) The `minimum` and `maximum` fields are of a type with the name format `
WithDateTimeAggregate`. This type includes fields that represent dates and times. The others fields have a different the type that only contains numerical columns. This type has the name format `
Aggregate`. For instance, for the `Order` table, the type that includes dates and times is called `OrderWithDateTimeAggregate`, and the type that only includes numerical fields is called `OrderAggregate`. | Only numerical fields | Numerical + Date/Time fields | | --------------------- | ---------------------------- | | ![System table](../aggregate3.png) | ![System table](../aggregate4.png) | Executing aggregate functions is demonstrated with the following example: ```graphql { title = "Query" } query read($cid : Int!) { useCompany(no: $cid) { order_aggregate { count { orderNo } sum { orderSumNetDomestic vatAmountDomestic } average { orderSumNetDomestic vatAmountDomestic } minimum { orderDate orderSumNetDomestic vatAmountDomestic } maximum { orderDate orderSumNetDomestic vatAmountInCurrency } variance { orderSumNetDomestic vatAmountDomestic } } } } ``` ```graphql { title = "Result" } { "data": { "useCompany": { "order_aggregate": { "count": { "orderNo": 344 }, "sum": { "orderSumNetDomestic": 26930, "vatAmountDomestic": 7952.5 }, "average": { "orderSumNetDomestic": 73.174085, "vatAmountDomestic": 19.363742 }, "minimum": { "orderDate": 20130105, "orderSumNetDomestic": 0, "vatAmountDomestic": 0 }, "maximum": { "orderDate": 20151205, "orderSumNetDomestic": 21950, "vatAmountInCurrency": 5487.5 }, "variance": { "orderSumNetDomestic": 1529866.15103014, "vatAmountDomestic": 93992.56825201 } } } } } ``` To include a filter, you need to specify it as an argument to the `order_aggregate` field, as shown here: ```graphql { title = "Query" } query read($cid : Int!) { useCompany(no: $cid) { order_aggregate(filter : { orderDate : {_gt : 20140101} }) { count { orderNo } sum { orderSumNetDomestic vatAmountDomestic } average { orderSumNetDomestic vatAmountDomestic } minimum { orderDate orderSumNetDomestic vatAmountDomestic } maximum { orderDate orderSumNetDomestic vatAmountInCurrency } variance { orderSumNetDomestic vatAmountDomestic } } } } ``` ```graphql { title = "Result" } { "data": { "useCompany": { "order_aggregate": { "count": { "orderNo": 312 }, "sum": { "orderSumNetDomestic": 25127, "vatAmountDomestic": 6642.5 }, "average": { "orderSumNetDomestic": 82.553134, "vatAmountDomestic": 21.453173 }, "minimum": { "orderDate": 20140102, "orderSumNetDomestic": 0, "vatAmountDomestic": 0 }, "maximum": { "orderDate": 20151205, "orderSumNetDomestic": 21145, "vatAmountInCurrency": 5675.5 }, "variance": { "orderSumNetDomestic": 1542331.617824, "vatAmountDomestic": 95752.0148624473 } } } } } ``` Distinct values /businessnxtapi/schema/queries/distinct page Guide to fetch distinct table values using distinct argument, translating into SQL. Examples include account groups, customers, and order types, with equivalent groupBy queries. 2025-04-15T09:48:42+02:00 # Distinct values Guide to fetch distinct table values using distinct argument, translating into SQL. Examples include account groups, customers, and order types, with equivalent groupBy queries. It is possible to fetch distinct values from a table using the `distinct` argument. This is a boolean parameter that must be set to `true` to enable distinct data fetching. This translates into the `SELECT DISTINCT` SQL statement. The following example shows how to fetch the distinct account groups from the general ledger table: ```graphql { title = "Query" } query read_distinct_account_groups($cid : Int!) { useCompany(no: $cid) { generalLedgerAccount(distinct : true) { items { accountGroup } } } } ``` ```graphql { title = "Result" } { "data": { "useCompany": { "generalLedgerAccount": { "items": [ { "accountGroup": "" }, { "accountGroup": "100K_FORSKNING_UTVIKLING" }, { "accountGroup": "102K_KONSESJON_PATENT_LISENSER" }, { "accountGroup": "107K_UTSATT_SKATTEFORDEL" }, { "accountGroup": "108K_GOODWILL" } ... ] } } } } ``` It is possible to select distinct values for multiple fields. The following example shows how to distinct customers and order types from the order table: ```graphql { title = "Query" } query read_orders($cid : Int!, $dt : Int) { useCompany(no: $cid) { order( filter : {changedDate : {_gt : $dt}}, distinct : true, first : 10) { pageInfo { hasNextPage startCursor endCursor } items { customerNo orderType } } } } ``` ```graphql { title = "Result" } { "data": { "useCompany": { "order": { "pageInfo": { "hasNextPage": false, "startCursor": "MQ==", "endCursor": "NQ==" }, "items": [ { "customerNo": 0, "orderType": 1 }, { "customerNo": 10000, "orderType": 1 }, { "customerNo": 10001, "orderType": 1 }, { "customerNo": 10002, "orderType": 2 }, { "customerNo": 10286, "orderType": 1 } ] } } } } ``` As mentioned before, the use of the `distinct` argument will produce a `SELECT DISTINCT` SQL statement for fetching data. The following two SQL statements are roughly equivalent: ```sql SELECT DISTINCT a,b,c FROM T ``` ```sql SELECT a,b,c FROM T GROUP BY a,b,c ``` Therefore, the same results can be achieved, typically, by using the `groupBy` argument. The following example shows how to fetch the same distinct customers and order types from the order table using the `groupBy` argument: ```graphql { title = "Query" } query read_orders($cid : Int!, $dt : Int) { useCompany(no: $cid) { order( filter : {changedDate : {_gt : $dt}}, groupBy : [ {customerNo : DEFAULT}, {orderType : DEFAULT} ], sortOrder : { customerNo : ASC, _thenBy : { orderType : DESC} } first : 10) { pageInfo { hasNextPage startCursor endCursor } items { customerNo orderType } } } } ``` ```graphql { title = "Result" } { "data": { "useCompany": { "order": { "pageInfo": { "hasNextPage": false, "startCursor": "MQ==", "endCursor": "NQ==" }, "items": [ { "customerNo": 0, "orderType": 1 }, { "customerNo": 10000, "orderType": 1 }, { "customerNo": 10001, "orderType": 1 }, { "customerNo": 10002, "orderType": 2 }, { "customerNo": 10286, "orderType": 1 } ] } } } } ``` > [!NOTE] > > When requesting distinct values, the `totalCount` field will still return the total number of records in the table that match the provided filter (if any was given), not the number of distinct records. > > It is recommended that you do not use `totalCount` when fetching distinct values, as it will indicate a misleading quantity. ## Obsolete distinct values > [!WARNING] > > This schema section is obsolete and will be removed in the future. > > It is recommended to use the `distinct` argument for fetching distinct values, as described above. An option to fetch distinct values of a field is also available under the [aggregates](./aggregates.md) field with a subfield called `distinct`. Aggregate types have the name of the form `Aggregate`, such as `AssociateAggregate`. On the other hand, the field used for fetching distinct values have their own type, using the format `Distinct`, such as `AssociateDistinct`. ![Distinct field](../aggregate5.png) The distinct type have the same fields as the aggregate types, which are the columns of the database tables. However, their type is a list of values and not a single value. The following image shows a snippet of the `AssociateDistinct` type: ![Distinct type](../aggregate6.png) The following table shows an example for fetching the distinct post codes and country codes from the `Associate` table: ```graphql { title = "Query" } query($cid:Int) { useCompany(no: $cid) { associate_aggregate { distinct { postCode countryNo } } } } ``` ```graphql { title = "Result" } { "data": { "useCompany": { "associate_aggregate": { "distinct": { "postCode": [ "", "AL20 0XX", "AL3 8JH", "B11 2BH", "B11 3RR", "B42 1DU", "B60 3DR", ... "WV10 7Ln", "WV14 OQL", "WV15 5HR" ], "countryNo": [ 0, 1, 33, 44, 46, 47, 353 ] } } } } } ``` You can use the same filters as for the aggregate functions. Here is an example: ```graphql { title = "Query" } query read($cid:Int) { useCompany(no: $cid) { associate_aggregate(filter:{ postalArea : {_eq : "London"} }) { distinct { postCode } } } } ``` ```graphql { title = "Result" } { "data": { "useCompany": { "associate_aggregate": { "distinct": { "postCode": [ "L63 4DJ", "MW10 2XA" ] } } } } } ``` Fetching distinct values for unique primary keys will not provide any benefit over a regular query for that specific field. In other words, the following two queries will return the same date, although in different forms. ```graphql { title = "Query" } query read($cid:Int) { useCompany(no: $cid) { associate_aggregate(filter:{ postalArea : {_eq : "London"} }) { distinct { associateNo } } associate(filter:{ postalArea : {_eq : "London"} }) { items { associateNo } } } } ``` ```graphql { title = "Result" } { "data": { "useCompany": { "associate_aggregate": { "distinct": { "associateNo": [ 28, 149 ] } }, "associate": { "items": [ { "associateNo": 28 }, { "associateNo": 149 } ] } } } } ``` Fetching user's available customers /businessnxtapi/schema/queries/customers page Fetch multiple customers linked to a user via GraphQL, using the availableCustomers query field with optional pagination and sorting. 2025-04-15T09:48:42+02:00 # Fetching user's available customers Fetch multiple customers linked to a user via GraphQL, using the availableCustomers query field with optional pagination and sorting. Typically, a user is associated with a single Visma.net customer. However, it is possible that one user is linked to multipled customers. Business NXT GraphQL allows to fetch all these customers. The list of customers linked to the authenticated user is available with a special field under the `Query` type. This field is called `availableCustomers`. It is similar to the connection types seen so far, except that the `pageInfo` field is missing. ![availableCustomers field](../availablecustomers.png) Here is a query example: ```graphql { title = "Query" } { availableCustomers { totalCount items { name vismaNetCustomerId } } } ``` ```graphql { title = "Result" } { "data": { "availableCustomers": { "totalCount": 1, "items": [ { "name": "Test Customer for Business NXT", "vismaNetCustomerId": 1234567 } ] } } } ``` The `availableCustomers` field has several optional arguments: - an argument called `first` that indicates the number of elements to be fetched from the top of the customers list - an argument called `last` that indicates the number of elements to be fetched from the back of the customers list; if both `first` and `last` are present, `first` is used and `last` is ignored - an argument called `sortOrder` that defines the sorting order of the result Fetching user's available companies /businessnxtapi/schema/queries/companies page Retrieve a list of companies available to an authenticated user using the availableCompanies GraphQL field, with optional filtering and sorting arguments. 2025-04-15T09:48:42+02:00 # Fetching user's available companies Retrieve a list of companies available to an authenticated user using the availableCompanies GraphQL field, with optional filtering and sorting arguments. The list of companies available to the authenticated user is available with a special field available under the `Query` type. This field is called `availableCompanies`. It is similar to the connection types seen so far, except that the `pageInfo` field is missing. ![availableCompanies field](../availablecompanies.png) Here is a query example: ```graphql { title = "Query" } { availableCompanies { totalCount items { name vismaNetCompanyId vismaNetCustomerId } } } ``` ```graphql { title = "Result" } { "data": { "availableCompanies": { "totalCount": 2, "items": [ { "name": "Marius Test AS", "vismaNetCompanyId": 5280... "vismaNetCustomerId": 9123... }, { "name": "JAM&WAFFLES", "vismaNetCompanyId": 5199... "vismaNetCustomerId": 9456... } ] } } } ``` The `availableCompanies` field has several optional arguments: - an argument called `customerNo` that indicates the Visma.net number of the customer for which available companies should be retrieved - an argument called `first` that indicates the number of elements to be fetched from the top of the companies list - an argument called `last` that indicates the number of elements to be fetched from the back of the companies list; if both `first` and `last` are present, `first` is used and `last` is ignored - an argument called `sortOrder` that defines the sorting order of the result To fetch companies specific to a particular customer, pass its Visma.net identifier in the `customerNo` parameter, as shown in the following example: ```graphql { title = "Query" } { availableCompanies(customerNo : 12345678) { totalCount items { name vismaNetCompanyId } } } ``` ```graphql { title = "Result" } { "data": { "availableCompanies": { "totalCount": 2, "items": [ { "name": "Marius Test AS", "vismaNetCompanyId": 5280... }, { "name": "JAM&WAFFLES", "vismaNetCompanyId": 5199... } ] } } } ``` If the `customerNo` parameter is missing, the companies available to all the available customers in the list are returned. To retrieve the list of available customers see [Fetching user's available customers](customers.md). ## Service context When you authenticate with client credentials (using a client ID and a client secret), fetching the list of available companies requires specifying a customer no (as described earlier). You can find out the customer number in two ways: - Authenticate with a Visma.net user and use the `availableCustomers` field to fetch the list (see [Fetching user's available customers](customers.md)). - Retrieve this information from Visma.Net Admin. To do the latter: - Logon to - Open the *Configuration* tab - Copy the value for *Visma.net Customer ID* ![Visma.Net Admin Configuration](../vismaadm1.png) When then customer ID is not provided, an error is returned, as follows: ```json { "errors": [ { "message": "GraphQL.ExecutionError: Could not fetch the list of available companies. Customer number is mandatory." } ], "data": { "availableCompanies": null }, "extensions": { "vbnxt-trace-id": "..." } } ``` Model information /businessnxtapi/schema/queries/modelinfo page Query model information using useModel to retrieve details on tables, columns, relations, domains, processings, reports, and folders. 2025-04-15T09:48:42+02:00 # Model information Query model information using useModel to retrieve details on tables, columns, relations, domains, processings, reports, and folders. You can query information from the data model. This is possible using the `useModel` field in the top-level query. This allows to retrieve information about the following entities: - tables - columns - relations - domains and domain members - processings - reports - folders The information that is available for each entity include: - primary key (such as `tableNo` for tables, `columnNo` and `tableNo` for columns, etc.) - identifier, which is a language-independent name of the field; this is what the GraphQL schema uses for entity names - name, which is a language-specific name for the entity - access restrictions and availability In addition, each type of entity has it's own specific fields. The languages supported for translations are: - English (this is the default, if no language is specified) - Norwegian - Swedish - Danish Here is an example for fetching the ID, identifier, name in Norwgian, and the database type it belongs to (which can be either `COMPANY` or `SYSTEM`) for all the existing tables: ```graphql { title = "Query" } query get_tables_info { useModel { tables(lang: NORWEGIAN) { tableNo name identifier databaseType } } } ``` ```graphql { title = "Result" } { "data": { "useModel": { "tables": [ { "tableNo": 1, "name": "Arbeidsområde­vindu", "identifier": "WorkspaceWindow", "databaseType": "SYSTEM" }, { "tableNo": 2, "name": "Arbeidsområde­element", "identifier": "WorkspacePageElement", "databaseType": "SYSTEM" }, # ... { "tableNo": 457, "name": "Inngående betalingslinje", "identifier": "AutopayVipPaymentLine", "databaseType": "COMPANY" }, { "tableNo": 458, "name": "Inngående betaling ekstratekst", "identifier": "AutopayVipTextInfo", "databaseType": "COMPANY" } ] } } } ``` On the other hand, it's possible to ask for this information for a specific table by specifying the table number: ```graphql { title = "Query" } query get_table_info { useModel { tables(lang: NORWEGIAN, tableNo : 152) { tableNo name identifier databaseType } } } ``` ```graphql { title = "Result" } { "data": { "useModel": { "tables": [ { "tableNo": 152, "name": "Aktør", "identifier": "Associate", "databaseType": "COMPANY" } ] } } } ``` Similarly, you can query, for instance, for: - all the columns in the system - all the columns of a specified table - a single column specifed by its column number In the next example, we query information about all the columns from the `Associate` table: ```graphql { title = "Query" } query get_columns_info { useModel { columns(lang: NORWEGIAN, tableNo : 152) { columnNo tableNo name identifier } } } ``` ```graphql { title = "Result" } { "data": { "useModel": { "columns": [ { "columnNo": 4028, "name": "Aktørnr", "identifier": "AssociateNo" }, { "columnNo": 4029, "name": "Navn", "identifier": "Name" }, # ... ] } }} ``` On the other hand, in the next example, we query information about the associate number column (from the `Associate` table): ```graphql { title = "Query" } query get_column_info { useModel { columns(lang: NORWEGIAN, columnNo: 4028) { columnNo tableNo name identifier } } } ``` ```graphql { title = "Result" } { "data": { "useModel": { "columns": [ { "columnNo": 4028, "tableNo": 152, "name": "Aktørnr", "identifier": "AssociateNo" } ] } } } ``` You can additionaly query for domain information for a column, as shown in the following example: ```graphql { title = "Query" } query read_column_info { useModel { columns(lang: NORWEGIAN, columnNo : 3363) { columnNo tableNo name domain { domainNo name length columnWidth storeFixedDecimals displayFixedDecimals fixedDecimals dataType fieldJustification fileName domainMembers { name identifier valueNo includeValue initiallyOn } } } } } ``` ```graphql { title = "Result" } { "data": { "useModel": { "columns": [ { "columnNo": 3363, "tableNo": 52, "name": "Ordrenr", "domain": { "domainNo": 555, "name": "Ordrenr", "length": 0, "columnWidth": 8, "storeFixedDecimals": false, "displayFixedDecimals": false, "fixedDecimals": 0, "dataType": "INT_32", "fieldJustification": "RIGHT", "fileName": false, "domainMembers": [] } } ] } } } ``` You can also query domains directly, by specifying the domain number: ```graphql { title = "Query" } query read_domain_info { useModel { domains(lang: NORWEGIAN, domainNo : 555) { domainNo name length columnWidth storeFixedDecimals displayFixedDecimals fixedDecimals dataType fieldJustification fileName domainMembers { name identifier groupName valueNo includeValue groupIncludeValue initiallyOn } } } } ``` ```graphql { title = "Result" } { "data": { "useModel": { "domains": [ { "domainNo": 555, "name": "Ordrenr", "length": 0, "columnWidth": 8, "storeFixedDecimals": false, "displayFixedDecimals": false, "fixedDecimals": 0, "dataType": "INT_32", "fieldJustification": "RIGHT", "fileName": false, "domainMembers": [] } ] } } } ``` Some domains are enumeration types. They define a set of possible values that can be stored in a column. The `domainMembers` field in the domain information query returns a list of domain members. An example is shown below: ```graphql { title = "Query" } query read_domain_info { useModel { domains(lang: NORWEGIAN, domainNo : 111) { domainNo name length columnWidth storeFixedDecimals displayFixedDecimals fixedDecimals dataType fieldJustification fileName domainMembers { name identifier groupName valueNo includeValue groupIncludeValue initiallyOn } } } } ``` ```graphql { title = "Result" } { "data": { "useModel": { "domains": [ { "domainNo": 111, "name": "Dok.­type", "length": 0, "columnWidth": 8, "storeFixedDecimals": false, "displayFixedDecimals": false, "fixedDecimals": 0, "dataType": "INT_32", "fieldJustification": "RIGHT", "fileName": false, "domainMembers": [ { "name": "Purrebrev", "identifier": "Reminder", "groupName": "", "valueNo": 1, "includeValue": -1, "groupIncludeValue": -1, "initiallyOn": false }, { "name": "Rentenota", "identifier": "InterestNote", "groupName": "", "valueNo": 2, "includeValue": -1, "groupIncludeValue": -1, "initiallyOn": false } ] } ] } } } ``` ## Text transformations The translated text may contain several application-specific escape characters or sequences. These are: - `^` for a hyphen - `|` for a new line (`\n`) - `&` for an access key accelerator - `{Ampersand}` for an `&` character When you retrieve the translated texts, these are automatically replaced with the appropriate character or sequence of characters. However, you can opt to perform your own custom replacement. This is possible with the following two arguments, available for all the fields under `useModel`: | Argument | Description | | -------- | ----------- | | `transform` | Indicates the transformation type: `AUTO` (the default option, application defined transformations), `NONE` (no transformation is performed), and `CUSTOM` (user-specified transformations are applied). | | `transformArgs` | Defines the transformation arguments when the `CUSTOM` value is specified for the `transform` argument. | The properties of the `transformArgs` parameter are as follows: | Property | Type | Description | | -------- | ---- | ----------- | | `modifyOptionalHyphen` | Boolean | Indicates whether hyphen replacement will be performed. | | `optionalHyphen` | String | Text for replacing the escape character (`^`) for a hyphen. | | `modifyManualLineBreak` | Boolean | Indicates whether manual line break replacement will be performed. | | `manualLineBreak` | String | Text replacing the escape character for manual line break. | | `modifyAccessKey` | Boolean | Indicates whether access key replacement will be performed. | | `accessKey` | String | Text for replacing the escape character (`&`) for an access key. | | `modifyAmpersandSubstitute` | Boolean | Indicates whether ampersand substitute replacement will be performed. | | `ampersandSubstitute` | String | Text for replacing the `{Ampersand}` escape sequence. | Here is an example for fetching raw texts: ```graphql { title = "Query" } query get_table_info { useModel { tables(lang: NORWEGIAN, tableNo : 138, transform : NONE) { tableNo name identifier } } } ``` ```graphql { title = "Result" } { "data": { "useModel": { "tables": [ { "tableNo": 138, "name": "Ordre^journal", "identifier": "OrderJournal" } ] } } } ``` On the other hand, the following sample shows how to perform user-defined transformations: ```graphql { title = "Query" } query get_table_info { useModel { tables(lang: NORWEGIAN, tableNo : 138, transform : CUSTOM, transformArgs : { modifyOptionalHyphen : true optionalHyphen : "-" }) { tableNo name identifier } } } ``` ```graphql { title = "Result" } { "data": { "useModel": { "tables": [ { "tableNo": 138, "name": "Ordre-journal", "identifier": "OrderJournal" } ] } } } ``` ## Access restrictions and availability Most model entities provide properties that indicate the availability and access restrictions. These properties are: | Property | Type | Description | Applies to | | -------- | ---- |----------- | ---------- | | `visible` | `Bool` | Indicates whether the entity is visible in the on-premise application. | All | | `cloudVisible` | `Bool` |Indicates whether the entity is visible in the BNXT front-end application. | All | | `writable` | `Bool` | Indicates whether the entity is writable in the on-premise application. | Columns, domain members | | `cloudWritable` | `Bool` | Indicates whether the entity is writable in the BNXT front-end application. | Columns, domain members | | `insertable` | `Bool` | Indicates whether the entity is insertable in the on-premise application. | Tables | | `cloudInsertable` | `Bool` | Indicates whether the entity is insertable in the BNXT front-end application. | Tables | | `updatable` | `Bool` | Indicates whether the entity is updatable in the on-premise application. | Tables | | `cloudUpdatable` | `Bool` | Indicates whether the entity is updatable in the BNXT front-end application. | Tables | | `deletable` | `Bool` | Indicates whether the entity is deletable in the on-premise application. | Tables | | `cloudDeletable` | `Bool` | Indicates whether the entity is deletable in the BNXT front-end application. | Tables | | `readAccess` | `Access` | Indicates the read access level for the entity in the on-premise application. | All | | `cloudReadAccess` | `CloudAccess` | Indicates the read access level for the entity in the BNXT front-end application. | All | | `writeAccess` | `Access` | Indicates the write access level for the entity in the on-premise application. | Columns, domain members | | `cloudWriteAccess` | `CloudAccess` | Indicates the write access level for the entity in the BNXT front-end application. | Columns, domain members | | `insertAccess` | `Access` | Indicates the insert access level for the entity in the on-premise application. | Tables | | `cloudInsertAccess` | `CloudAccess` | Indicates the insert access level for the entity in the BNXT front-end application. | Tables | | `updateAccess` | `Access` | Indicates the update access level for the entity in the on-premise application. | Tables | | `cloudUpdateAccess` | `CloudAccess` | Indicates the update access level for the entity in the BNXT front-end application. | Tables | | `deleteAccess` | `Access` | Indicates the delete access level for the entity in the on-premise application. | Tables | | `cloudDeleteAccess` | `CloudAccess` | Indicates the delete access level for the entity in the BNXT front-end application. | Tables | | `deleteAccess` | `Access` | Indicates the delete access level for the entity in the on-premise application. | Tables | | `cloudDeleteAccess` | `CloudAccess` | Indicates the delete access level for the entity in the BNXT front-end application. | Tables | | `executionAccess` | `Access` | Indicates the execution access level for the entity in the on-premise application. | Processings, reports | | `cloudExecutionAccess` | `CloudAccess` | Indicates the execution access level for the entity in the BNXT front-end application. | Processings, reports | | `availability` | `Availability` | Indicates whether the entity is available for use (`CURRENT`), it will be available in the future (`FUTURE`), or it is available but it is deprecated and will be removed (`OBSOLETE`). | All | The `Default` value for `CloudAccess` indicates that the access level is inherited from the on-premise application. For a column to be available in GraphQL for reading, the following conditions must be met: - `availability` should be `CURRENT` - `cloudReadAccess` must be different than `NONE` and `DEFAULT` - if `cloudReadAccess` is `DEFAULT` then `readAccess` must be different than `NONE` Similar rules are used for writing, inserting, updating, deleting, and executing access. ## Model information properties ### Tables The following properties are available for tables : | Property | Type | Description | | -------- | ---- | ----------- | | `tableNo` | `Long` | The table number. | | `name` | `String` | The translated name of the table. | | `identifier` | `String` | A language-independent identifier of the table. | | `databaseType` | `DatabaseTypeNo` | One of `COMPANY` or `SYSTEM`. | | `view` | `bool` | Indicates whether the table is a view. | | `primaryKeys` | `[Long]` | The primary keys' column numbers in the order they're defined in the database. | | `primaryKeyClustered` | `Bool` | Indicates whether the primary key is clustered. | | `orgUnitClass` | `OrgUnitClass` | The property returns one of the twelve values `ORGUNITCLASS01` (1) – `ORGUNITCLASS12` (12) if the table is associated with an organisational unit class, else `NONE` (0). The table will not be visible if the corresponding `CompanyInformation.OrgUnit1Name` – `OrgUnit12Name` column is empty. | | `folderNo` | `Long` | The folder number the table belongs to, or 0 if none. | | `formView` | `Bool` | `true` if new table page elements against this table should initially be displayed in Form view (instead of Grid view). | | `primaryKeyAssignment` | `PrimaryKeyAssignment` | Returns one of the following values for how the last primary key column is assigned a value on rows in the table: `NORMAL` - the ordinary behaviour of the table, `INCREMENTAL` - the last primary key column will get the next unused value when a value suggestion is requested, or `IDENTITY` - The last primary key column is created as an IDENTITY column in the database (the last two only for top tables that does not have a sort sequence column). | | `fromRelationsNo` | `[Long]` | A collection of relation numbers for the relations **from** the table, in priority order. | | `toRelationsNo` | `[Long]` | A collection of relation numbers for the relations **to** the table. | | `processingsNo` | `[Long]` | A collection of processing numbers for the processings in the table. | | `reportsNo` | `[Long]` | A collection of report numbers for the reports in the table. | ### Relations The following properties are available for relations: | Property | Type | Description | | -------- | ---- | ----------- | | `relationNo` | `Long` | The relation number. | | `name` | `String` | The translated name of the relation. | | `identifier` | `String` | A language-independent identifier of the relation. | | `fromTableNo` | `Long` | The table number that this relation refers from. | | `toTableNo` | `Long` | The table number that this relation refers to. | | `fromColumnsNo` | `[Long]` | A collection of column numbers that define the from columns. | | `toColumnsNo` | `[Long]` | A collection of column numbers that define the to columns. | | `switchOfColumnNo` | `Long` | The number of a column for this relation to apply to. | | `switchValue` | `Int` | The value that a column needs to have for this relation to apply. E.g. the debit (or credit) account type column on a voucher row that determines whether the debit (or credit) account number column refers to (1) a customer, (2) supplier, (3) general ledger account, or (4) capital asset. | | `noLookup` | `Bool` | `true` if the relation is not applicable for lookup, but e.g. only for (left outer) join purposes in i.a. layouts. | | `necessity` | `Necessity` | Returns one of the following values for how necessary a corresponding reference is: `REQUIRED`, `OPTIONAL`, or `PASS`. | | `deleteRule` | `DeleteRule` | Returns one of the following values to determine behavior when deleting or discarding a parent row: `CASCADE` will also delete or discard child rows, recursively downwards the hierarchy, or `RESTRICT` that demands that no references exist, e.g. to reject deletion of accounts with transactions. | ### Columns The following properties are available for columns: | Field | Type | Description | | ----- | ---- | ----------- | | `columnNo` | `Long` | The column number. | | `tableNo` | `Long` | The table number that this column belongs to. | | `name` | `String` | The translated name of the column. | | `identifier` | `String` | A language-independent identifier of the column. | | `orgUnitClass` | `OrgUnitClass` | One of the twelve values `ORGUNITCLASS01` – `ORGUNITCLASS12` if the column is associated with an organisational unit class, else `NONE`. The column will not be visible if the corresponding `CompanyInformation.OrgUnit1Name` – `OrgUnit12Name` column is empty. | | `suggestValueInInterval` | `Bool` | `true` if the column supports suggesting values in intervals. | | `breakValues` | `Bool` | `true` if the column by default should show an aggregated value on group and total rows. | | `accumulateOnMerge` | `Bool` | true if the value in the column should be accumulated when rows are merged, e.g. on collection invoices. | | `recalculation` | `Recalculation` | one of the following values for whether the value in the column should be recalculated according to the actual value on the current, parent row (order line), when page elements (e.g. stock balance or shipment) are joined via an underlying table (e.g. product): `NO`, `UNIT`, or `PRICE`. | | `formatting` | `Formatting` | one of the following values for whether the column should apply formatting defined on the row: `NO`, `ROWFRONT`, or `UNDERLINE`. | | `viewZero` | `ViewZero` | one of the following values for whether 0 should be shown if the column is empty: `NO`, `ZEROIFEMPTY`, or `ZEROIFNOTSUMLINE`. | | `debitCreditType` | `DebitCreditType` | one of the following values for how the memory column should be calculated: `NONE`, `DEBIT`, `CREDIT`. | | `currencyHandling` | `CurrencyHandling` | one of the following values for whether the column is involved in currency calculations: `NORMAL`, `LOGISTICSDOMESTICVALUE`, `ACCOUNTINGCURRENCYVALUE`, `ACCOUNTINGDOMESTICVALUE`, `EUROVALUE`, or `CURRENCY2VALUE`. | | `lookupProcessingNo` | `Long` | The number of the processing that declares the lookup dialog for this column, if any. | ### Domains and domain members The following properties are available for domains: | Field | Type | Description | | ----- | ---- | ----------- | | `domainNo` | `Long` | The domain number. | | `name` | `String` | The translated name of the domain. | | `dataType` | `DataType` | The kind of data that is stored in the column. Can be one of: `INT_8`, `INT_16`, `INT_32`, `INT_64`, `DECIMAL`, `LIMITED_STRING`, `UNLIMITED_STRING`, `BLOB`. | | `lenght` | `Int` | The maximum number of characters that can be stored, or 0 if the domain type is not `LIMITED_STRING`. | | `columnWidth` | `Decimal` | The default column width, measured in the average number of characters that will fit in the column. | | `storeFixedDecimals` | `Bool` | `true` if the domain type is `DECIMAL` and a fixed number of decimals should be stored. | | `displayFixedDecimals` | `Bool` | `true` if the domain type is `DECIMAL` and a fixed number of decimals should be displayed by default. | | `fixedDecimals` | `Int` | The actual number of fixed decimals (up to 6) that may be applied by default if the domain type is `DECIMAL`, else 0. | | `fieldJustification` | `FieldJustification` | Returns one of the following values for how the values (and the column heading in grid view) should be aligned by default: `CENTER`, `LEFT`, or `RIGHT`. | | `fileName` | `Bool` | `true` if the domain type is LimitedString and file name lookup is applicable. | | `domainMembers` | [`Query_UseModel_Domains_DomainMembers`] | A list of domain member objects. | The following properties are available for domain members: | Field | Type | Description | | ----- | ---- | ----------- | | `valueNo` | `Long` | Unique domain member indentification number. | | `identifier` | `String` | A language-independent identifier of the domain member. | | `name` | `String` | The name of the domain member. | | `includeValue` | `Int` | An integer value to include in the name, or -1 if not applicable. | | `groupName` | `String` | The name of the group the domain member belongs to. | | `groupIncludeValue` | `Int` | An integer value to include in the group name, or -1 if not applicable. | | `initiallyOn` | `Bool` | `true` if this flag domain member is intended to be initially ON. | ### Processings The following properties are available for processings: | Field | Type | Description | | ----- | ---- | ----------- | | `processingNo` | `Long` | The processing number. | | `name` | `String` | The translated name of the processing. | | `identifier` | `String` | A language-independent identifier of the processing. | | `description` | `String` | A textual description of the processing. | | `dialogOnly` | `Bool` | `true` if the processing is intended to only show a dialog, typically visualizing one or more columns. Such processings have no parameters, and no processing contributions in the backend, so operations for getting data for a processing dialog, or executing a processing, should not be performed on them. | | `rowIndependent` | `Bool` | `true` if the processing is row-independent. | ### Reports The following properties are available for reports: | Field | Type | Description | | ----- | ---- | ----------- | | `processingNo` | `Long` | The report number. | | `name` | `String` | The translated name of the report. | | `identifier` | `String` | A language-independent report of the processing. | | `description` | `String` | A textual description of the report. | ### Folders The following properties are available for folders: | Field | Type | Description | | ----- | ---- | ----------- | | `folderNo` | `Long` | The folder number. | | `name` | `String` | The translated name of the folder. | | `identifier` | `String` | A language-independent identifier of the folder. | | `isRoot` | `Lool` | `true` if the folder is a root folder. | | `parentFolderNo` | `Long` | The number of the parent folder, or 0 if the folder is a root folder. | | `childFoldersNo` | `[Long]` | A collection of folder numbers for the child folders. | ## Language selection The language for the translations can be specified in two ways: - using the `lang` argument in the query, with one of the following values: `ENGLISH`, `NORWEGIAN`, `SWEDISH`, `DANISH` - using the `langNo` argument in the query, with one of the following values: `44` (English), `45` (Danish), `46` (Swedish), `47` (Norwegian) If both arguments are specified, the `lang` argument is used (`langNo` is ignored). The following two examples are equivalent: ```graphql { title = "Query" } query get_tables_info { useModel { tables(lang: NORWEGIAN) { tableNo name identifier databaseType } } } ``` ```graphql { title = "Result" } query get_tables_info { useModel { tables(langNo: 47) { tableNo name identifier databaseType } } } ``` The `langNo` argument is useful for specifying the language based directly on settings, such as the user's language preference (from the `User` table). Mutations /businessnxtapi/schema/mutations section Guides you on using mutations for inserts, updates, deletes, and running tasks within a GraphQL schema. 2024-09-24T15:57:29+02:00 # Mutations Guides you on using mutations for inserts, updates, deletes, and running tasks within a GraphQL schema. You can perform inserts, updates, and deletes on all the company and system tables. This is possible with the `mutation` operation. Additional mutation operations include running processings and reports as well as executing queries asynchronously. Mutations are available with the `mutation` field at the top of the GraphQL schema, as shown in the following image: ![VBNXT schema](./schema0.png) The Mutation Type /businessnxtapi/schema/mutations/mutation page Root Mutation type defines operations on company and system tables, enabling create, update, delete, and asynchronous queries. 2024-09-24T15:57:29+02:00 # The Mutation Type Root Mutation type defines operations on company and system tables, enabling create, update, delete, and asynchronous queries. The `Mutation` type is the root of all types that define table objects used in these operations, in the same way the `Query` type was the root for all types used in reading operations. In fact, `Mutation` and `Query` are very similar, as `Mutation` contains the following fields: | Field | Description | | ----- | ----------- | | `useCompany` | Provides access to operations on the company tables. | | `useCustomer` | Provides access to operations on the system tables. | | `asyncQuery` | A field for executing another GraphQL query asynchronously. See [Async queries](../async.md). | ![The mutation type](../schema14.png) The type of the `useCompany` field is `Mutation_UseCompany`. This type contains a field for each of the create, update, and delete operation for every company table. Similarly, the type of the `useCustomer` field is `Mutation_UseCustomer`. This type contains a field for each of the create, update, and delete operation for every system table. You can see the two types, `Mutation_UseCustomer` and `Mutation_UseCompany`, side-by-side in the following table: | System table | Company table | | ----------- | --- | | ![System table](../schema5.png) | ![Company table](../schema6.png) | Insert operations /businessnxtapi/schema/mutations/inserts page GraphQL Insert Operations - Define _create mutation fields, arguments, type structure, and example queries, emphasizing field order and inserting between records. 2025-04-15T09:48:42+02:00 # Insert operations GraphQL Insert Operations - Define _create mutation fields, arguments, type structure, and example queries, emphasizing field order and inserting between records. Insert operations are available through a field having the name of the form `_create` of a type having the name of the form `__Result`. For instance, for the `Associate` table, the field is called `associate_create` and its type is `Mutation_UseCompany_Associate_Result`. The form of this operation is the following: ```graphql associate_create(values: [Associate_Input!]!, insertAtRow: FilterExpression_Order, insertPosition: InsertPosition): Mutation_UseCompany_Associate_Result ``` The `_create` field has several arguments: - one mandatory called `values` which is a non-nullable array of objects of type `_Input`. For instance, for the `Associate` table, the input type is `Associate_Input`. - one optional called `insertAtRow` which is a filter expression that identifies the row where the new records should be inserted. - one optional called `insertPosition` which is an enum of type `InsertPosition` that can have the values `BEFORE` and `AFTER`, which is used in conjunction with the `insertAtRow` argument to define the direction of the insertion of the new records. - one optional called `suggest` which is object of type `Suggest__Input`. For instance, for the `Associate` table, this input type is called `Suggest_Associate_Input`. > [!WARNING] > > The `suggest` argument for requesting suggested values is now deprecated. The input types for the `values` argument expose all the columns of the table, except for: - the primary key column (which is automatically incremented) - in-memory columns - utility columns `createdDate` (SQL name `CreDt`), `createdTime` (SQL name `CreTm`), `createdByUser` (SQL name `CreUsr`), `changedDate` (SQL name `ChDt`), `changedTime` (SQL name `ChTm`), and `changedByUser` (SQL name `ChUsr`), which are present in every table. If a table has more than one column as primary keys, then all but the last of these primary key columns must be filled in. The last primary key column is automatically incremented but the others must be explicitly supplied. These are foreign keys to other tables and the records they point to must exist for the operation to succeed. In this case, the type name has the form `_Insert_Input`. The following image shows a snippet of the `Associate_Input` type from the GraphiQL document explorer: ![Associate_Input](../schema8.png) Similarly, the next image shows a snippet of the `AssociateInformation_Insert_Input`, that has two primary key fields: - `associateNo` is also a foreign key to the `Associate` table and must be supplied with an existing value - `lineNo` is a primary key that is auto incremented, and, therefore, not present in the input type. ![AssociateInformation_Insert_Input](../schema10.png) The mutation result type (`__Result`) has two fields: - `affectedRows` indicated the number of rows successfully affected by the operation (in the case of inserts the number of records that were successfully inserted) - `items` an array of objects affected by the mutation operation (for inserts, these are the records that were successfully inserted). Here is a snippet of the `Mutation_UseCompany_Associate_Result` from the GraphiQL document explorer: ![Mutation_UseCompany_Associate_Result](../schema7.png) The objects in the `items` field have the same type that is used for query operations. For the `Associate` table, this is called `Associate` and looks like this: ![Associate](../schema9.png) An insert operation has the following form: ```graphql { title = "Query" } mutation insert($cid : Int!) { useCompany(no: $cid) { associate_create(values:[ { name:"Erik Larson", shortName :"Erik", customerNo: 30101 }, { name :"Frida Olson", shortName:"Frida", customerNo: 30102 } ]) { affectedRows items { associateNo customerNo name shortName languageNo } } } } ``` ```graphql { title = "Result" } { "data": { "useCompany": { "associate_create": { "affectedRows": 2, "items": [ { "associateNo": 547, "customerNo": 30101, "name": "Erik Larson", "shortName": "Erik", "languageNo": 0 }, { "associateNo": 548, "customerNo": 30102, "name": "Frida Olson", "shortName": "Frida", "languageNo": 0 } ] } } } } ``` ### Fields order > [!TIP] > > The order of fields given in the `values` argument is important, because the fields are assigned in this user-given order. Listing the fields in some particular order may result in unexpected results (such as fields having default values). Here is an example when creating an order. When `customerNo` is given before `dueDate`, the results are as expected: ```graphql { title = "Query" } mutation create_order($cid : Int!) { useCompany(no : $cid) { order_create( values:[ { customerNo : 10000 dueDate: 20221124 } ] ) { affectedRows items { orderNo dueDate } } } } ``` ```graphql { title = "Result" } { "data": { "useCompany": { "order_create": { "affectedRows": 1, "items": [ { "orderNo": 6, "dueDate": 20221124, } ] } } } } ``` However, if the `dueDate` is specified before `customerNo`, then the `dueDate` is not set: ```graphql { title = "Query" } mutation create_order($cid : Int!) { useCompany(no : $cid) { order_create( values:[ { dueDate: 20221124 customerNo : 10000 } ] ) { affectedRows items { orderNo dueDate } } } } ``` ```graphql { title = "Result" } { "data": { "useCompany": { "order_create": { "affectedRows": 1, "items": [ { "orderNo": 6, "dueDate": 0, } ] } } } } ``` ### Suggested values When you create a new record, you can ask the system to automatically fill in values for numerical fields. This is done by specifying `null` for the value of a field. ```graphql { title = "Query" } mutation create_voucher($cid: Int, $batchId: Int!) { useCompany(no: $cid) { voucher_create(values: [ { batchNo: $batchId voucherNo : null voucherDate: null amountDomestic: 1300 creditAccountType: 2 creditAccountNo: 50000 }, { batchNo: $batchId voucherNo : null voucherDate: null amountDomestic: 600 debitAccountType: 3 debitAccountNo: 4300 }, { batchNo: $batchId voucherNo : null voucherDate: null amountDomestic: 700 debitAccountType: 3 debitAccountNo: 1930 }]) { affectedRows items { batchNo voucherNo voucherDate debitAccountNo creditAccountNo amountDomestic } } } } ``` ```graphql { title = "Result" } { "data": { "useCompany": { "voucher_create": { "affectedRows": 3, "items": [ { "batchNo": 10002, "voucherNo": 60002, "voucherDate": 20220101, "debitAccountNo": 0, "creditAccountNo": 50000, "amountDomestic": 1300 }, { "batchNo": 10002, "voucherNo": 60002, "voucherDate": 20220101, "debitAccountNo": 4300, "creditAccountNo": 0, "amountDomestic": 600 }, { "batchNo": 10002, "voucherNo": 60002, "voucherDate": 20220101, "debitAccountNo": 1930, "creditAccountNo": 0, "amountDomestic": 700 } ] } } } } ``` > [!TIP] > > The order of specifying the fields when creating or updating a value is important in Business NXT. > > Therefore, an incorrect order of fields with either explicit or null values (or both) can produce in incorrect results. There are several fields in the data model that support suggesting an interval as a value. These fields are: - `customerNo`, `suppliedNo`, and `exployeeNo` in `Associate` - `capitalAssetNo` in `CapitalAsset` - `resourceNo` in `Resource` Because a `null` value cannot be used to indicate an interval, for these fields an additional field with the `_suggest_interval` exists. These fields are of the type `SuggestIntervalType` that has a `from` and `to` field to define the bounds of the interval. For instance, for the `customerNo` field in the `Associate` table, an `customerNo_suggest_interval` field is available. This is shown in the following example: ```graphql { title = "Query" } mutation CreateAssociate($cid : Int) { useCompany(no :$cid) { associate_create(values:[{ name : "Demo Customer AS", customerNo_suggest_interval : { from : 10000, to : 20000 }, countryNo : 47, currencyNo : null }]) { affectedRows items { associateNo customerNo } } } } ``` ```graphql { title = "Result" } { "data": { "useCompany": { "associate_create": { "affectedRows": 1, "items": [ { "associateNo": 1234, "customerNo": 15726 } ] } } } } ``` The rules of thumb for providing inputs are as follows: - fields must be specified in the correct order - if a field should have a specific value, provide it as an input - if a field should remain unassigned, do not list it in the input - if a field should have a system given value, specify `null` for its value - if a field can have a suggest value in a given interval and you want to specify the interval, use the `_suggest_interval` suffixed field, such as `customerNo_suggest_interval`. If you do not want to specify the interval, use the regular input and give the value `null`. ### Deprecated suggested values A deprecated way of requesting suggested values is using an optional argument of the create mutation fields. This field is called `suggest`, and its type is an input type containing all the fields from the table for which the system can automatically fill in values. > [!WARNING] > > This method should not be used because fields listed in the `suggest` argument are always listed last in the request issued to the backend. Since the order of fields is important (as previously mentioned) this will produce incorrect results in many cases. > [!NOTE] > > This argument is deprecated and will soon be removed. Here is an example of the `Suggest_Associate_Input` type for the `Associate` table: ![Suggest_Associate_Input](../schema12.png) The field of all these input types are of one of the following two types: - `Boolean`, in which case you must use the value `true` to include the column in the suggestions list. - `SuggestIntervalType`, in which case you must specify a `from` and `to` value to bound the numeric interval for the value of the field. ![SuggestIntervalType](../schema13.png) > [!NOTE] > > A limited number of fields in the data model support specifying an internal. Here is an example for suggesting values: ```graphql { title = "Query" } mutation create_voucher($cid : Int!, $bno : Int!, $cno : Int!) { useCompany(no : $cid) { voucher_create( values: [{ batchNo : $bno debitAccountNo : 1930 creditAccountNo: $cno customerNo : $cno amountDomestic : 100 }], suggest : { voucherNo : true, voucherDate : true valueDate : true } ) { affectedRows items { batchNo voucherNo voucherDate valueDate } } } } ``` ```graphql { title = "Result" } { "data": { "useCompany": { "voucher_create": { "affectedRows": 1, "items": [ { "batchNo": 2, "voucherNo": 60003, "voucherDate": 20220715, "valueDate": 20220715 } ] } } } } ``` Here is an example with suggesting an interval: ```graphql { title = "Query" } mutation create_customer($cid : Int!) { useCompany(no : $cid) { associate_create( values: [{ name : "Test Customer }], suggest : { customerNo : { from : 10000 to : 19999 } } ) { affectedRows items { associateNo customerNo name } } } } ``` ```graphql { title = "Result" } { "data": { "useCompany": { "associate_create": { "affectedRows": 1, "items": [ { "associateNo": 187, "customerNo": 10928, "name": "Test Customer", } ] } } } } ``` ### Assigning fields from unset variables When fields are assigned from variables, but the variables are not set, the fields are ignored as they were not provided in insert and update operations. For instance, consider the following query: ```graphql { title = "Query" } mutation update_c_ompany( $companyID: Int! $customerNo: Int!, $phone: String, $mobilePhone: String, $privatePhone: String, ) { useCompany(no: $companyID) { associate_update( filter: { customerNo: {_eq: $customerNo } }, value: { phone: $phone mobilePhone: $mobilePhone privatePhone: $privatePhone } ) { affectedRows } } } ``` ```graphql { title = "Result" } { "companyID" : 1234567, "customerNo": 11000, "phone": "9411223344" } ``` The `$mobilePhone` and `$privatePhone` are not set in the variables dictionary. Therefore, the query becomes equivalent to the following: ```graphql mutation update_c_ompany( $companyID: Int! $customerNo: Int!, $phone: String ) { useCompany(no: $companyID) { associate_update( filter: { customerNo: { _eq: $customerNo } }, value: { phone: $phone } ) { affectedRows } } } ``` ### Insert between existing records It is possible to insert new records between existing ones. This is done by using the `insertAtRow` and `insertPosition` optional arguments for the `_create` fields. The `insertAtRow` argument defines a filter for indetifying the position where the new records should be inserted. This filter is applied for every object in the `values` array. Although the filter can be anything, it should include the primary key at least partially. If more that one row matches the filter, the first one is considered the insertion point. The `insertPosition` argument defines where the new records should be inserted in relation to the row identified by the `insertAtRow` filter. The possible values are `BEFORE` and `AFTER`. This argument is optional and if missing, the default value is `BEFORE`. > [!NOTE] > > The objects in the `values` array should not assign values to the primary key fields. These are automatically assigned by the system from the values of the record matching the `insertAtRow` argument. ```graphql { title = "Query" } mutation insert_order_line($cid : Int!, $ono : Int!, $pno3 : String) { useCompany(no : $cid) { orderLine_create(values:[ { productNo : $pno3, quantity : 1 } ], insertAtRow: { _and : [ {orderNo : {_eq : $ono}}, {lineNo : {_eq : 1}}, ]}, insertPosition : AFTER) { affectedRows items { orderNo lineNo sortSequenceNo productNo quantity } } } } ``` ```graphql { title = "Result" } { "data": { "useCompany": { "orderLine_create": { "affectedRows": 1, "items": [ { "orderNo": 2024, "lineNo": 3, "sortSequenceNo": 2, "productNo": "PRO-01", "quantity": 1 } ] } } } } ``` To understand how this works, let's consider the following example of inserting new order lines between existing ones for a particular order. Let's start with the following data for the `OrderLine` table: | OrderNo | LineNo | SortSequenceNo | ProductNo | | ------- | ------ | -------------- | --------- | | 2024 | 1 | 1 | PRO-01 | | 2024 | 2 | 2 | PRO-02 | If we insert a new line with product number `PRO-03` _before_ the line with the primary key `OrderNo=2024, LineNo=1`, the result will be: | OrderNo | LineNo | SortSequenceNo | ProductNo | | ------- | ------ | -------------- | --------- | | 2024 | 1 | 2 | PRO-01 | | 2024 | 2 | 3 | PRO-02 | | 2024 | 3 | 1 | PRO-03 | However, if we insert the same line but _after_ the one with the primary key `OrderNo=2024, LineNo=1`, the result will be: | OrderNo | LineNo | SortSequenceNo | ProductNo | | ------- | ------ | -------------- | --------- | | 2024 | 1 | 1 | PRO-01 | | 2024 | 2 | 3 | PRO-02 | | 2024 | 3 | 2 | PRO-03 | On the other hand, if we insert two order lines at the same time, one for product `PRO-03` and one for product `PRO-04`, the result will be the following when inserting _before_ the record with the primary key `OrderNo=2024, LineNo=1`: | OrderNo | LineNo | SortSequenceNo | ProductNo | | ------- | ------ | -------------- | --------- | | 2024 | 1 | 3 | PRO-01 | | 2024 | 2 | 4 | PRO-02 | | 2024 | 3 | 1 | PRO-03 | | 2024 | 4 | 2 | PRO-04 | Similarly, if the insertion occurs _after_, the result will be: | OrderNo | LineNo | SortSequenceNo | ProductNo | | ------- | ------ | -------------- | --------- | | 2024 | 1 | 1 | PRO-01 | | 2024 | 2 | 4 | PRO-02 | | 2024 | 3 | 3 | PRO-03 | | 2024 | 4 | 2 | PRO-04 | This is because the insertion point is determined each time a new record is inserted (and not just once for all records in a create operation). Update operations /businessnxtapi/schema/mutations/updates page API documentation detailing update operations using GraphQL. Highlights use of filter/value pairs and filters/values pairs, with examples. 2025-04-15T09:48:42+02:00 # Update operations API documentation detailing update operations using GraphQL. Highlights use of filter/value pairs and filters/values pairs, with examples. Update operations are available through a field having the name of the form `_update` of a type having the name of the form `__Result`. For instance, for the `Associate` table, the field is called `associate_update` and its type is `Mutation_UseCompany_Associate_Result`. The result type is the same for inserts, updates, and deletes. The form of this operation is the following: ```graphql associate_update( filter: FilterExpression_Associate value: Associate_Input filters: [FilterExpression_Associate] values: [Associate_Input!] ): Mutation_UseCompany_Associate_Result ``` The `_update` field has two pairs arguments, that you must use together. The first pair is: - An argument called `filter` which defines the selection filter for the records to be updated. The type of this argument is the same used for filters in queries and will be described in details later in the document. - An argument `value` which is an object of type `_Input`. For instance, for the `Associate` table, the input type is `Associate_Input`. This is the same input type used for insert operations for tables with a single primary key column. For tables with more than one primary key columns, it's a type slightly different than the one used for inserting, none of the primary key columns being available for updating. You can use this if you want to update all records matching the filter with the same values. If you want to update each (or different) record with a different value, you must use the second pair of arguments: - An argument called `filters` which is a list of filters, one for each record to be updated. The type of the elements of `filters` is the same used as the type of `filter. - An argument called `values` which is a list of objects of type `_Input`. > [!NOTE] > > The `filter` \ `value` pair is considered deprecated and will be removed in the future. You should use the `filters` \ `values` pair instead. An update operation has the following form (in this example, we set the `languageNo` field to 44 for all associates that have the `associateNo` field greater than 546): ```graphql { title = "Query" } mutation update_lang($cid : Int!) { useCompany(no:$cid) { associate_update( filter : {associateNo : {_gt : 546}}, value:{languageNo:44}) { affectedRows items { associateNo customerNo name shortName languageNo } } } } ``` ```graphql { title = "Result" } { "data": { "useCompany": { "associate_update": { "affectedRows": 2, "items": [ { "associateNo": 547, "customerNo": 30101, "name": "Erik Larson", "shortName": "Erik", "languageNo": 44 }, { "associateNo": 548, "customerNo": 30102, "name": "Frida Olson", "shortName": "Frida", "languageNo": 44 } ] } } } } ``` You can collect multiple edits in a single update operation. You can do that with the use of `filters` and `values`. The following examples shows how to update multiple lines of an order with the different values. One filter and one value are provided for each line: ```graphql mutation multi_order_line_update($cid : Int!, $ono : Int!) { useCompany(no : $cid) { orderLine_update( filters: [ # filter for line 1 {_and:[ {orderNo : {_eq : $ono}} {lineNo : {_eq : 1}}]}, # filter for line 2 {_and:[ {orderNo : {_eq : $ono}} {lineNo : {_eq : 2}}]} ] values: [ # value for line 1 { priceInCurrency : 199.99, invoiceNow : 1.0 }, # value for line 2 { priceInCurrency : 59.99, invoiceNow : 1.0 }, ]) { affectedRows items { orderNo lineNo quantity priceInCurrency invoiceNow } } } } ``` You can transform any query using `filter` and `value` into a query using `filters` and `values`. An example is the following query: ```graphql { title = "filter & value" } mutation update_lang($cid : Int!) { useCompany(no:$cid) { associate_update( filter : {associateNo : {_gt : 546}}, value:{languageNo:44}) { affectedRows items { associateNo customerNo name shortName languageNo } } } } ``` ```graphql { title = "filters & values" } mutation update_lang($cid : Int!) { useCompany(no:$cid) { associate_update( filters : [{associateNo : {_gt : 546}}], values: [{languageNo:44}] ) { affectedRows items { associateNo customerNo name shortName languageNo } } } } ``` Delete operations /businessnxtapi/schema/mutations/deletes page Delete table records using _delete(filter), with results shown in affectedRows. Works similarly to update filters. Example provided. 2025-04-15T09:48:42+02:00 # Delete operations Delete table records using _delete(filter), with results shown in affectedRows. Works similarly to update filters. Example provided. Delete operations are available through a field having the name of the form `_delete` of a type having the name of the form `__Result`. For instance, for the `Associate` table, the field is called `associate_delete` and its type is `Mutation_UseCompany_Associate_Result`. The result type is the same for inserts, updates, and deletes. The form of this operation is the following: ```graphql associate_delete(filter: FilterExpression_Associate): Mutation_UseCompany_Associate_Result ``` The `_delete` field has a single argument called `filter` which defines the selection filter for the records to be delete. This is the same filter used for update operations. A delete operation has the following form (in this example we delete all the associates that have the `associateNo` field greater than 546): ```graphql { title = "Query" } mutation delete_associate($cid : Int!) { useCompany(no: $cid) { associate_delete(filter : { associateNo : {_gt : 546}}) { affectedRows } } } ``` ```graphql { title = "Result" } { "data": { "useCompany": { "associate_delete": { "affectedRows": 2 } } } } ``` The value of the `items` field of the return type will always be `null` for a delete operation. The API does not return the value of the records that were deleted. Processings /businessnxtapi/schema/mutations/processings page Backend processings perform tasks like canceling orders or importing data via GraphQL mutations, potentially running long and requiring specific table fields like order_processings. 2025-04-15T09:48:42+02:00 # Processings Backend processings perform tasks like canceling orders or importing data via GraphQL mutations, potentially running long and requiring specific table fields like order_processings. Processings are business logic operations that are performed in the backend. Examples of processings include canceling or finishing an order, validating or updating batches, importing data into a company, or create payment suggestions. In GraphQL, these are available as mutations. > [!WARNING] > > Processings are potentially long-running operations. Depending on the nature of the processing and the volume of data it has to process (and return) may increase significantly and exceed the timeout for the HTTP request. In this case, you would get back an error status even though the process continues to run in the background and may finish successfully. Processings are associated with a table and a table can have multiple processes. For each table, a field called `_processings` is available. This is a field of the type `Processings`. For instance, for the `Order` table, the field is called `order_processings` and its type is called `OrderProcessings`. You can see this in the following image: ![Mutation schema](../processings1.png) Under this field, there is one field for each available processing. These fields have the name of the processing. For instance, the `Order` table has processings called `finish`, `cancel`, `confirm`. These are available as fields under the `order_processings` field. This is exemplified here: ![Processings schema](../processings2.png) ![Processing example](../processings3.png) Here is an example for executing a processing. The following GraphQL requests executes the finish process on an order. ```graphql { title = "Query" } mutation finish_order($cid : Int!, $orderno : Int!) { useCompany(no : $cid) { order_processings { finish( args : { finishType :0 }, filter : { orderNo : {_eq : $orderno} } ) { succeeded items { handledOrderLine { lineNo finishedNow } } } } } } ``` ```graphql { title = "Result" } { "data": { "useCompany": { "order_processings": { "finish": { "succeeded": true, "items": [ { "handledOrderLine": [ { "lineNo": 1, "finishedNow": 1 } ] } ] } } } } } ``` Each processing field has one or two arguments, as follows: - `filter`: allows for selecting the table rows that will be processed. This is the same filter used for querying data from the table. You can read more about that here: [Filtering](../../features/filtering.md). - `args`: is an optional argument present for the processings that have parameters. When present, this is an object of a type with the name having the form `Processing__Parameters`, such as in `OrderProcessing_Transfer_Parameters`. The fields of this type are different for each processing. The `args` field allows to specify arguments for the processing. This could be either: - arguments for the overall processing, which are available in the root, or - arguments for each processed row, which are provided as an array, one element for each row. It is possible to have recursive data, i.e. arrays of arrays, on multiple levels. The following example shows possible arguments for the finish order processing: ```graphql mutation run_processing($cid : Int!, $orderno : Int) { useCompany(no : $cid) { order_processings { finish( args: { finishType : 0 group : [ { key : "1" quantity : 1 }, { key : "2" quantity : 1 }, { key : "3" quantity : 2 } ] }, filter : { orderNo : { _gte : $orderno} } ) { succeeded items { handledOrderLine { lineNo finishedNow } } } } } } ``` In this example: - `finishType` is an argument for the entire processing - `group` is a node containing a collection of objects with two properties, `key` and `quantity`. Each object in this collection is used for one processed row (which are selected here with a filter). If the number of rows is greater than the provided arguments (elements of the array) the rest of the rows are processed as if no arguments were supplied. A similar structure is used for returning results. There are results: - per processing, available directly in the root of the result object. All processings have a Boolean field called `succeeded` that indicate whether the processing completed successfully or not. Additional results, are available at this level. - per row, available under the `items` field, which is an array. Each element in the array represents the result for a processed row. For the previous request of order finishing, the following is a potential result: ```json { "data": { "useCompany": { "order_processings": { "finish": { "succeeded": true, "items": [ { "handledOrderLine": [ { "lineNo": 1, "finishedNow": 1 }, { "lineNo": 2, "finishedNow": 1 } ] }, { "handledOrderLine": [ { "lineNo": 1, "finishedNow": 1 }, { "lineNo": 2, "finishedNow": 1 }, { "lineNo": 3, "finishedNow": 2 } ] }, { "handledOrderLine": [ { "lineNo": 1, "finishedNow": 1 } ] } ] } } } } } ``` You can see here that for each order that was processed, there is an object in the `items` array. The property `handledOrderLine` is also an array and contains one object for each order line. The following table shows another example of a process running on the `CompanyInformation` table that fetches access restrictions. ```graphql { title = "Mutation" } mutation get_access($cid : Int!) { useCompany(no : $cid) { companyInformation_processings { getAccessRestrictions { succeeded tableAccess { tableNo noTableRead noInsert noUpdate noDelete } functionAccess { processingAccess { processingNo noProcessingAccess } reportAccess { reportNo noReportAccess } } } } } } ``` ```graphql { title = "Result" } { "data": { "useCompany": { "companyInformation_processings": { "getAccessRestrictions": { "succeeded": true, "tableAccess": [ { "tableNo": 361, "noTableRead": 0, "noInsert": 1, "noUpdate": 1, "noDelete": 1 }, { "tableNo": 362, "noTableRead": 0, "noInsert": 1, "noUpdate": 1, "noDelete": 1 }, ... ], "functionAccess": [ { "processingAccess": [ { "processingNo": 754, "noProcessingAccess": 1 }, { "processingNo": 1045, "noProcessingAccess": 1 }, ... ], "reportAccess": [ { "reportNo": 251, "noReportAccess": 0 }, { "reportNo": 162, "noReportAccess": 0 }, ... ] } ] } } } } } ``` From this snippet, you can see that: - the processing returns information about access restrictions to tables, processings, and reports - table access information is gathered under the `tableAccess` field, which is an array of objects, each containing information about a single table - processing and report access information is available under the `processingAccess` and `reportAccess` fields, both being children of the `functionAccess` field. Also, like `tableAccess`, both `processingAccess` and `reportAccess` are arrays This example shows a pattern that defines the general structure of processing results. Notice that even though `tableAccess` and `functionAccess` are themselves arrays, they represent overall processing data, and not results per row. > [!TIP] > > If you don't know what processings are available for each table, or what each process is doing, you can get this information using the schema explorer, available in both GraphiQL and Insomnia. The images at the beginning of this page demonstrate this. Reports /businessnxtapi/schema/mutations/reports page Comprehensive guide on executing and customizing report mutations, including parameters and result structures, for generating business documents in software applications. 2025-04-15T09:48:42+02:00 # Reports Comprehensive guide on executing and customizing report mutations, including parameters and result structures, for generating business documents in software applications. Reports are business logic operations that typically result in one or more documents. Reports have many similarities with [processings](processings.md). They have the very same structure for parameters and results. They are also available as mutation requests. However, unlike processings, they return documents and attachments. > [!WARNING] > > Line, processings, they are potentially long running operations. The time to execute a report may exceed the timeout of the HTTP request. In this case, you would get back an error status even though the process continues to run in the background and may finish successfully. Reports are associated with a table and a table can have multiple reports. However, not all tables have reports. For each table that provides reports, a field called `_reports` is available. The type of this field is called `Reports`. For instance, for the `Order` table, the reports field is called `order_reports` and its type is called `OrderReports`. You can see this in the following image: ![Mutation schema](../reports1.png) Under each field of this form, there is one field for each report available for the table. For instance, for the `Order` table, there are multiple reports such as `pickList`, `packingSlips`, `orderConfirmations`, `orderPrints`, etc. These are available under the field `order_reports`. This is shown in the next image: ![Report schema](../reports2.png) The following query shows an example for executing the order confirmation report for an order. ```graphql { title = "Query" } mutation run_report($cid : Int!, $ono : Int!) { useCompany(no : $cid) { order_reports { orderConfirmations( filter : { orderNo :{_eq: $ono} } ) { succeeded documents { name content } } } } } ``` ```graphql { title = "Result" } { "data": { "useCompany": { "order_reports": { "orderConfirmations": { "succeeded": true, "documents": [ { "name": "200022.pdf", "content": "JVBERi0xLjQKJdDExrT...", } ] } } } } } ``` Reports may have multiple arguments, as shown in the following table. All these arguments are optional. | Field | Type | Description | | ----- | ---- | ----------- | | `filter` | `FilterExpression_` | Allows for selecting the table rows that will be processed. This is the same filter used for querying data from the table. You can read more about that here: [Filtering](../../features/filtering.md). | | `args` | `Reports__Parameters` | Provides various arguments for the reporting process. | | `returnDocuments` | `bool` | Indicates whether documents should be returned as part of the result. When present, their content is available as base64-encoded text. | | `splitAttachments` | `bool` | Indicates whether attachments, when available, should be split into separate PDFs. Attachment content is available as base64-encoded text. | | `approval` | `bool` | When this value is `true` (which is the default if not specified) tables in the database will be updated, if applicable for the report. E.g. for order documents (like invoices), the tables with order lines and reservations will accumulate quantities processed so far. The documents may be archived in the database, and vouchers may be produced. The `Approval` property can be set to `false` when you only preview results. | | `documentDate` | `date` | Can be used by the business logic, depending on the actual report (e.g. as the invoice date), as well as be displayed on the printed form. | | `valueDate` | `date` | Used when producing vouchers (as part of the approval processing), to determine the accounting period and VAT period, if applicable for the report. | | `formNo` | `int` | The form number for the document type/form type. The value 0 (the default) indicates the default form. | | `printDestination` | `PrintDestination` | Specify different options for the result documents. | The following options are available for printing destination: | Destination | Description | | ----------- | ----------- | | `PRINT_TO_PDF` | Produces PDF files. This is the default value. | | `PRINT_TO_DEFINED_DESTINATIONS` | Distributes the documents according to "Document delivery methods" on the order/associate, if applicable for the report. The destination can be a selection for whether to use one or more of mail, e-mail, fax, EDI, and AutoInvoice, depending on the actual order; suggested from the customer or supplier. | | `SEND_EMAIL` | Sends PDFs by e-mail. | | `ONLY_APPROVAL` | Approves the documents without printing them (i.e. writes only to the database). | The execution of the order confirmation report is shown again here, this time with all the possible arguments: ```graphql { title = "Mutation" } mutation run_report($cid : Int!, $ono : Int!) { useCompany(no : $cid) { order_reports { orderConfirmations( returnDocuments :true splitAttachments : true approval : true formNo : 0 printDestination : PRINT_TO_PDF documentDate : "2022-03-01" valueDate : "2022-03-01" filter : { orderNo :{_eq: $ono} } ) { succeeded documents { name content attachments { name content } } } } } } ``` ```graphql { title = "Result" } { "data": { "useCompany": { "order_reports": { "orderConfirmations": { "succeeded": true, "documents": [ { "name": "200022.pdf", "content": "JVBERi0xLjQKJdDExrT...", "attachments": [] } ] } } } } } ``` > [!TIP] > > If you don't know what reports are available for each table, or what each report is doing, you can get this information using the schema explorer, available in both GraphiQL and Insomnia. The images at the beginning of this page demonstrate this. Async queries /businessnxtapi/schema/async page Async queries allow for the execution of GraphQL queries and mutations asynchronously, useful for long-running operations that might exceed timeouts. 2025-04-15T09:48:42+02:00 # Async queries Async queries allow for the execution of GraphQL queries and mutations asynchronously, useful for long-running operations that might exceed timeouts. Some GraphQL requests, such as the execution of processings or reports, may take long times to execute. These times could exceed internal GraphQL timeouts or HTTP timeouts (for instance when you're executing a query from a browser-based IDE such as GraphiQL), in which case the status and the result of the execution will be lost, even though it would complete successfully. To avoid this problem, you can run any Business NXT GraphQL request asynchronously. This works as follows: - You request the excution of a query ascynhronously. The service will queue the request and immediatelly return an operation identifier. This identifier will then be used to fetch the result. - You will ask for the result of the async query using the previously returned operation identifier. If the operation is taking a long time to execute, you will have to periodically poll for the result. - In addition, you can request the list of all your asynchronous queries, which would return their operation identifier and status. > [!NOTE] > > An asynchronous query carries the request of executing a synchronous query/mutation. The system executes this query normally (in a synchronous way) but hides the details from the caller and then makes its result available on a further request upon completion. > [!TIP] > >There are several important thing to note: > > - You can only execute a maximum of 10 asynchronous query at a time and you can only start a new one 5 seconds after the previous one has been started. (_Note_: These numbers may be subject to change.) > - You cannot execute an asynchronous query within an asynchronous query. > - The result of the asynchronous query is the stringified JSON of the synchronous query. > [!WARNING] > > Aynchronous queries cannot contain join operations (`joinup_` and `joindown_` fields) or an `@export` directive. > > If you try to execute an asynchronous query containing any of these, you will not get the expected results. This is because an async query containing joins or the `@export` directive implies a sequence of requests to the backend: a first one to retrieve a set of data, and then another one after, based on the previously retrieved data. However, in the case of async queries, the first batch of requests are sent to the backend, but the response is not awaited for. Instead, the API returns an operation ID that is later used to query for the result. Only when you ask for the result of the async query, the response from the backend is read and interpreted. Therefore, the expected sequence of requests to the backend cannot be executed in the case of async queries. > [!NOTE] > > The result of an asynchronous request is stored on the server for 96 hours. During this time, you can read the result multiple times. After this period, the result is deleted and will no longer be available for reading. ## Making an asynchronous request An asynchronous execution is requested with a mutation operation using the `asyncQuery` field. ![Mutation schema](../async01.png) The `asyncQuery` field has two arguments: | Field | Type | Description | | ----- | ---- | ----------- | | `query` | `String` | Contains the GraphQL query to be executed ascynhronously (it can either be a query or a mutation). | | `args` | `Dictionary` | Contains the key-value pairs representing the arguments for the query. | ![AsyncRequest](../async02.png) The result of executing an asynchronous query contains the following fields: | Field | Type | Description | | ----- | ---- | ----------- | | `operationId` | `String` | The identifier of the requested operation. | | `status` | `TaskStatus` | An enumeration with several possible values: `ERROR`: the operation execution finished because of an error, `SUCCESS`: the request has completed successfully, and `QUEUED`: the request has been received and placed in a queue but the execution has not started. | Here is an example that requests the first five general ledger accounts in an asynchronous way: ```graphql { title = "Query" } mutation { asyncQuery(query:""" query { useCompany(no: 123456) { generalLedgerAccount(first: 5) { totalCount items { accountNo name } } } } """) { operationId status } } ``` ```graphql { title = "Result" } { "data": { "asyncQuery": { "operationId": "29bb4abe-5299-4542-b1e4-c05aca11c3ed", "status": "QUEUED" } }, "extensions": { "vbnxt-trace-id": "43c281879ec08b257563ddbb5668a12b" } } ``` Typically, you would need to pass arguments to the query that needs to be executed asynchronously. This is done using the `args` argument of the `asyncQuery` field. The next example shows the previous query modified to contain arguments for the company number and the page size: ```graphql { title = "Query" } mutation ($args: Dictionary) { asyncQuery(query:""" query read($cid : Int!, $pagesize : Int!){ useCompany(no: $cid) { generalLedgerAccount(first: $pagesize) { totalCount items { accountNo name } } } } """, args: $args) { operationId status } } ``` ```graphql { title = "Variables" } { "args" : { "cid" : 123456, "pagesize" : 5 } } ``` The returned `operationId` must be used to read the result with another request. ## Reading the result of an asynchronous request Reading the result of an asynchronous request is done with the `asyncResult` field of the [Query](queries/query.md) type. This field has a single argument, `operationId`, which is a string containing the identifier returned by an `asyncQuery` request. ![Query schema](../async03.png) ```graphql { title = "Query" } query fetch_results($oid : String!) { asyncResult(id: $oid) { operationId status error data createdAt completedAt } } ``` ```graphql { title = "Result" } { "data": { "asyncResult": { "operationId": "caa8812d-fdb2-4546-86a9-eab30e9dde20", "status": "SUCCESS", "error": null, "data": "{\"data\":{\"useCompany\":{\"generalLedgerAccount\":{\"totalCount\":342,\"items\":[{\"accountNo\":1000,\"name\":\"Forskning og utvikling\"},{\"accountNo\":1020,\"name\":\"Konsesjoner\"},{\"accountNo\":1030,\"name\":\"Patenter\"},{\"accountNo\":1040,\"name\":\"Lisenser\"},{\"accountNo\":1050,\"name\":\"Varemerker\"}]}}}}", "createdAt": "2022-09-23T12:34:07Z", "completedAt": "2022-09-23T12:34:11Z" } }, "extensions": { "vbnxt-trace-id": "ad02666f0531856aaadfc74625dc37f1" } } ``` The result is available as a string containing the JSON result of the requested query. You would deserialize this just as you'd do if the request was executed synchronously. However, data may not be available immediatelly. If the operation is long-runnning and not yet completed, the returned status would be `QUEUED`, as shown next: ```json { "data": { "asyncResult": { "operationId": "caa8812d-fdb2-4546-86a9-eab30e9dde20", "status": "QUEUED", "error": null, "data": "null", "createdAt": "2022-09-23T12:34:07Z", "completedAt": null } }, "extensions": { "vbnxt-trace-id": "eb52491bfe7268e89d61a1121c0453af" } } ``` In this case, you have to poll periodically for the result until the status changes to `SUCCESS` or `ERROR`. ## Getting the list of asynchronous request The result of the requests that executed asynchronously is available for 96 hours on the server. During this time, you can read it multiple times. If you want to know what asynchronous operations you have requested and are still available on the server, you can do so with a query using the `asyncRequests` field. What you get back is the operation identifier and the status of each operation: ```graphql { title = "Query" } query { asyncRequests { operationId status } } ``` ```graphql { title = "Result" } { "data": { "asyncRequests": [ { "operationId": "caa8812d-fdb2-4546-86a9-eab30e9dde20", "status": "SUCCESS" }, { "operationId": "dd2a0f07-bcc2-45fb-bf3f-057b0f5eb5fa", "status": "SUCCESS" }, { "operationId": "c9e979bc-2b03-4b56-91ed-738ffb17dbd4", "status": "QUEUED" } ] }, "extensions": { "vbnxt-trace-id": "c7415ed35a81b7362c602024ff88b77f" } } ``` ## Deleting an asynchronous request An async request that has been queue for execution can be deleted if it is not running (it's either queued or finished). This can be done using the `asyncQuery_delete` field in the mutation scheme. What you need to provide is the operation identifier of the request you want to delete: ```graphql { title = "Query" } mutation delete_async_query($id : String!) { asyncQuery_delete(id: $id) { operationId status } } ``` ```graphql { title = "Result" } { "data": { "asyncQuery_delete": { "operationId": "4918391E-95C2-46E3-B771-3AD99BCA78B0", "status": "DELETED" } } } ``` The possible status values are: | Status | Description | |------- | ----------- | | `DELETED` | The request job has been deleted. | | `DENIED` | The request job cannot be deleted. | | `NOT_FOUND` | The request operation identifier was not found. | | `UNKNOWN` | An unexpected error has occurred. | Date and time fields /businessnxtapi/schema/datetime page Date and time fields API documentation - describes field formats, conversion examples, and usage in GraphQL queries and mutations. 2025-04-15T09:48:42+02:00 # Date and time fields Date and time fields API documentation - describes field formats, conversion examples, and usage in GraphQL queries and mutations. ## Basic types The Business NXT data model has featured fields that represent either dates or time values. These are stored in the database as integer values with some specific formats, as described in the following table: | Type | Format | Examples | | ---- | ------ | -------- | | date | `yyyymmdd` | `20190101` for 1 January 2019 | | time | `hhmm` | `0` for `00:00`, `101` for `01:01`, `2330` for `23:30` | | precision time | `(((h * 60 + m) * 60) + s) * 1000 + ms` | `0` for `00:00:00.000`, `3661001` for `01:01:01.001`, `2862000500` for `23:30:30.500` | Since October 2024, all tables contain two new timestamp fields for created and changed date-time values. These are T-SQL `datetime2` values (which in GraphQL are represented as `DateTime` values). | Type | Format | Examples | | ---- | ------ | -------- | | timestamp | `yyyy-MM-dd HH:mm:ss[.nnnnnnn]` | `2024-10-01 12:45:33.798` for 1 October 2024, 12:45:33.798 | > [!TIP] > > These fields, `createdTimestamp` and `changedTimestamp`, should be preferred for use in filters due to their higher precision. > > However, these fields are not available for already existing rows, only for new or modified rows after the release date. > [!TIP] > > The date/time values represent the local time of the server. They do not indicate the timezone. Since the GraphQL API exposes the data model as it is internally defined, the date and time fields appear in the API as integers. Here is an example: ```graphql { title = "Query" } query read($cid : Int!) { useCompany(no: $cid) { order( first : 2, filter : {orderDate : {_gt : 20150101}} ) { items { orderNo orderDate createdDate createdTime changedDate changedTime } } } } ``` ```json { title = "Result" } { "data": { "useCompany": { "order": { "items": [ { "orderNo": 75, "orderDate": 20150115, "createdDate": 20200511, "createdTime": 1325, "changedDate": 20200511, "changedTime": 1325, "changeTimeInMs": 48308716 }, { "orderNo": 76, "orderDate": 20150115, "createdDate": 20200511, "createdTime": 1325, "changedDate": 20200511, "changedTime": 1325, "changeTimeInMs": 48308716 } ] } } } } ``` The use of the date and time fields require conversions as follow: For date: ``` { title = "From integer" } value = 20210122 year = value / 10000 value = value % 10000 month = value / 100 day = value % 100 ``` ``` { title = "To integer" } value = year \* 10000 + month \* 100 + day ``` For time: ``` { title = "From integer" } value = 1245 hour = value / 100 minute = value % 100 ``` ``` { title = "To integer" } value = hour \* 100 + minute ``` For precision time: ``` { title = "From integer" } value = 2862000500 ms = value % 1000 timeValue = value / 1000 hour = timeValue / 3600 timeValue = timeValue % 3600 minute = timeValue / 60 second = timeValue % 60 ``` ``` { title = "To integer" } value = (((hour \* 60 + minute) \* 60) + second) \* 1000 + ms ``` The `changeTimeInMs` field is the only precision time field. However, this is only available on a limited number of tables, listed below: - `Associate` - `AssociateReference` - `AssociateInformation` - `Appointment` - `Resource` - `Product` - `Barcode` - `PriceAndDiscountMatrix` - `Order` - `OrderLine` - `IncomingDocumentChange` On the other hand, the new timestamp fields apear as fields of the `DateTime` type, as shown in the following example: ```graphql { title = "Query" } query read_orders($cid : Int!) { useCompany(no: $cid) { order( first : 2, orderBy : {changedTimestamp : DESC}) { items { orderNo orderType customerNo changedDate changedTime changedDateTime changedTimestamp createdDate createdTime createdDateTime createdTimestamp } } } } ``` ```json { title = "Result" } { "data": { "useCompany": { "order": { "items": [ { "orderNo": 4656, "orderType": 6, "customerNo": 10002, "changedDate": 20241010, "changedTime": 1022, "changedDateTime": "2024-10-10T10:22:30.399", "changedTimestamp": "2024-10-10T10:22:30.399", "createdDate": 20241010, "createdTime": 1022, "createdDateTime": "2024-10-10T10:22:00", "createdTimestamp": "2024-10-10T10:22:29.958" }, { "orderNo": 1, "orderType": 2, "customerNo": 10002, "changedDate": 20240207, "changedTime": 813, "changedDateTime": "2024-02-07T08:13:14.764", "changedTimestamp": null, "createdDate": 20240217, "createdTime": 1325, "createdDateTime": "2024-02-17T13:25:00", "createdTimestamp": null } ] } } } } ``` ## Date fields and time fields In order to make it easier to work with date and time values, GraphQL is exposing all these fields also in an ISO date format and, respectively, a time format, according to the following table: | Type | Format | Examples | | ---- | ------ | -------- | | date | `yyyy-mm-dd` | `2019-01-01`, `2021-12-31` | | time | `hh:mm` | `00:00`, `01:01`, `23:30` | | precision time | `hh:mm:ss.ms` | `00:00:00.000`, `01:01:01.001`, `23:30:30.500` | To make this possible, every date or time field has a companion with the same name, but the suffix `AsDate` for dates and, respectively, `AsTime` for time. This is exemplified below: | Field | Type | Value | | ----- | ---- | ----- | | `orderDate` | integer | `20211022` | | `orderDateAsDate` | date | `2021-10-22` | | `estimatedTime` | integer | `1710` | | `estimatedTimeAsTime` | string | `"17:10"` | | `changeTimeInMs` | integer | `48308716` | | `changeTimeInMsAsTime` | string | `"13:25:08.716"` | **Note**: There is no natural date type to represent a time value without a date. Therefore, the type of the time fields with the `AsTime` suffix is actually string. These date and time fields are available for: - [connection types](queries/query.md) (used for reading data) - [aggregate types](queries/aggregates.md) (only for the `minimum` and `maximum` aggregate functions) - [input types](mutations/inserts.md) (used with mutations for inserting or updating records) - [filter types](../features/filtering.md) (used for filtering records) Several examples are provided below. Example: reading data from the system. ```graphql { title = "Query" } query read($cid : Int!) { useCompany(no: $cid) { generalLedgerAccount(first: 2) { items { accountNo name changedDate changedDateAsDate changedTime changedTimeAsTime } } } } ``` ```graphql { title = "Result" } { "data": { "useCompany": { "generalLedgerAccount": { "items": [ { "accountNo": 1000, "name": "Forskning og utvikling", "changedDate": 20210218, "changedDateAsDate": "2021-02-18", "changedTime": 1710, "changedTimeAsTime": "17:10" }, { "accountNo": 1020, "name": "Konsesjoner", "changedDate": 20210219, "changedDateAsDate": "2021-02-19", "changedTime": 1020, "changedTimeAsTime": "10:20" } ] } } } } ``` Example: filtering data. ```graphql { title = "Query" } query read($cid : Int!) { useCompany(no: $cid) { order( first : 2, filter : { orderDateAsDate : {_gt : "2015-01-01"} } ) { totalCount items { orderNo orderDate orderDateAsDate } } } } ``` ```graphql { title = "Result" } { "data": { "useCompany": { "order": { "totalCount": 268, "items": [ { "orderNo": 75, "orderDate": 20150115, "orderDateAsDate": "2015-01-15" }, { "orderNo": 76, "orderDate": 20150116, "orderDateAsDate": "2015-01-16" } ] } } } } ``` Example: input values in insert and update mutations. ```graphql { title = "Query" } mutation create($cid : Int!) { useCompany(no: $cid) { order_create(values :[{ orderNo : 999, orderDateAsDate : "2021-10-25" }]) { affectedRows items { orderNo orderDate orderDateAsDate } } } } ``` ```graphql { title = "Result" } { "data": { "useCompany": { "order_create": { "affectedRows": 1, "items": [ { "orderNo": 999, "orderDate": 20211025, "orderDateAsDate": "2021-10-25" } ] } } } } ``` Example: computing aggregates `minimum` and `maximum`. ```graphql { title = "Query" } query read($cid : Int!) { useCompany(no: $cid) { order_aggregate { minimum { orderDate orderDateAsDate changedTime changedTimeAsTime } maximum { orderDate orderDateAsDate changedTime changedTimeAsTime } } } } ``` ```graphql { title = "Result" } { "data": { "useCompany": { "order_aggregate": { "minimum": { "orderDate": 20130105, "orderDateAsDate": "2013-01-05", "changedTime": 901, "changedTimeAsTime": "09:01" }, "maximum": { "orderDate": 20151205, "orderDateAsDate": "2015-12-05", "changedTime": 1945, "changedTimeAsTime": "15:45" } } } } } ``` ## Datetime special fields Every Business NXT table contains the following fields: | Field | Type | Description | | ----- | ---- | ----------- | | `createdTimestamp` | datetime | The time point when the record was created (e.g. `2021-10-12 13:24:54.165`). | | `createdDate` | integer | The date when the record was created (e.g. `20211012`). | | `createdTime` | integer | The time when the record was created (e.g. `1324`). | | `createdUser` | integer | The ID of the user that created the record. | | `changedTimestamp` | datetime | The time point when the record was changed (e.g. `2021-10-12 09:02:13.843`). | | `changedDate` | integer | The date when the record was last changed (e.g. `20211023`). | | `changedTime` | integer | The time when the recod was last changed (e.g. `902`). | | `changedUser` | integer | The ID of the user that made the last change to the record. | > [!TIP] > > For precise values, prefer to use the `createdTimestamp` and `changedTimestamp` fields. These fields are available for all tables and have a higher precision than the date and time fields. > > If these fields are `null` then you need to use `createdDate`/`createdTime` and `changedDate`/`changedTime`, respectively. The pair `createdDate`/`createdTime` represents the point in time when the record was created. Similarly, the pair `changedDate`/`changedTime` represent the point in time when the record was last changed. As previously mentioned, some tables have another field called `changeTimeInMs` that includes seconds and miliseconds to the time. These are important in different contexts, such as fetching data created or changed after a particular moment in time. In order to simplify the use of these date-time values, the API makes these two pairs available through a compound field, as described in the following table: | Field | Type | Examples | | ----- | ---- | ----------- | | `createdDateTime` | datetime | `2021-10-21T13:20:00` | | `changedDateTime` | datetime | `2021-10-22T14:59:00` or `2021-10-22T14:59:22.456` (where `changeTimeInMs` is available) | These values do not indicate the timezone (as previously mentioned). They represent the local time of the server. A companion set of fields suffixed with `Utc` are available, which represent the same date-time values, but in the Coordinated Universal Time (UTC) timezone. | Field | Type | Examples | | ----- | ---- | ----------- | | `createdDateTimeUtc` | datetime | `2021-10-21T13:20:00Z` | | `changedDateTimeUtc` | datetime | `2021-10-22T14:59:00Z` or `2021-10-22T14:59:22.456Z` (where `changeTimeInMs` is available) | In the following example, the `changedDateTime` is used to select all the general ledger accounts that have been modified since `2021-10-01 17:00:00`. ```graphql { title = "Query" } query read($cid : Int!) { useCompany(no: $cid) { generalLedgerAccount(filter : { changedDateTime : { _gte: "2021-10-01T17:00:00"} }) { totalCount items { accountNo name createdDateTime createdDateTimeUtc changedDateTime changedDateTimeUtc } } } } ``` ```graphql { title = "Result" } { "data": { "useCompany": { "generalLedgerAccount": { "totalCount": 2, "items": [ { "accountNo": 9001, "name": "Special ACC", "createdDateTime": "2021-10-21T13:20:00", "createdDateTimeUtc": "2021-10-21T13:20:00Z", "changedDateTime": "2021-10-21T13:21:00", "changedDateTimeUtc": "2021-10-21T13:21:00Z" }, { "accountNo": 9002, "name": "Extra ACC", "createdDateTime": "2021-10-21T13:20:00", "createdDateTimeUtc": "2021-10-21T13:20:00Z", "changedDateTime": "2021-10-21T13:21:00", "changedDateTimeUtc": "2021-10-21T13:21:00Z" } ] } } } } ``` The use of the `changedDateTime` or `createdDateTime` field is equivalent to the use of an expression built with the `changedDate`/`changedTime` or `changedDate`/`changeTimeInMs` fields (when available) or, respectively, the `createdDate`/`createdTime` fields. Alternatively, the `changedDateAsDate`/`changedTimeAsTime` and the `createdDateAsDate`/`createdTimeAsTime` fields can be used. Expression: ``` changedDateTime OP 2021-10-21T13:20:00 ``` Equivalent to: ``` (changedDate OP 20211021) OR (changedDate == 20211021 AND changedTime OP 1320) or (changedDateAsDate OP "2021-10-21") OR (changedDateAsDate == "2021-10-21" AND changedTimeAsTime OP "13:20") ``` Expression (where `changeTimeInMs` exists): ``` changedDateTime OP 2021-10-21T13:20:10.500 ``` Equivalent to: ``` (changedDate OP 20211021) OR (changedDate == 20211021 AND changeTimeInMs OP 48010500) or (changedDateAsDate OP "2021-10-21") OR (changedDateAsDate == "2021-10-21" AND changedTimeInMsAsTime OP "13:20:10.500") ``` Where `OP` is `_gt`, `_gte`, `_lt`, `lte`. An example for the filter expression `changedDateTime >= "2021-10-01T07:00:00"` is provided below: ```graphql { title = "Query" } query read($cid : Int!) { useCompany(no: $cid) { generalLedgerAccount(filter : { changedDateTime : { _gte : "2021-10-01T07:00:00"}, }) { totalCount items { accountNo name createdDateTime changedDateTime } } } } ``` ```graphql { title = "Equivalent" } query read($cid : Int!) { useCompany(no: $cid) { generalLedgerAccount(filter : { _or : [ {changedDate : {_gt : 20211001}}, { _and : [ {changedDate : {_eq : 20211001}}, {changedTime : {_gte : 700}} ] } ] }) { totalCount items { accountNo name createdDateTime changedDateTime } } } } ``` To filter with a date time value that includes second and milliseconds the query and its equivalent are as follows (example for `changedDateTime >= "2023-05-16T07:17:28.659"`): ```graphql { title = "Query" } query read($cid : Int!) { useCompany(no: $cid) { order( filter : { changedDateTime : { _gte : "2023-05-16T07:17:28.659"} } ) { totalCount items { orderNo orderDate changedDateTime } } } } ``` ```graphql { title = "Equivalent" } query read($cid : Int!) { useCompany(no: $cid) { order(filter : { _or : [ {changedDate : {_gt : 20230516}}, { _and : [ {changedDate : {_eq : 20230516}}, {changeTimeInMs : {_gte : 26248659}} ] } ] }) { totalCount items { orderNo orderDate changedDateTime } } } } ``` The datetime format has the ISO8601 compliant form `yyyy-mm-ddTHH:mm:ss.ms`, such as `2021-10-01T07:00:00` and `2023-05-16T07:17:28.659` in the examples above. In the case of filter objects, the `changedDateTime` and `createdDateTime` fields have a companion field called `changedDateTimeTZ` and `createdDateTimeTZ`, respectively. The values for thse two fields is expected to be in a specific timezone. Therefore, it will be converted to the local time before being compared with date and time values in the database. ``` query read($cid : Int!) { useCompany(no: $cid) { order( filter : { changedDateTimeTZ : { _gte : "2023-05-16T09:17:28.659+02:00"} } ) { totalCount items { orderNo orderDate changedDateTimeUtc } } } } ``` The Business NXT time fields to not store seconds, only hours and minutes. For the tables that have the `changeTimeInMs` field available, seconds and milliseconds are also available. However, for the majority of tables, this field is not present. Regardless the case, when you use the `createdDateTime` and `changedDateTime` fields and specify a date-time value, you must also supply a value for seconds. Failure to do so, such as in the example `2021-10-01T07:00` will result in the following GraphQL error: ```json { title = "Equivalent" } { "errors": [ { "message": "Argument 'filter' has invalid value. In field 'changedDateTime': [In field '_gte': [Expected type 'DateTime', found \"2021-10-01T07:00\".]]", "locations": [ { "line": 5, "column": 26 } ], "extensions": { "code": "ARGUMENTS_OF_CORRECT_TYPE", "codes": [ "ARGUMENTS_OF_CORRECT_TYPE" ], "number": "5.6.1" } } ] } ``` Bitflags /businessnxtapi/schema/bitflags page Bitflags allow managing preferences and statuses using bit flags in integer columns. GraphQL queries and mutations simplify interacting with these fields. 2025-04-15T09:48:42+02:00 # Bitflags Bitflags allow managing preferences and statuses using bit flags in integer columns. GraphQL queries and mutations simplify interacting with these fields. Some table columns store various preferences, statuses, or other kind of data that is defined using bit flags. These columns have the type `Int`. Working with them directly, however, requires good knowledge of the corresponding bitflags (both their value and their meaning). To ease this scenarios, all such columns have a corresponding columns with the same name but suffixed with `Flags`, which is defined as an array of an enumeration. An example of such a column is `OrderPreferences` from the `Order` table. It can store a combination of the following flags: | Name | Value | Description | | ---- | ----- | ----------- | | CreditNote | 2 | Credit note | | BatchInvoice | 4 | Batch invoice | | JustifyExchangeRates | 8 | Justify exchange rate | | ExemptFromInvoiceFee | 16 | Exempt from invoice fee | | GrossOrder | 256 | Gross order | | ReserveOnSave | 512 | Reserve on save | | AcceptChangesManually | 1024 | Accept changes manually | | ReservationWithinLeadTime | 2048 | Reserve within lead time | | ProduceCid | 4096 | Produce CID code | | PickWholeOrder | 8192 | Pick complete order | | InvoiceNoFromLabel | 16384 | Invoice no. from 'Label'. | | DeductFromClientBankAccount | 32768 | Withdrawal from client bank account | | PostToClientBankAccount | 65536 | Post to client bank account | | UseClientResponsibility2 | 131072 | Use client responsibility 2 | | FreeOfInterest | 262144 | Ref. Entry Free of interest | | Prepayment | 524288 | Prepayment | | FreeOfReminderFee | 1048576 | Ref. Entry Free of reminder fee | | DontCopyFromRemittanceSupplier | 2097152 | Do not copy from payment supplier | | UseClientResponsibility3 | 4194304 | Use client responsibility 3 | | ExcludeFromReduplication | 8388608 | Exclude from reduplication | | DontMoveConfirmedDeliveryDateEarlier | 16777216 | Do not move confirmed delivery date to earlier | | UseOriginalExchangeRateOnCreditNotes | 33554432 | Use original exchange rate on credit notes | In the GraphQL schema, the following two fields are available, for the `OrderPreferences` column: ![OrderPreferences](../bitflags1.png) The field `orderPreferences` is an integer, while the field `orderPreferencesFlags` is an array of the enumeration type `OrderPreferencesDomain`, shown below: ![OrderPreferences](../bitflags2.png) We can query these fields as follows: ```graphql { title = "Query" } query read_order_preferences($cid : Int!) { useCompany(no: $cid) { order { items { orderPreferences orderPreferencesFlags } } } } ``` ```graphql { title = "Result" } { "data": { "useCompany": { "order": { "items": [ { "orderPreferences": 16640, "orderPreferencesFlags": [ "GrossOrder", "InvoiceNoFromLabel" ] }, { "orderPreferences": 0, "orderPreferencesFlags": [] }, ... ] } } } } ``` The bitflags can be used when inserting new records or when updating an existing record. This is shown in the next examples: ```graphql { title = "Query" } mutation insert_order($cid: Int) { useCompany(no: $cid) { order_create(values: [ { orderPreferencesFlags : [ GrossOrder, PickWholeOrder, FreeOfReminderFee ] } ]) { affectedRows items { orderNo orderPreferences orderPreferencesFlags } } } } ``` ```graphql { title = "Result" } { "data": { "useCompany": { "order_create": { "affectedRows": 1, "items": [ { "orderNo": 4498, "orderPreferences": 1057024, "orderPreferencesFlags": [ "GrossOrder", "PickWholeOrder", "FreeOfReminderFee" ] } ] } } } } ``` ```graphql { title = "Update" } mutation update_order($cid: Int, $ono : Int!) { useCompany(no: $cid) { order_update( filter : {orderNo : {_eq : $ono}}, value: { orderPreferencesFlags : [ GrossOrder, InvoiceNoFromLabel ] }) { affectedRows items { orderNo orderPreferences orderPreferencesFlags } } } } ``` ```graphql { title = "Result" } { "data": { "useCompany": { "order_update": { "affectedRows": 1, "items": [ { "orderNo": 4498, "orderPreferences": 16640, "orderPreferencesFlags": [ "GrossOrder", "InvoiceNoFromLabel" ] } ] } } } } ``` To reset (erase) all the bitflags of a column, you can choose between: - Using the integer column and set the value to `0` (e.g. `orderPreferences : 0`). - Using the flags column and set the value to an empty array, i.e. `[]` (e.g. `orderPreferencesFlags : []`). This second option is exemplified next: ```graphql { title = "Update" } mutation update_order($cid: Int, $ono : Int!) { useCompany(no: $cid) { order_update( filter : {orderNo : {_eq : $ono}}, value: { orderPreferencesFlags : [] }) { affectedRows items { orderNo orderPreferences orderPreferencesFlags } } } } ``` ```graphql { title = "Result" } { "data": { "useCompany": { "order_update": { "affectedRows": 1, "items": [ { "orderNo": 4498, "orderPreferences": 0, "orderPreferencesFlags": [] } ] } } } } ``` The bitflag fields can also be used in filters, with one of the following operators: | Operator | Description | | -------- | ----------- | | `_is_on` | The specified flag is active (present among the enabled flags). | | `_is_off` | The specified flag is not active (not present among the enabled flags). | | `_eq` | The column value is set to exactly the specified flag (the specified flag is the only one that is active). | | `_not_eq` | The column valus is not set to exactly the specified flag. | An example is used in the following snippet: ```graphql { title = "Query" } query read_order($cid: Int) { useCompany(no: $cid) { order(filter : { orderPreferencesFlags : {_is_on : GrossOrder}}) { totalCount items { orderNo orderPreferences orderPreferencesFlags } } } ``` ```graphql { title = "Result" } { "data": { "useCompany": { "order": { "totalCount": 10, "items": [ { "orderNo": 132, "orderPreferences": 16640, "orderPreferencesFlags": [ "GrossOrder", "PostToClientBankAccount" ] }, ... ] } } } } ``` Flags can be provided as a variable as shown in the following examples: ```graphql { title = "Update" } mutation update_order($cid: Int, $ono : Int!, $opf : [OrderPreferencesDomain]) { useCompany(no: $cid) { order_update( filter : {orderNo : {_eq : $ono}}, value: { orderPreferencesFlags : $opf }) { affectedRows items { orderNo orderPreferences orderPreferencesFlags } } } } ``` ```graphql { title = "Variables" } { "cid" : 123456789, "ono" : 42, "opf": [ "GrossOrder", "PickWholeOrder", "FreeOfReminderFee" ] } ``` ```graphql { title = "Update" } mutation update_order($cid: Int, $ono : Int!, $ord : Order_Input!) { useCompany(no: $cid) { order_update( filter : {orderNo : {_eq : $ono}}, value: $ord) { affectedRows items { orderNo orderPreferences orderPreferencesFlags } } } } ``` ```graphql { title = "Variables" } { "cid" : 12345678, "ono" : 42, "ord": { "orderPreferencesFlags" : [ "GrossOrder", "PickWholeOrder", "FreeOfReminderFee" ] } } ``` Enum fields /businessnxtapi/schema/enumdomains page Some columns, arguments, or result values are enumeration values. GraphQL defines fields suffixed with AsEnum to handle them using native enumeration values. 2025-04-15T09:48:42+02:00 # Enum fields Some columns, arguments, or result values are enumeration values. GraphQL defines fields suffixed with AsEnum to handle them using native enumeration values. Columns, as well as arguments and result values for processings and reports, have an associated domain for their value. This can be integer, decimal, string, or binary for example. However, there are cases when the domain is an enumeration type. Examples for this include the `orderType` for an order, or the `finishType` argument for the order finish processing. In GraphQL, for every such column, parameter, or result whose value is an enumeration type, there are two fields available: - a field with the same name as the column, parameter, or result, which returns the value as the underlying integral type (e.g. `orderType`, `finishType`) - a field with the same name as the column, parameter, or result, but suffixed with `AsEnum`, which returns the value as an enumeration type (e.g. `orderTypeAsEnum`, `finishTypeAsEnum`) The `AsEnum` fields enable you to define query without having to know what are the exact numerical values for a field and what they actually mean, since their meaning is defined by the name of enumeration values. For instance, the `orgUnit1Processing` to `orgUnit12Processing` fields have the following possible values: | Name | Value | | ---- | ----- | | Blocked | 0 | | InUse | 1 | | Stop | 2 | | Mandatory | 3 | These fields can be used as follows: ```graphql { title = "Query" } mutation create_gla($cid : Int!, $no : Int!, $name : String!) { useCompany(no: $cid) { generalLedgerAccount_create(values:[ { accountNo : $no, name: "demo", orgUnit1ProcessingAsEnum : Mandatory orgUnit2ProcessingAsEnum : InUse } ]) { affectedRows items { accountNo name shortName orgUnit1ProcessingAsEnum orgUnit2ProcessingAsEnum orgUnit3ProcessingAsEnum orgUnit1Processing orgUnit2Processing orgUnit3Processing editStatusAsEnum } } } } ``` ```json { title = "Result" } { "data": { "useCompany": { "generalLedgerAccount_create": { "affectedRows": 1, "items": [ { "accountNo": 12345, "name": "Demo", "shortName": "", "orgUnit1ProcessingAsEnum": "Mandatory", "orgUnit2ProcessingAsEnum": "InUse", "orgUnit3ProcessingAsEnum": "Blocked", "orgUnit1Processing": 3, "orgUnit2Processing": 1, "orgUnit3Processing": 0, "editStatusAsEnum": "Inserted" } ] } } } } ``` GraphQL Features /businessnxtapi/features section Comprehensive GraphQL features - filtering, sorting, pagination, fragments, aliases, directives, error handling, unoptimized queries, batch requests. 2024-09-24T15:57:29+02:00 # GraphQL Features Comprehensive GraphQL features - filtering, sorting, pagination, fragments, aliases, directives, error handling, unoptimized queries, batch requests. Filtering /businessnxtapi/features/filtering page API filtering enables precise data retrieval using logical operators like _gt, _lt, _like, _in, and _between. It supports complex queries and comparisons. 2025-04-15T09:48:42+02:00 # Filtering API filtering enables precise data retrieval using logical operators like _gt, _lt, _like, _in, and _between. It supports complex queries and comparisons. ## Overview Filtering is a key feature of the API. You can specify the filter as a parameter to a connection. Let's look at some examples: In this first example, we fetch all the general ledger accounts that have the account number greater or equal than 6000 and lower than 7000. ```graphql { title = "Query" } query read($cid : Int!) { useCompany(no: $cid) { generalLedgerAccount( filter: {_and :[ {accountNo :{_gte : 6000}}, {accountNo :{_lt : 7000}} ]} ) { totalCount items { accountNo name } } } } ``` ```graphql { title = "Result" } { "data": { "useCompany": { "generalLedgerAccount": { "totalCount": 46, "items": [ { "accountNo": 6000, "name": "Avskr. bygn. og annen eiendom" }, { "accountNo": 6010, "name": "Avskr. maskiner, inventar mv." }, ... ] } } } } ``` We can complicate the query a bit, and ask only for those general ledger accounts in the range 6000 - 7000 that start with "Leie". In this case, the filter would look as follows: ```graphql { title = "Query" } query read($cid : Int!) { useCompany(no: $cid) { generalLedgerAccount( filter: {_and :[ {accountNo :{_gte : 6000}}, {accountNo :{_lt : 7000}}, {name :{_like:"Leie%"}} ]} ) { totalCount items { accountNo name } } } } ``` ```graphql { title = "Result" } { "data": { "useCompany": { "generalLedgerAccount": { "totalCount": 6, "items": [ { "accountNo": 6300, "name": "Leie lokaler" }, { "accountNo": 6400, "name": "Leie maskiner" }, { "accountNo": 6410, "name": "Leie inventar" }, { "accountNo": 6420, "name": "Leie datasystemer" }, { "accountNo": 6430, "name": "Leie andre kontormaskiner" }, { "accountNo": 6440, "name": "Leie transportmidler" } ] } } } } ``` In the next example, we fetch all the associates that are either customers with the number in the range 10000 - 10010, or suppliers, with the number in the range 50000 - 50010. ```graphql { title = "Query" } query read($cid : Int!) { useCompany(no: $cid) { associate ( filter:{ _or: [ { _and : [ {customerNo :{_gt:10000}}, {customerNo :{_lt:10010}} ] }, { _and : [ {supplierNo : {_gt:50000}}, {supplierNo : {_lt:50010}}, ] } ] } ) { totalCount items { name customerNo supplierNo } } } } ``` ```graphql { title = "Result" } { "data": { "useCompany": { "associate": { "totalCount": 18, "items": [ { "name": "YoYo Solutions AS", "customerNo": 10001, "supplierNo": 0 }, { "name": "Spire Business", "customerNo": 10002, "supplierNo": 0 }, ... { "name": "Scorpio Services Limited", "customerNo": 0, "supplierNo": 50001 }, { "name": "Atlas Systems", "customerNo": 0, "supplierNo": 50002 }, ... ] } } } } ``` Filtering is very similar to an open-source GraphQL implementation for Postgres databases. You can read more about that [here](https://hasura.io/docs/1.0/graphql/core/queries/query-filters.html#using-multiple-filters-in-the-same-query-and-or). This document can be used for finding more examples. The filter for each table type (connection) has its own type. For instance, the filter type for the `Associate` table is called `FilterExpression_Associate`. You can see this if you explore the schema with GraphiQL, for instance: ![Filter expression](../filter1.png) `FilterExpression_Associate` type, in turn, has field for each column in the table. This fields are of a type called *_FilterClause*, such as `Int32_FilterClause`, `LimitedString_FilterClause`, etc. Two additional fields, `_and` and `_or`, of the same `FilterExpression_Associate` allow composing complex filter with the AND and OR logical operators. ![Filter expression type](../filter2.png) The filter clause type, in turn, has fields representing operators, such as `_lt` or "lower than" or `_like` for the text comparison *like* operator. ![Filter clause type](../filter3.png) The following table lists the available operators: | Operator | Description | | -------- | ----------- | | `_eq` | equal to `value` | | `_gt` | greater than `value` | | `_gte` | greater than or equal to `value` | | `_in` | in list of values | | `_is_not_null` | is not `null` | | `_is_null` | is `null` | | `_is_off` | is `off` - available for bit field columns only | | `_is_on` | is `on` - available for bit field columns only | | `_lt` | less than `value` | | `_lte` | less than or equal to `value` | | `_like` | like `value` | | `_not_eq` | not equal `value` | | `_not_in` | not in list of values | | `_not_like` | not like `value` | | `_between` | between `from` and `to` (inclusive bounds) | | `_not_between` | not between `from` and `to` (exclusive bounds) | The filter can also be provided as an argument to the query. The filter value is an object of the filter expression type. This is shown in the following example: ```graphql { title = "Query" } query GetGLAs( $companyNo: Int, $filter : FilterExpression_GeneralLedgerAccount) { useCompany(no: $companyNo) { generalLedgerAccount(filter: $filter) { totalCount items { accountNo name } } } } ``` ```graphql { title = "Variables" } { "companyNo": 9112233, "filter": {"_and" :[ {"accountNo" :{"_gte" : 6000}}, {"accountNo" :{"_lt" : 7000}}]} } ``` ## `_in` and `_not_in` operators Most of the operators take a single value. The `_in` and `_not_in` operators, however, take an array of values, as show in the following example: ```graphql query read($cid : Int!) { useCompany(no: $cid) { associate (filter : {customerNo : {_in: [10001,10002,10003]}}) { totalCount items { name associateNo customerNo supplierNo } } } } ``` These two operators (`_in` and `_not_in`) are helpful for simplifying more complex filters. The following two tables show a filter using these two operators, and the equivalent expression without them. ```graphql { title = "With_in" } query read($cid : Int!) { useCompany(no: $cid) { associate (filter : { customerNo : {_in: [10001,10002,10003]}}) { totalCount items { name associateNo customerNo supplierNo } } } } ``` ```graphql { title = "Without_in" } query read($cid : Int!) { useCompany(no: $cid) { associate (filter : {_or: [ customerNo : {_eq: 10001}, customerNo : {_eq: 10002}, customerNo : {_eq: 10003} ]}) { totalCount items { name associateNo customerNo supplierNo } } } } ``` ```graphql { title = "With_in" } query read($cid : Int!) { useCompany(no: $cid) { associate (filter : { customerNo : {_not_in: [10001,10002,10003]}}) { totalCount items { name associateNo customerNo supplierNo } } } } ``` ```graphql { title = "Without_in" } query read($cid : Int!) { useCompany(no: $cid) { associate (filter : {_and: [ customerNo : {_not_eq: 10001}, customerNo : {_not_eq: 10002}, customerNo : {_not_eq: 10003} ]}) { totalCount items { name associateNo customerNo supplierNo } } } } ``` If the argument for the `_in` or `_not_in` operators is an empty array, the result will be an empty set. An example is shown below, where we first query the `Order` table, exporting the value of the `sellerOrBuyer` column to the variable `sellers`. We then query the `Associate` table, filtering for the employees with the numbers in the `sellers` array. Because no order matches the filter, the result is an empty `sellers` array, which results in no data being returned. ```graphql { title = "Query" } query find_selers($cid: Int!, $sellers: [Int!] = []) { useCompany(no: $cid) { order( filter: {sellerOrBuyer: {_gt: 999}}, first: 5) { items { sellerOrBuyer @export(as: "sellers") } } associate( filter: {employeeNo: {_in: $sellers}}) { items { employeeNo name } } } } ``` ```graphql { title = "Result" } { "data": { "useCompany": { "order": { "items": null }, "associate": { "items": null } } } } ``` If the `_in` or `_not_in` operators are used in an `AND` expression, and the argument is an empty array, the resulting filter will be an empty expression, and no data will be returned. ```graphql order( filter: {_and: [ {changeDateTime : {_gte: {"2024-01-24T12:00:00"}}} {orderNo: {_in: []}} ]}) { items { orderNo dueDate } } ``` This results in an empty filter. No data is returned when a filter is provided but it is empty. On the other hand, when no filter is provided, all the data is returned. If the `_in` or `_not_in` operators are used in an `OR` expression and the argument is an empty array, the subexpression is removed from the filter expression, which may still produce a valid filter that can return data. ```graphql { title = "Filter" } filter: {_or: [ {changeDateTime : {_gte: {"2024-01-24T12:00:00"}}} {orderNo: {_in: []}} ]} ``` ```graphql { title = "Equivalent" } filter: {_or: [ {changeDateTime : {_gte: {"2024-01-24T12:00:00"}}} ]} ``` ## `_between` / `_not_between` operators These two operators require two values that define the lower and upper bounds of the range. The range is inclusive, meaning that the values that are equal to the lower or upper bound are included in the result. ```graphql query read_glas($cid : Int!, $pagesize : Int!) { useCompany(no: $cid) { generalLedgerAccount( first: $pagesize, filter : {accountNo : {_between : {from: 1000, to: 2000}}}) { totalCount items { accountNo name } } } } ``` These operators are support for fields of the following types: - numeric (`Int`, `Long`, `Decimal`, etc.) - Date - Time - DateTime The following example shows an expression using both the `_between` operator (for an int field) and the `_not_between` operator (for a datetime field): ```graphql query read_glas($cid : Int!) { useCompany(no: $cid) { generalLedgerAccount(filter: { _and : [ {accountNo : { _not_between : { from : 3000, to : 5000 } }}, {changedDateTime : { _between : { from : "2024-05-01T12:00:00", to : "2024-05-30T23:59:59" } }} ]}) { totalCount items { accountNo name } } } } ``` ## Comparing with current date/time The model define various fields for date and time. All the nodes that have the type `Date_FilterClause`, `Time_FilterClause`, or `DateTime_FilterClause` can be compared with the current date/time. These GraphQL types, have, in addition for fields defining operators, an additional field called `_cmp` whose type is `ReferenceDateTime_FilterClause`, which looks as follows: ![ReferenceDateTime_FilterClause type](../filter4.png) This type features nodes for all the comparison operators available for date and time fields. However, their type is `ReferenceDateTime` that has the following fields: | Name | Type | Description | | ---- | ---- | ----------- | | `day` | `ReferenceDays` | The day this moment refers to. One of `YESTERDAY`, `TODAY`, or `TOMORROW`. | | `dayOffset` | `Int` | The number of days to offset from the current date. For instance `TODAY` with an offset of one is `TOMORROW`, and `TODAY` with an offset of -1 is `YESTERDAY`. | | `time` | `ReferenceTimePoints` | Can only have the value `NOW`. | | `minuteOffset` | `Int` | The number of minutes to offset from the current time. | The following example shows a query that fetches all orders that where changed in the last hour: ```graphql query read_orders ($cid: Int) { useCompany(no: $cid) { order(filter : { _and : [ { changedDateAsDate : { _cmp : { _eq : {day : TODAY, dayOffset : 0} } } }, { changedTimeAsTime : { _cmp : { _gte : {time : NOW, minuteOffset: -60} } } } ] }) { totalCount items { orderNo } } } } ``` This particular query can be simplified by using the `createdDateTime` field, as shown bellow: ```graphql query read_orders ($cid: Int) { useCompany(no: $cid) { order(filter : { createdDateTime : { _cmp : { _gte : { day : TODAY, time : NOW, minuteOffset : -60} } } }) { totalCount items { orderNo } } } } ``` ## Having expressions The same operators described here for filters can be used for `having` clauses in queries that include aggregations. To learn more about this, see [Grouping: The `having` argument](../schema/queries/grouping.md#the-having-argument). Sorting /businessnxtapi/features/sorting page API documentation explaining how to specify custom sorting of query results using the orderBy parameter in GraphQL queries. 2025-04-15T09:48:42+02:00 # Sorting API documentation explaining how to specify custom sorting of query results using the orderBy parameter in GraphQL queries. For every query, you can specify a sorting expression through the `orderBy` parameter on each connection. Every connection has the `orderBy` parameter, although its type differs for each connection. The name format is `OrderBy_`. Therefore, the sort order types have names such as `OrderBy_Associate` or `OrderBy_GeneralLedgerAccount`. This sort order type contains fields for all the columns of the table, except for the in-memory ones which cannot be searched or sorted. The type of all these fields is called `SortOrder` and is an enumeration with two values: `ASC` for asceding order and `DESC` for descending order. Therefore, this makes it possible to sort the results ascending or desceding by any column in the table. The following image shows a partial view of the `OrderBy_Associate` type of the `orderBy` parameter of the `UseCompany_Associate_Connection` connection. ![Sort order type](../sort1.png) Here is an example of a query for the first 10 customers from the associates table sorted descending by their name. ```graphql { title = "Query" } query read($cid : Int!) { useCompany(no: $cid) { associate(first: 10, filter:{customerNo:{_not_eq:0}}, orderBy:[{name:DESC}]) { totalCount items { associateNo customerNo name } } } } ``` ```graphql { title = "Result" } { "data": { "useCompany": { "associate": { "totalCount": 319, "items": [ { "associateNo": 286, "customerNo": 10285, "name": "Yggdrasil Ltd." }, { "associateNo": 12, "customerNo": 10011, "name": "Wilbur Andersen" }, { "associateNo": 285, "customerNo": 10284, "name": "Werner Systems AS" }, ... ] } } } } ``` As you can see, the `orderBy` is an array of values which makes it possible to define multiple sorting columns. They are considered in the give order. ```graphql { title = "Query" } query read($cid : Int!) { useCompany(no: $cid) { postalAddress( first :5, orderBy: [ {postalArea:ASC}, {postCode:ASC} ] ) { totalCount items { postCode postalArea } } } } ``` ```graphql { title = "Result" } { "data": { "useCompany": { "postalAddress": { "totalCount": 53, "items": [ { "postCode": "0001", "postalArea": "Bergen" }, { "postCode": "0010", "postalArea": "Bergen" }, { "postCode": "0015", "postalArea": "Bergen" }, { "postCode": "0018", "postalArea": "Oslo" }, { "postCode": "0021", "postalArea": "Oslo" } ] } } } } ``` The sort order can also be passed as a variable. Here is an example: ```graphql { title = "Query" } query read($cid : Int!, $size : Int!, $order : [OrderBy_PostalAddress]!) { useCompany(no: $cid) { postalAddress( first :$size, orderBy: $order) { totalCount items { postCode postalArea } } } } ``` ```graphql { title = "Variables" } { "cid": 9112233, "size": 10, "order": [ {"postalArea" : "ASC"}, {"postCode" :"ASC"} ] } ``` ## Deprecated sorting The sorting order can be specified with the `sortOrder` argument, is similar to `orderBy`, except that it is a single object instead of an array. > [!WARNING] > > The `sortOrder` argument is deprecated and will be removed in the future. Therefore, it is recommended to use the `orderBy` argument instead. When you need to specify more than one column for sorting, the only way to do it when using `sortOrder` is by using the composition field `_thenBy`. This is exemplified in the next query, where we fetch the first 10 postal addresses, sorted first by postal area, asceding, and then by postal code, also ascending. ```graphql { title = "Query" } query read($cid : Int!) { useCompany(no: $cid) { postalAddress( first :5, sortOrder:{ postalArea:ASC _thenBy : { postCode:ASC}}) { totalCount items { postCode postalArea } } } } ``` ```graphql { title = "Result" } { "data": { "useCompany": { "postalAddress": { "totalCount": 53, "items": [ { "postCode": "0001", "postalArea": "Bergen" }, { "postCode": "0010", "postalArea": "Bergen" }, { "postCode": "0015", "postalArea": "Bergen" }, { "postCode": "0018", "postalArea": "Oslo" }, { "postCode": "0021", "postalArea": "Oslo" } ] } } } } ``` Any number or fields can be chained to define the sort order in this manner. However, only one table column per level can be specified without the `_thenBy` field. Otherwise, an error message is returned and the result wil be sorted in the alphabetical order of the specified columns. ```graphql { title = "Query" } query read($cid: Int!) { useCompany(no :$cid) { postalAddress( first : 3, sortOrder: { postalArea:ASC postCode:ASC }) { totalCount items { postCode postalArea } } } } ``` ```graphql { title = "Result" } { "errors": [ { "message": "More than one field is specified in one sort clause (postCode,postalArea). The result may be incorrect. Use the _thenBy field to chain multiple table fields in the sort order." } ], "data": { "useCompany": { "postalAddress": { "totalCount": 5093, "items": [ { "postCode": "0001", "postalArea": "OSLO" }, { "postCode": "0010", "postalArea": "OSLO" }, { "postCode": "0015", "postalArea": "OSLO" } ] } } } } ``` Pagination /businessnxtapi/features/pagination page Pagination uses relay style, supporting forward and backward navigation. 2025-04-15T09:48:42+02:00 # Pagination Pagination uses relay style, supporting forward and backward navigation. ## Overview Pagination is implemented in the relay style. Reference documentation can be found [here](https://relay.dev/graphql/connections.htm). Both forward and backward pagination are supported: - the arguments for forward pagination are `first` (specifing the maximum number of items to return) and `after` (which is an opaque cursor, typically pointing to the last item in the previous page) - the arguments for backward pagination are `last` (specifying the maximum number of items to return) and `before` (which is an opaque cursor, typically pointing to the first item in the next page) - a `skip` argument is available for both forward and backward pagination and specifies a number of records to be skipped (from the given cursor) before retrieving the requested number of records. > [!NOTE] > > The values for `first`, `last`, and `skip` must be positive integers. > [!WARNING] > > Pagination does not work with grouping (`groupBy` and `having` arguments). Information about a page is returned in the `pageInfo` node. This contains the following fields: | Field | Description | | ----- | ----------- | | `hasNextPage` | Indicates whether there are more records to fetch after the current page. | | `hasPreviousPage` | Indicates whether there are more records to fetch before the current page. | | `startCursor` | An opaque cursor pointing to the first record in the current page. | | `endCursor` | An opaque cursor pointing to the last record in the current page. | When forward pagination is performed, the `hasNextPage` field is `true` if there are more records to fetch after the current page. When backward pagination is performed, the `hasNextPage` field is `true` if there are more records to fetch before the current page. Similarly, when forward pagination is performed, the `hasPreviousPage` field is `true` if there are more records to fetch before the current page. When backward pagination is performed, the `hasPreviousPage` field is always `false`. ## Understanding pagination In the following example, we fetch the first 5 associtates. This being the first page, there is no argument for the `after` parameter. Passing a `null` value for the `after` parameter is equivalent to not passing it at all. ```graphql { title = "Query" } query read($cid : Int!) { useCompany(no: $cid) { associate(first: 5) { pageInfo { hasNextPage hasPreviousPage startCursor endCursor } items { associateNo name } } } } ``` ```graphql { title = "Result" } { "data": { "useCompany": { "associate": { "pageInfo": { "hasNextPage": true, "hasPreviousPage": false, "startCursor": "MQ==", "endCursor": "NQ==" }, "items": [ { "associateNo": 1, "name": "Et Cetera Solutions" }, { "associateNo": 2, "name": "Rock And Random" }, { "associateNo": 3, "name": "Handy Help AS" }, { "associateNo": 4, "name": "EasyWay Crafting" }, { "associateNo": 5, "name": "Zen Services AS" } ] } } } } ``` For a following page, we need to take the value of the `endCursor` return from a query and supply it as the argument to `after`. This is shown in the following example, where `"NQ=="` from the query above was supplied for the `after` parameter: ```graphql { title = "Query" } query read($cid : Int!) { useCompany(no: $cid) { associate(first: 5, after: "NQ==") { pageInfo { hasNextPage hasPreviousPage startCursor endCursor } items { associateNo name } } } } ``` ```graphql { title = "Result" } { "data": { "useCompany": { "associate": { "pageInfo": { "hasNextPage": true, "hasPreviousPage": true, "startCursor": "Ng==", "endCursor": "MTA=" }, "items": [ { "associateNo": 6, "name": "Smart Vita AS" }, { "associateNo": 7, "name": "Full Force Services" }, { "associateNo": 8, "name": "HomeRun Auto AS" }, { "associateNo": 9, "name": "Trade Kraft AS" }, { "associateNo": 10, "name": "Nodic Cool Sports AS" } ] } } } } ``` Requests with backwards pagination are performed similarly, exept that `last` and `before` are used instead of `first` and `after`. In the following example, the value of `before` is taken from the value of `startCursor` returned by the previous query that returned the second page of associates (with five records per page). As a result, the data returned from this new query is actually the first page of associates. ```graphql { title = "Query" } query read($cid : Int!) { useCompany(no: $cid) { associate(last: 5, before: "Ng==") { pageInfo { hasNextPage hasPreviousPage startCursor endCursor } items { associateNo name } } } } ``` ```graphql { title = "Result" } { "data": { "useCompany": { "associate": { "pageInfo": { "hasNextPage": true, "hasPreviousPage": false, "startCursor": "MQ==", "endCursor": "NQ==" }, "items": [ { "associateNo": 1, "name": "Et Cetera Solutions" }, { "associateNo": 2, "name": "Rock And Random" }, { "associateNo": 3, "name": "Handy Help AS" }, { "associateNo": 4, "name": "EasyWay Crafting" }, { "associateNo": 5, "name": "Zen Services AS" } ] } } } } ``` Providing both forward and backward pagination arguments (`first`/`after` and `last`/`before`) is illegal and the query will fail with an error. If pagination arguments are not supplied a default page size of 5000 records is used. When you fetch data in pages (which is recommended for most scenarious) you can fetch the total number of objects in the table with disregard to the page that is fetched or the size of the page. This is possible with the `totalCount`. This will return the number of records that match the filter (if any) but ignoring paginagion (if any). This feature is exemplified below. We, again, fetch the first 5 associates that have a customer number greater or equal than 11000 but also request the total number of records that match this filter. ```graphql { title = "Query" } query read($cid : Int!) { useCompany(no: $cid) { associate( first: 5, filter: {customerNo :{_gte: 11000}}) { totalCount pageInfo { hasNextPage hasPreviousPage startCursor endCursor } items { associateNo customerNo name } } } } ``` ```graphql { title = "Result" } { "data": { "useCompany": { "associate": { "totalCount": 29, "pageInfo": { "hasNextPage": true, "hasPreviousPage": false, "startCursor": "MQ==", "endCursor": "NQ==" }, "items": [ { "associateNo": 308, "customerNo": 11000, "name": "Auto Glory AS" }, { "associateNo": 309, "customerNo": 11001, "name": "Home Team Business" }, { "associateNo": 310, "customerNo": 11003, "name": "Corpus Systems" }, { "associateNo": 311, "customerNo": 11004, "name": "Dash Credit AS" }, { "associateNo": 312, "customerNo": 11007, "name": "Smart Help Services AS" } ] } } } } ``` You can skip a number of records before feetching a page (with the size indicated by either the `first` or the `last` arguments) using the argument `skip`. This is an optional argument. When present, it indicates the number of records to be skipped. Here is an example: ```graphql { title = "Forward" } query read($cid : Int!) { useCompany(no: $cid) { generalLedgerAccount(skip : 5, first: 10) { totalCount items { accountNo } } } } ``` ```graphql { title = "Backward" } query read($cid : Int!) { useCompany(no: $cid) { generalLedgerAccount(skip : 5, last : 10, before: "MTE=") { totalCount items { accountNo } } } } ``` > [!WARNING] > > Because the execution of queries for joined tables is optimized for performance, pagination does not work as described in this document. To understand the problem and the workaround see [Unoptimized queries](./unoptimized.md). ## Pagination in depth To understand how pagination works, we will consider the following example, where the general ledger account table contains 25 records. It could look like this: ``` page 1 (10 records) page 2 (10 records) page 3 (5 records) |---------------------------------|---------------------------------|---------------------------------| | 1000 | 1020 | ... | 1100 | 1120 | 1130 | 1140 | ... | 1240 | 1250 | 1260 | 1270 | 1280 | 1300 | 1310 | ``` ### Forward pagination To fetch the records from the beginning, we perform a query like this: ```graphql { title = "Query" } query read($cid : Int!) { useCompany(no: $cid) { generalLedgerAccount(first : 10, after: null) { pageInfo { hasNextPage hasPreviousPage startCursor endCursor } items { accountNo } } } } ``` ```graphql { title = "Result" } { "data": { "useCompany": { "generalLedgerAccount": { "pageInfo": { "hasNextPage": true, "hasPreviousPage": false, "startCursor": "MQ==", "endCursor": "MTA=" }, "items": [ { "accountNo": 1000 }, { "accountNo": 1020 }, ... { "accountNo": 1100 }, { "accountNo": 1120 } ] } } } } ``` This returns the first ten records, from index 1 to 10, and the `startCursor` and `endCursor` are pointing to the first and last elements in the set. ``` page 1 (10 records) |---------------------------------| | 1000 | 1020 | ... | 1100 | 1120 | 1130 | 1140 | ... | 1240 | 1250 | 1260 | 1270 | 1280 | 1300 | 1310 | ^ ^ | | startCursor endCursor ``` To fetch the next page of 10 records, we provide the value of `endCursor` as the argument for `after`: ```graphql { title = "Query" } query read($cid : Int!) { useCompany(no: $cid) { generalLedgerAccount(first : 10, after: "MTA=") { pageInfo { hasNextPage hasPreviousPage startCursor endCursor } items { accountNo } } } } ``` ```graphql { title = "Result" } { "data": { "useCompany": { "generalLedgerAccount": { "pageInfo": { "hasNextPage": true, "hasPreviousPage": true, "startCursor": "MTE=", "endCursor": "MjA=" }, "items": [ { "accountNo": 1130 }, { "accountNo": 1140 }, ... { "accountNo": 1240 }, { "accountNo": 1250 } ] } } } } ``` This returns the second page of ten records, from index 11 to 20, and the `startCursor` and `endCursor` are pointing to the first and last elements in the set. ``` after page 2 (10 records) | |---------------------------------| v | 1000 | 1020 | ... | 1100 | 1120 | 1130 | 1140 | ... | 1240 | 1250 | 1260 | 1270 | 1280 | 1300 | 1310 | ^ ^ | | startCursor endCursor ``` To fetch the next page of 10 records, we repeat the operation, again, using the `endCursor` for the value of the `after` argument: ```graphql { title = "Query" } query read($cid : Int!) { useCompany(no: $cid) { generalLedgerAccount(first : 10, after: "MjA=") { pageInfo { hasNextPage hasPreviousPage startCursor endCursor } items { accountNo } } } } ``` ```graphql { title = "Result" } { "data": { "useCompany": { "generalLedgerAccount": { "pageInfo": { "hasNextPage": false, "hasPreviousPage": true, "startCursor": "MjE=", "endCursor": "MjU=" }, "items": [ { "accountNo": 1260 }, { "accountNo": 1270 }, { "accountNo": 1280 }, { "accountNo": 1300 }, { "accountNo": 1310 }, ] } } } } ``` This returns a page of only 5 records, from index 21 to 25, and the `startCursor` and `endCursor` are pointing to the first and last elements in the set. ``` after page 3 (5 records) | |----------------------------------| v | 1000 | 1020 | ... | 1100 | 1120 | 1130 | 1140 | ... | 1240 | 1250 | 1260 | 1270 | 1280 | 1300 | 1310 | ^ ^ | | startCursor endCursor ``` The value of the `hasNextPage` field is `false` because there are no more records to fetch. > [!NOTE] > > The value used for the `after` argument should be the value of the `endCursor` from the previous page (unless it's `null`, in which case it means the fetching should start from the beginning). The `after` cursor identifies the position of the last record in a (previous) page. The record at the position represented by the cursor passed to the `after` argument is not included in the result set. A page starts with the record following the one identified by `after`. It is also possible to skip records before fetching a page. This is done by providing the `skip` argument. The following example skips the first 3 records before fetching a page of 10 records. We don't start from the beginning, but at the end of the first page of 10 records, as exemplify below: ``` after skip 3 records page of 10 records | >-------------------<|-----------------------------------------------| v | 1000 | 1020 | ... | 1100 | 1120 | 1130 | 1140 | 1150 | 1160 | ... | 1240 | 1250 | 1260 | 1270 | 1280 | 1300 | 1310 | ^ ^ | | startCursor endCursor ``` ```graphql { title = "Query" } query read($cid : Int!) { useCompany(no: $cid) { generalLedgerAccount(first : 10, skip : 3, after: "MTA=") { pageInfo { hasNextPage hasPreviousPage startCursor endCursor } items { accountNo } } } } ``` ```graphql { title = "Result" } { "data": { "useCompany": { "generalLedgerAccount": { "pageInfo": { "hasNextPage": true, "hasPreviousPage": true, "startCursor": "MTQ=", "endCursor": "MjM=" }, "items": [ { "accountNo": 1160 }, { "accountNo": 1200 }, ... { "accountNo": 1270 }, { "accountNo": 1280 } ] } } } } ``` ### Backward pagination Backward pagination works similarly, except that the `last` and `before` arguments are used instead of `first` and `after`. The argument `last` indicates how many records the page should contain. The argument `before` represents the position of the record before which the page is located. The record at the `before` position is not included in the result set. Therefore, if in the preceding example we fetched all the records in the table, and now want to move backwards, but use the value of the `endCursor` as the arguement for `before`, then this last record will not be included in the returned page. ``` page of 10 records before |---------------------------------------------------------------------| | v | 1000 | 1020 | ... | 1160 | 1200 | 1210 | 1220 | 1230 | 1240 | 1250 | 1260 | 1270 | 1280 | 1300 | 1310 | ^ ^ | | startCursor endCursor ``` ```graphql { title = "Query" } query read($cid : Int!) { useCompany(no: $cid) { generalLedgerAccount(last : 10, before: "MjU=") { pageInfo { hasNextPage hasPreviousPage startCursor endCursor } items { accountNo } } } } ``` ```graphql { title = "Result" } { "data": { "useCompany": { "generalLedgerAccount": { "pageInfo": { "hasNextPage": true, "hasPreviousPage": false, "startCursor": "MTU=", "endCursor": "MjQ=" }, "items": [ { "accountNo": 1200 }, { "accountNo": 1210 }, ... { "accountNo": 1280 }, { "accountNo": 1300 } ] } } } } ``` Fragments /businessnxtapi/features/fragments page Documentation on using and nesting fragments in GraphQL queries to efficiently construct reusable sets of fields. 2025-04-15T09:48:42+02:00 # Fragments Documentation on using and nesting fragments in GraphQL queries to efficiently construct reusable sets of fields. You can build fragments to construct sets of fields and then include them in queries where you need to. Nested fragments are supported. Here is an example of a query constructed using a fragment called `associateBasic`, which in turn uses two other fragments, `associateContact` and `associateAddress`. ```graphql { title = "Query" } query read($cid : Int!) { useCompany(no: $cid) { associate { items { ...associateBasic customerNo supplierNo } } } } fragment associateBasic on Associate { name userName ... associateContact ... associateAddress } fragment associateContact on Associate { emailAddress phone } fragment associateAddress on Associate { addressLine1 postCode postalArea countryNo } ``` ```graphql { title = "Result" } { "data": { "useCompany": { "associate": { "items": [ { "name": "ABB Installasjon AS", "userName": "", "emailAddress": "", "phone": "12345678", "addressLine1": "Ole Deviks Vei 10", "postCode": "1375", "postalArea": "Billingstad", "countryNo": 0, "customerNo": 10000, "supplierNo": 0 }, { "name": "ABB Kraft AS", "userName": "", "emailAddress": "", "phone": "12345678", "addressLine1": "Jacob Borchs Gate 6", "postCode": "3002", "postalArea": "Drammen", "countryNo": 0, "customerNo": 10001, "supplierNo": 0 } ] } } } } ``` Named queries and parameters /businessnxtapi/features/parameters page GraphQL supports named queries and variables, enhancing query clarity. Example includes defining and utilizing variables for efficient data retrieval. 2024-09-24T15:57:29+02:00 # Named queries and parameters GraphQL supports named queries and variables, enhancing query clarity. Example includes defining and utilizing variables for efficient data retrieval. GraphQL supports variables and naming queries, which should always be used since they make the query more clear while reading. Let's look at an example. In the following snippet, `GetCustomers` is a named query that returns a page associates. This query has three parameters: `$companyNo` specifies the Visma.net company number that uniquely identifies the company within the system, `$pageSize` the maximum number of records to be returned, and `$after` the cursor that indicates the position after which the records are to be fetched. If nothing is specified, this means the first `$pageSize` records are to be fetched. ```graphql query GetCustomers($companyNo: Int, $pageSize: Int, $after :String) { useCompany(no: $companyNo) { associate(first: $pageSize, after: $after) { totalCount pageInfo { hasNextPage hasPreviousPage startCursor endCursor } items { associateNo customerNo name } } } } ``` If you use GraphiQL, you can define the variables in the query variable pane in the lower left of the IDE, as shown in the following image: ![GraphiQL Query Variables](../graphiqlvars.png) When you perform the request to GraphQL programmatically, or from a tool that does not directly support GraphQL variables, you must put the request containing both the query and variables in the body, as `application/json`. ```json { "query" : "query GetCustomers($companyNo: Int, $pageSize: Int, $after :String)\n{\n useCompany(no: $companyNo)\n {\n associate(first: $pageSize, after: $after)\n {\n totalCount\n pageInfo\n {\n hasNextPage\n hasPreviousPage\n startCursor\n endCursor\n }\n items\n {\n associateNo\n customerNo\n name\n }\n }\n }\n}", "variables":"{\"companyNo\": 9112233,\"pageSize\": 5}" } ``` Details about the raw form of a GraphQL query were presented in an earlier section of this tutorial. Aliases /businessnxtapi/features/aliases page GraphQL API documentation on using aliases to rename fields and query the same field multiple times with different arguments. 2025-04-15T09:48:42+02:00 # Aliases GraphQL API documentation on using aliases to rename fields and query the same field multiple times with different arguments. GraphQL allows you to rename the result of a field to anything you want. This is possible with the help of [aliases](https://graphql.org/learn/queries/#aliases). You can use this feature to: - rename long fields and use name that you prefer over fields from the schema - query for the same field multiple times but with different arguments Here is an example: ```graphql query GetPaymentLines($cid: Int!) { useCompany(no: $cid) { paymentLine(first: 1) { items { paymentNo lineNo currency : joinup_Currency { isoCode } supplier : joinup_Associate_via_Supplier { bankAccount country : joinup_Country { isoCode } } } } } } ``` Aliases can be used in: - queries (example shown above) - inserts and updates - aggregates Here is another example in an aggregate function: ```graphql query GetAgregates($cid: Int!) { useCompany(no: $cid) { order_aggregate { totalvat : sum { vatAmountDomestic } } } } ``` As previously mentioned, an important use case is to query for the same field multiple times but using different arguments. An example is shown below: ```graphql { title = "Query" } query ($cid : Int!) { useCompany(no : $cid) { some : generalLedgerAccount(first : 2) { items { accountNo name } } more : generalLedgerAccount(first : 2, filter : {accountNo : {_gt : 6000}}) { items { accountNo name } } } } ``` ```graphql { title = "Result" } { "data": { "useCompany": { "some": { "items": [ { "accountNo": 1000, "name": "Forskning og utvikling" }, { "accountNo": 1020, "name": "Konsesjoner" } ] }, "more": { "items": [ { "accountNo": 6010, "name": "Avskr. maskiner, inventar mv." }, { "accountNo": 6020, "name": "Avskr. immaterielle eiendeler" } ] } } }, "extensions": { "vbnxt-trace-id": "02c029a3a07c7a..." } } ``` Directives /businessnxtapi/features/directives page GraphQL directives modify query execution, including core directives like @include and @skip, and custom ones like @export. 2025-04-15T09:48:42+02:00 # Directives GraphQL directives modify query execution, including core directives like @include and @skip, and custom ones like @export. Directives are a GraphQL feature that affect the execution of a query in any way the server desires. Directives can be attached to different parts of the schema (field, fragment inclusion, etc.). There are several core GraphQL directives: | Directive | Attached to | Description | | --------- | ----------- | ----------- | | `@include` | field, fragment inclusion | Only include this field in the result if the argument is true. | | `@skip` | field, fragment inclusion | Skip this field if the argument is true. | In addition, we provide custom directives: | Directive | Attached to | Description | | --------- | ----------- | ----------- | | `@export` | field, fragment inclusion | Export the value of a field into a variable that can be used somewhere else in the query. | | `@dependsOn` | field, fragment inclusion | Specify that a field depends on another field. | ## The `@include` directive Includes a field or fragment in the result only if the Boolean argument is `true`. **Syntax**: ```graphql @include(if: Boolean!) ``` **Example**: ```graphql query($cid : Int!, $pagesize : Int, $withdetails : Boolean!) { useCompany(no : $cid) { order(first : $pagesize) { totalCount items { orderNo orderDate lines : joindown_OrderLine_via_Order(first: 2) @include(if: $withdetails) { totalCount items { lineNo transactionDate } } } } } } ``` **Result**: ```graphql { title = "$withdetails is false" } { "data": { "useCompany": { "order": { "totalCount": 451, "items": [ { "orderNo": 1, "orderDate": 20210212 }, { "orderNo": 2, "orderDate": 20130203 } ] } } } } ``` ```graphql { title = "$withdetails is true" } { "data": { "useCompany": { "order": { "totalCount": 451, "items": [ { "orderNo": 1, "orderDate": 20210212, "lines": { "totalCount": 6, "items": [ { "lineNo": 1, "transactionDate": 0 }, { "lineNo": 2, "transactionDate": 0 } ] } }, { "orderNo": 2, "orderDate": 20130203, "lines": { "totalCount": 5, "items": [ { "lineNo": 1, "transactionDate": 20140904 }, { "lineNo": 2, "transactionDate": 20140904 } ] } } ] } } } } ``` ## The `@skip` directive Skips a field if the Boolean argument is `true`. **Syntax**: ```graphql @skip(if: Boolean!) ``` **Example**: ```graphql query($cid : Int!, $pagesize : Int, $nodetails : Boolean!) { useCompany(no : $cid) { order(first : $pagesize) { totalCount items { orderNo orderDate lines : joindown_OrderLine_via_Order(first: 2) @skip(if: $nodetails) { totalCount items { lineNo transactionDate } } } } } } ``` **Result**: ```graphql { title = "$nodetails is true" } { "data": { "useCompany": { "order": { "totalCount": 451, "items": [ { "orderNo": 1, "orderDate": 20210212 }, { "orderNo": 2, "orderDate": 20130203 } ] } } } } ``` ```graphql { title = "$nodetails is false" } { "data": { "useCompany": { "order": { "totalCount": 451, "items": [ { "orderNo": 1, "orderDate": 20210212, "lines": { "totalCount": 6, "items": [ { "lineNo": 1, "transactionDate": 0 }, { "lineNo": 2, "transactionDate": 0 } ] } }, { "orderNo": 2, "orderDate": 20130203, "lines": { "totalCount": 5, "items": [ { "lineNo": 1, "transactionDate": 20140904 }, { "lineNo": 2, "transactionDate": 20140904 } ] } } ] } } } } ``` ## The `@export` directive The `@export` directive in GraphQL exports the value of a field into a variable that is used somewhere else in the query. This can be either a single value or an array. **Syntax**: ```graphql @export(as: "variablename", distinct : true) ``` **Example**: Fetch the cutomer number of the associate whose indentifier is specified and then use the customer number to fetch orders. ```graphql { title = "Query" } query($cid : Int!, $ano : Int!, $pagesize: Int, $customerId : Int = 0) { useCompany(no: $cid) { associate(filter : {associateNo : {_eq: $ano}}) { items { customerNo @export(as: "customerId") } } order(first : $pagesize, filter : {customerNo : {_eq : $customerId}}) { totalCount items { orderNo orderDate customerNo } } } } ``` ```graphql { title = "Result" } { "data": { "useCompany": { "associate": { "items": [ { "customerNo": 10000 } ] }, "order": { "totalCount": 14, "items": [ { "orderNo": 81, "orderDate": 20150115, "customerNo": 10000 } ] } } } } ``` **Example**: Add one order and two order lines for the order with a single request. ```graphql { title = "Query" } mutation ($cid: Int, $cno : Int, $pid1 : String, $pid2 : String, $orderId: Int = 0) { useCompany(no: $cid) { order_create( values: [ { orderDate: 20221104, customerNo: $cno, orderType: 1, transactionType: 1 } ] ) { affectedRows items { orderNo @export(as: "orderId") } } orderLine_create( values: [ { orderNo: $orderId, productNo: $pid1, quantity: 1 }, { orderNo: $orderId, productNo: $pid2, quantity: 2 } ] ) { affectedRows items { lineNo orderNo productNo } } } } ``` ```graphql { title = "Result" } { "data": { "useCompany": { "order_create": { "affectedRows": 1, "items": [ { "orderNo": 632 } ] }, "orderLine_create": { "affectedRows": 2, "items": [ { "lineNo": 1, "orderNo": 632, "productNo": "1001" }, { "lineNo": 2, "orderNo": 632, "productNo": "1002" } ] } } } } ``` There are several things to keep in mind when using this directive: - The variable into which the field value is exported must be defined in the query parameter list. Otherwise, when you use the variable later on in another part of the query, the server will complain that it is not defined. - You can only export the value of one field into one variable. If you attempt to write values from multiple fields into the same variable they will be overwritten based of the order of evaluation. - If you export multiple values into the same variable, the last field that is evaluated will define the value of the variable. - If the variable is defined as an array, you can store multiple values. In the previous examples, we have used a variable that could store a single value. Therefore, if a query returned multiple elements, a field would get evaluated multiple times and each time the variable would be overritten. The value from the last evaluation is the one that is finally stored in the variable. However, all the values can be preserved in an array. The only change is that you need to define the variable of an array type. Moreover, you can use the Boolean optional argument `distinct` to retain only the distict values and discard duplicates. An array variable can be used for instance with the `_in` and `_not_in` filters. The following example shows a query that fetches information about all the orders that have lines that were updated after a given moment in time: ```graphql query read_modified_orders($cid : Int!, $dt : DateTime, $ono : [Int] = []) { useCompany(no : $cid) { orderLine(filter : {changedDateTime : {_gt : $dt}}) { items { orderNo @export(as : "ono", distict : true) } } order(filter : {orderNo : {_in : $ono}) { items { orderNo orderDate customerNo } } } } ``` ## The `@dependsOn` directive The `@dependsOn` directive in GraphQL specifies that a field depends on another field. This directive can be used in the rare situations when fields are executed out of order due. Typically, fields are executed in the order they are defined in the query. However, in order to optimize the query execution, some requests are packed together before being sent to the back-end. This changes the order of execution as some fiels defined later in the query are executed before fields defined earlier. Moreoever, some fields depend on others because they use a variable set from an earlier field with the use of the `@export` directive. Use the `@dependsOn` directive to ensure that a field is executed only after a previous field was executed. **Syntax**: ```graphql @dependsOn(field: "name") ``` **Example**: ```graphql mutation CreateBatchWithAttachment ($cid: Int, $batchId: Int = 0, $fina: String, $fiby: String, $tok: String) { useCompany(no: $cid) { # create the batch batch_create( values: { voucherSeriesNo: 1, valueDate: 20250121 description: "Demo batch" } ) { affectedRows items { batchNo @export(as: "batchId") } } # create the voucher voucher_create( values: { batchNo: $batchId voucherDate: null voucherType: 21 voucherNo: null debitAccountNo: 5000 creditAccountNo: 1920 amountDomestic: 500.00 text: "first voucherline" } ) { affectedRows items { batchNo voucherNo } # add a document to the batch voucher_processings { addNewDocument( filter: {batchNo: {_eq: $batchId}}, args: { fileName: $fina, fileBytes: $fiby } ) { succeeded } } # upload the document to the file service incomingAccountingDocumentAttachment_processings { uploadToFileService( filter: {fileName: {_eq: $fina}}, args: {connectToken: $tok} ) @dependsOn(field: "addNewDocument") { succeeded } } } } ``` ## References You can learn more about the core GraphQL directives from these articles: - [Directives](https://graphql-dotnet.github.io/docs/getting-started/directives/) - [GraphQL Directives](https://spec.graphql.org/October2021/#sec-Type-System.Directives) Error handling /businessnxtapi/features/errors page GraphQL API error handling - status codes, error messages, syntax errors, multiple request outcomes, execution tracing for troubleshooting, and detailed error property explanations. 2025-04-15T09:48:42+02:00 # Error handling GraphQL API error handling - status codes, error messages, syntax errors, multiple request outcomes, execution tracing for troubleshooting, and detailed error property explanations. A GraphQL query is an HTTP POST request with the content type `application/json` and the body having the form: ```json { "query" : "...", "variables" : "..." } ``` If the authorization for a request fails (no authorization header, expired token, etc.) the return status code is `401` (`Unauthorized)` and the response body is the following: ```html 401 Authorization Required

401 Authorization Required


nginx
``` However, for most requests, whether they are successful or they failed, the return status code is `200`. When a successful query is executed, the JSON object that is returned contains a property called `data` whose value is an object representing the returned data. Here is an example: ```json { "data": { "useCompany": { "generalLedgerAccount": { "items": [ { "accountNo": 9001, "name": "Special account", } ] } } } } ``` When an error occurs during the execution of the request, the return JSON object contains both the `data` property, as well as a property called `errors`, which is an array of objects containing information about the error(s) that occurred. The following is an example: ```json { "errors": [ { "message": "Error: Could not get authorize user using VismaNetCompanyId: 1234567. Description: External integration error. Status: 18." } ], "data": { "useCompany": { "generalLedgerAccount": null } } } ``` If the GraphQL query has a syntax error, the returned information contains not just a message but also detains about the location of the error within the query. This is exemplified below: ```graphql { title = "Query" } query read($cid : Int!) { useCompany(no : $cid) { generalLedgerAccount { totalRows } } } ``` ```graphql { title = "Result" } { "errors": [ { "message": "GraphQL.Validation.Errors.FieldsOnCorrectTypeError: Cannot query field 'totalRows' on type 'Query_UseCompany_GeneralLedgerAccount_Connection'. Did you mean 'totalCount'?", "locations": [ { "line": 4, "column": 7 } ], "extensions": { "code": "FIELDS_ON_CORRECT_TYPE", "codes": [ "FIELDS_ON_CORRECT_TYPE" ], "number": "5.3.1" } } ] } ``` A GraphQL query may contain multiple requests (such as reading from different tables). It is possible that some may be successful, while other will fail. The result will contain data that was fetched successfully but also information about the errors that occurred for the other requests. Here is an example: ```graphql { title = "Query" } query read($cid : Int!) { useCompany(no : $cid) { generalLedgerAccount(first: 2) { totalCount items { accountNo name } } generalLedgerBalance(last: 10) { totalCount items { accountNo sumDebitObDomestic sumCreditObDomestic } } } } ``` ```graphql { title = "Result" } { "errors": [ { "message": "The cursor 'before' must have a value.", "path": [ "useCompany", "generalLedgerBalance" ] } ], "data": { "useCompany": { "generalLedgerAccount": { "totalCount": 340, "items": [ { "accountNo": 1000, "name": "Forskning og utvikling" }, { "accountNo": 1020, "name": "Konsesjoner" } ] }, "generalLedgerBalance": null } } } ``` On the other hand, an operation may be successful (such as inserting a new row) although sub-operations (such as assigning a value to a column) may fail. GraphQL returns the result as well as all the errors messages from executing the request. ```graphql { title = "Query" } mutation create_batch($cid : Int!) { useCompany(no: $cid) { batch_create(values: [ { valueDate: 20211122 voucherSeriesNo: 3 orgUnit1 : 0 orgUnit2 : 0 orgUnit3 : 0 orgUnit4 : 0 orgUnit5 : 0 period :11 year :2021 } ]) { affectedRows items { batchNo valueDate voucherSeriesNo } } } } ``` ```graphql { title = "Result" } { "errors": [ { "message": "Error: Illegal value date 11/22/2021. Check suspension date and the accounting periods and VAT periods tables..", "path": [ "useCompany", "batch_create", "values/0" ], "extensions": { "data": { "status": 0 } } }, { "message": "Error: Org unit class not named. Description: Not read access to destination column. Column: OrgUnit3.", "path": [ "useCompany", "batch_create", "values/0" ], "extensions": { "data": { "status": 3, "status_name" : "NotReadAccessToDestinationColumn" } } }, { "message": "Error: Org unit class not named. Description: Not read access to destination column. Column: OrgUnit4.", "path": [ "useCompany", "batch_create", "values/0" ], "extensions": { "data": { "status": 3, "status_name" : "NotReadAccessToDestinationColumn" } } }, { "message": "Error: Org unit class not named. Description: Not read access to destination column. Column: OrgUnit5.", "path": [ "useCompany", "batch_create", "values/0" ], "extensions": { "data": { "status": 3, "status_name" : "NotReadAccessToDestinationColumn" } } } ], "data": { "useCompany": { "batch_create": { "affectedRows": 1, "items": [ { "batchNo": 26, "valueDate": 20211122, "voucherSeriesNo": 3 } ] } } } } ``` ## Understanding error information When an error occurs during the execution of the query, you may see the following information for each returned error: - `message`: always present, contains a description of the error - `path`: contains the path in the query (schema) of the field that produced the error - `extensions`: additional information about the error. An example is `data:status` that contains an internal error code that could be useful for troubleshouting a failed execution. In addition, `data:status_name` provides a symbolic name of the status code, such as `NotReadAccessToDestinationColumn`. Another example of an error message from attempting to create an order with a duplicate ID is shown below. You can see that `status` is 3 but `status_name` is set to `PrimaryKeyAssignmentFailed` to give you a better understanding of what the status code 3 means in this context. ```json { "errors": [ { "message": "A record with the same primary key already exists.", "path": [ "useCompany", "order_create", "values/0" ], "extensions": { "data": { "status": 3, "status_name": "PrimaryKeyAssignmentFailed" } } } ], "data": { "useCompany": { "order_create": { "items": null } } } } ``` However, it is important to note that these status codes (and their names) are not unique. A request is composed of multiple operations, such as selecting a table, assigning a value to a field, etc. There are various status codes for each such contextual operation. Therefore, an operation may return status code 3 that means `NotReadAccessToDestinationColumn`, or status code 3 that means `NotInsertAccessToTable`. That is why the `status_name` field is useful to help you better understand the problem. > [!TIP] > > For more information about the GraphQL engine errors (such as schema errors, input errors and processing errors) see [GraphQL.NET Error Handling](https://graphql-dotnet.github.io/docs/getting-started/errors/). ## Tracing query execution Each GraphQL query that executes is assigned a unique identifier. This is used to trace the execution of the query and can be used for identifying problems with the execution. If you need to contact the Business NXT support for help to investigate a problem, you need to provide this unique identifier. You can find this unique ID in the response of a GraphQL request. Although this was skipped in the examples shown in this tutorial (for simplicity), each response has a field called `extensions` that contains an object named `vbnxt-trace-id`. ```graphql { title = "Query" } query read($cid : Int, $pagesize : Int) { useCompany(no: $cid) { generalLedgerAccount(first: $pagesize) { totalCount items { accountNo name } } } } ``` ```graphql { title = "Result" } { "data": { "useCompany": { "generalLedgerAccount": { "totalCount": 340, "items": [ { "accountNo": 1000, "name": "Forskning og utvikling" }, { "accountNo": 1020, "name": "Konsesjoner" }, ... ] } } }, "extensions": { "vbnxt-trace-id": "00000000000000000196b4ea383242fa" } } ``` Use the value of the `vbnxt-trace-id` when contacting the Business NXT support. > [!TIP] > > The same trace identifier can be retrieved from the response headers. GraphQL responses have a custom `x-vbc-traceid` header containing the value of the trace identifier.
Unoptomized queries /businessnxtapi/features/unoptimized page Learn about the limitations and handling of optimized queries for joined tables, including performance trade-offs and the usage of the unoptimized Boolean argument for accurate pagination and counting. 2025-04-15T09:48:42+02:00 # Unoptomized queries Learn about the limitations and handling of optimized queries for joined tables, including performance trade-offs and the usage of the unoptimized Boolean argument for accurate pagination and counting. Because of the way fetching data from the backend is optimized, several features do not work for joined tables (the fields that start with the `joindown_` or `joinup_` prefix): - pagination (requesting data in pages and diplaying paging information - the `pageInfo` field) - counting the total number of rows that match the filter (the `totalCount` field) In order to have these features working properly, you must explicitly request that the execution of the query is not optimized by the system. This is done with a Boolean field argument called `unoptimized`. The way this works is described below. First, let's understand the problem. ## Query optimization When you request data from joined tables we are faced with a problem called the N+1 problem. Let's say you want to fetch the last 10 orders of a customer, but along with the order heads also the order lines info. Typically, this means we will do one request to fetch the order heads first, and then, for each order, we perform one request to fetch the lines. For 10 orders that is 10 more request for the lines, amounting to a total of 11 requests. For N orders, that amounts to N+1 requests. This incurs a performace loss and this is aggravated if more tables are joined together. For instance, fetching the orders, and for each order the order lines, and for each order line the order notes, just to give an example. To avoid the performance penalty, our system is optimizing the queries. As a result, we are performing only 2 requests instead of N+1. For the orders and orders line example, we perform a first request to fetch the heads and then a second request to fetch the lines. The result is a potential 10x or more speed-up for executing a query. The downside is the features mentioned above no longer work. ## The problem with the optimization The understand the problem with optimization let us discuss the following scenario. Consider a query that fetches the first 10 orders and for each order the first 3 order lines. That means that for each order, the query should return a maximum of 3 order lines, therefore, potentially, a total of 30 order lines in total. However, some orders may have less than 3 order lines others may have more. The current implementation takes the first 30 order lines (that match the given filter, if any) regardless to which order they belong. Let's explain this with an example: | Order no | No. of order lines | No. of expected returned lines | No. of actual returned lines | | -------- | ----------- | ----------------------- | --------------------- | | 1 | 2 lines | 2 lines | 2 lines | | 2 | 5 lines | 3 lines | 5 lines | | 3 | 2 line | 2 lines | 2 lines | | 4 | 1 lines | 1 lines | 1 lines | | 5 | 7 lines | 3 lines | 7 lines | | 6 | 3 lines | 3 lines | 3 lines | | 7 | 5 lines | 3 lines | 5 lines | | 8 | 8 lines | 3 lines | 5 lines | | 9 | 5 lines | 3 lines | 0 lines | | 10 | 2 lines | 2 lines | 0 lines | The third column in this table shows the expected result when you ask for the first 3 order lines for each order. What is returned instead is the data of the fourth column. This is shown with the following query: ```graphql { title = "Query" } query ($cid :Int!, $pagesize : Int) { useCompany(no: $cid) { order(first: $pagesize) { totalCount items { orderNo joindown_OrderLine_via_Order(first: 3) { items { lineNo } } } } } } ``` ```graphql { title = "Result" } { "data": { "useCompany": { "order": { "totalCount": 348, "items": [ { "orderNo": 1, "joindown_OrderLine_via_Order": { "items": [ { "lineNo": 1 }, { "lineNo": 2 } ] } }, { "orderNo": 2, "joindown_OrderLine_via_Order": { "items": [ { "lineNo": 1 }, { "lineNo": 2 }, { "lineNo": 3 }, { "lineNo": 4 }, { "lineNo": 5 } ] } }, { "orderNo": 3, "joindown_OrderLine_via_Order": { "items": [ { "lineNo": 1 }, { "lineNo": 2 } ] } }, { "orderNo": 4, "joindown_OrderLine_via_Order": { "items": [ { "lineNo": 1 } ] } }, { "orderNo": 5, "joindown_OrderLine_via_Order": { "items": [ { "lineNo": 1 }, { "lineNo": 2 }, { "lineNo": 3 }, { "lineNo": 4 }, { "lineNo": 5 }, { "lineNo": 6 }, { "lineNo": 7 } ] } }, { "orderNo": 6, "joindown_OrderLine_via_Order": { "items": [ { "lineNo": 1 }, { "lineNo": 2 }, { "lineNo": 3 } ] } }, { "orderNo": 7, "joindown_OrderLine_via_Order": { "items": [ { "lineNo": 1 }, { "lineNo": 2 }, { "lineNo": 3 }, { "lineNo": 4 }, { "lineNo": 5 } ] } }, { "orderNo": 8, "joindown_OrderLine_via_Order": { "items": [ { "lineNo": 1 }, { "lineNo": 2 }, { "lineNo": 3 }, { "lineNo": 4 }, { "lineNo": 5 } ] } }, { "orderNo": 9, "joindown_OrderLine_via_Order": { "items": null } }, { "orderNo": 10, "joindown_OrderLine_via_Order": { "items": null } } ] } } } } ``` The reason for this is that we fetch a total of 10\*3 order lines from the order lines table, in a single request. This means the result is not what was actually requested from with the query. Notice this is not a problem when no pagination is requested. The following example, that fetches some orders and all their order lines works without any problem. ```graphql { title = "Query" } query ($cid :Int!, $pagesize : Int) { useCompany(no: $cid) { order(first: $pagesize) { totalCount items { orderNo joindown_OrderLine_via_Order { items { lineNo } } } } } } ``` ```graphql { title = "Result" } { "data": { "useCompany": { "order": { "totalCount": 348, "items": [ { "orderNo": 1, "joindown_OrderLine_via_Order": { "items": [ { "lineNo": 1 }, { "lineNo": 2 } ] } }, { "orderNo": 2, "joindown_OrderLine_via_Order": { "items": [ { "lineNo": 1 }, { "lineNo": 2 }, { "lineNo": 3 }, { "lineNo": 4 }, { "lineNo": 5 } ] } }, { "orderNo": 3, "joindown_OrderLine_via_Order": { "items": [ { "lineNo": 1 }, { "lineNo": 2 } ] } }, { "orderNo": 4, "joindown_OrderLine_via_Order": { "items": [ { "lineNo": 1 } ] } }, { "orderNo": 5, "joindown_OrderLine_via_Order": { "items": [ { "lineNo": 1 }, { "lineNo": 2 }, { "lineNo": 3 }, { "lineNo": 4 }, { "lineNo": 5 }, { "lineNo": 6 }, { "lineNo": 7 } ] } }, { "orderNo": 6, "joindown_OrderLine_via_Order": { "items": [ { "lineNo": 1 }, { "lineNo": 2 }, { "lineNo": 3 } ] } }, { "orderNo": 7, "joindown_OrderLine_via_Order": { "items": [ { "lineNo": 1 }, { "lineNo": 2 }, { "lineNo": 3 }, { "lineNo": 4 }, { "lineNo": 5 } ] } }, { "orderNo": 8, "joindown_OrderLine_via_Order": { "items": [ { "lineNo": 1 }, { "lineNo": 2 }, { "lineNo": 3 }, { "lineNo": 4 }, { "lineNo": 5 }, { "lineNo": 6 }, { "lineNo": 7 }, { "lineNo": 8 } ] } }, { "orderNo": 9, "joindown_OrderLine_via_Order": { "items": [ { "lineNo": 1 }, { "lineNo": 2 }, { "lineNo": 3 }, { "lineNo": 4 }, { "lineNo": 5 } ] } }, { "orderNo": 10, "joindown_OrderLine_via_Order": { "items": [ { "lineNo": 1 }, { "lineNo": 2 } ] } } ] } } } } ``` However, fetching the total count of order lines per each order and displaying paging information for each chunk of order lines does not work in this case either. ## Unoptimized queries To solve the problem of the optimized queries for joined tables, you can explicitly request to run the query without optimizations. This means there will be N+1 requests to the backend and the execution time will increase significantly. However, counting records from the joined table and fetching data in pages works as expected. The unoptimized execution is requested with a Boolean argument for the field called `unoptimized` that must be set to `true`. This is shown in the following example: ```graphql { title = "Query" } query ($cid :Int!, $pagesize : Int) { useCompany(no: $cid) { order(first: $pagesize) { totalCount items { orderNo joindown_OrderLine_via_Order(first: 3, unoptimized: true) { items { lineNo } } } } } } ``` ```graphql { title = "Result" } { "data": { "useCompany": { "order": { "totalCount": 348, "items": [ { "orderNo": 1, "joindown_OrderLine_via_Order": { "items": [ { "lineNo": 1 }, { "lineNo": 2 } ] } }, { "orderNo": 2, "joindown_OrderLine_via_Order": { "items": [ { "lineNo": 1 }, { "lineNo": 2 }, { "lineNo": 3 } ] } }, { "orderNo": 3, "joindown_OrderLine_via_Order": { "items": [ { "lineNo": 1 }, { "lineNo": 2 } ] } }, { "orderNo": 4, "joindown_OrderLine_via_Order": { "items": [ { "lineNo": 1 } ] } }, { "orderNo": 5, "joindown_OrderLine_via_Order": { "items": [ { "lineNo": 1 }, { "lineNo": 2 }, { "lineNo": 3 } ] } }, { "orderNo": 6, "joindown_OrderLine_via_Order": { "items": [ { "lineNo": 1 }, { "lineNo": 2 }, { "lineNo": 3 } ] } }, { "orderNo": 7, "joindown_OrderLine_via_Order": { "items": [ { "lineNo": 1 }, { "lineNo": 2 }, { "lineNo": 3 } ] } }, { "orderNo": 8, "joindown_OrderLine_via_Order": { "items": [ { "lineNo": 1 }, { "lineNo": 2 }, { "lineNo": 3 } ] } }, { "orderNo": 9, "joindown_OrderLine_via_Order": { "items": [ { "lineNo": 1 }, { "lineNo": 2 }, { "lineNo": 3 } ] } }, { "orderNo": 10, "joindown_OrderLine_via_Order": { "items": [ { "lineNo": 1 }, { "lineNo": 2 } ] } } ] } } } } ``` The `unoptimized` flag is an argument to a connection (see [Queries](../schema/queries/query.md)). However, a connection may contain other inner connections. These are the joindown tables discussed here and seen in the previous examples. When you are requesting the fetching of data in an unoptimized manner, this applies not only to the connection on which the argument was specified, but also on all its descendants. This behavior can be overridden by using the `unoptimized` argument again on a descendant connection. Here are several examples: - the order lines are fetched in an optimized manner but not the order line notes ```graphql query ($cid :Int!, $pagesize : Int) { useCompany(no: $cid) { order(first: $pagesize) { totalCount items { orderNo joindown_OrderLine_via_Order(first: 3) { items { lineNo joindown_OrderLineNote_via_OrderLine(unoptimized : true) { items { note } } } } } } } } ``` - both the order lines and the order line notes are fetched unoptimized ```graphql query ($cid :Int!, $pagesize : Int) { useCompany(no: $cid) { order(first: $pagesize) { totalCount items { orderNo joindown_OrderLine_via_Order(first: 3, unoptimized : true) { items { lineNo joindown_OrderLineNote_via_OrderLine { items { note } } } } } } } } ``` - the order lines are fetched unoptimized but the fetching of order line notes is optimized ```graphql query ($cid :Int!, $pagesize : Int) { useCompany(no: $cid) { order(first: $pagesize) { totalCount items { orderNo joindown_OrderLine_via_Order(first: 3, unoptimized : true) { items { lineNo joindown_OrderLineNote_via_OrderLine(unoptimized : false) { items { note } } } } } } } } ``` Using the `unoptimized` flag on a top-level table (such as the orders table in these examples) has no effect on fetching data from that table. However, it will affect the manner of fetching data from all its joined tables (the fields prefixed with either `joindown_` or `joinup_`). ## Asynchronous execution Executing the queries with joined down tables fast and having pagination or counting the total items in the joined down table are mutually exclusive. However, if you do need these features but do not want to wait for the execution of the query you can execute the query asynchronously. To learn more about this, see [Async queries](../schema/async.md). Batch requests /businessnxtapi/features/batches page Batch multiple GraphQL queries or mutations in a single API request, with results returned after complete execution. 2025-04-15T09:48:42+02:00 # Batch requests Batch multiple GraphQL queries or mutations in a single API request, with results returned after complete execution. A GraphQL request is basically a JSON object that has the following form: ```json { "query" : "..." "variables" : {} "operationname" : "..." } ``` The `OperationName` property is optional, but if present, it must match the operation name from the query. To exemplify, let's consider the following request: ```graphql { title = "Query" } query read_accounts($cid : Int, $pagesize : Int){ useCompany(no: $cid) { generalLedgerAccount(first: $pagesize) { totalCount pageInfo { hasNextPage hasPreviousPage startCursor endCursor } items { accountNo name } } } } ``` ```graphql { title = "Result" } { "cid" : 987654321, "pagesize" : 10 } ``` This query is formatted as the following JSON content when dispatched to the GraphQL API: ```json { "query" : "query read_accounts($cid : Int,\n $pagesize : Int){\n useCompany(no: $cid) {\n generalLedgerAccount(first: $pagesize) {\n totalCount\n pageInfo {\n hasNextPage\n hasPreviousPage\n startCursor\n endCursor\n }\n items {\n accountNo\n name\n }\n }\n }\n}", "variables": {"cid": 987654321, "pagesize" : 10}, } ``` > [!NOTE] > > Notice that JSON does not support multiple line strings. Therefore, new lines must be escaped, and the entire query provided as a single line string. The result you get back is also a JSON object, such as the following: ```json { "data": { "useCompany": { "generalLedgerAccount": { "totalCount": 412, "pageInfo": { "hasNextPage": true, "hasPreviousPage": false, "startCursor": "MA==", "endCursor": "MTA=" }, "items": [ { "accountNo": 1200, "name": "Plant and machinery" }, { "accountNo": 1209, "name": "Depreciation plant and machinery" }, ... ] } } }, "extensions": { "vbnxt-trace-id": "a2f3f1ad045d7bd2f62f833e136ff0b0" } } ``` It is possible that a single request contains multiple parts. For instance, you can query orders, products, and customers in a single request. You can also create an order and its order lines in a single mutation. However, it's not possible to mix queries and mutations inside the same request. VBNXT GraphQL allows you to batch multiple queries inside a single API requests. That is possible by sending an array of JSON objects, as follows: ```json [ { "query" : "..." "variables" : {} "operationname" : "..." }, { "query" : "..." "variables" : {} "operationname" : "..." } ] ``` The result, is also an array of objects, one for each requested operation. ```json [ , , ... ] ``` An example with two requests (a query and a mutation) batched together is shown below: ```json [ { "query" : "query read($cid : Int, $pagesize : Int){\n useCompany(no: $cid) {\n generalLedgerAccount(first: $pagesize) {\n totalCount\n pageInfo {\n hasNextPage\n hasPreviousPage\n startCursor\n endCursor\n }\n items {\n accountNo\n name\n }\n }\n }\n}", "variables": {"cid": 987654321, "pagesize" : 10}, "operationname" : "read" }, { "query" : "mutation update_gla($cid : Int!, $no : Int!, $name : String!)\n{\n useCompany(no: $cid) \n {\n generalLedgerAccount_update(\n filter : {accountNo : {_eq: $no}},\n value : {shortName : $name})\n {\n affectedRows\n items {\n accountNo\n name\n shortName\n }\n }\n }\n}", "variables": {"cid": 987654321, "no" : 9999, "name": "test"}, "operationname" : "update_gla" } ] ``` ```json [ { "data": { "useCompany": { "generalLedgerAccount": { "totalCount": 117, "pageInfo": { "hasNextPage": true, "hasPreviousPage": false, "startCursor": "MA==", "endCursor": "MTA=" }, "items": [ { "accountNo": 1200, "name": "Plant and machinery" }, { "accountNo": 1209, "name": "Depreciation plant and machinery" }, { "accountNo": 1210, "name": "Buildings" }, { "accountNo": 1211, "name": "Land" }, { "accountNo": 1219, "name": "Depreciation, Buildings" }, { "accountNo": 1220, "name": "Office Equipment" }, { "accountNo": 1229, "name": "Depreciation, Office Equipment" }, { "accountNo": 1240, "name": "Motor vehicles" }, { "accountNo": 1249, "name": "Depreciation motor vehicles" }, { "accountNo": 1300, "name": "Goodwill" } ] } } } }, { "data": { "useCompany": { "generalLedgerAccount_update": { "affectedRows": 1, "items": [ { "accountNo": 9999, "name": "SUSPENSE ACCOUNT", "shortName": "test" } ] } } } } ] ``` It is possible to batch both queries and mutations together in any order. They are executed individually, one by one, in the order they where provided. When batching multiple request, the results will only be returned after all the requests have been executed. A large number of requests could make the overall operation exceed the HTTP timeout interval. In this case, the operation will timeout and the result will be lost. > [!NOTE] > > There is currently a limit of the number of requests that can be batched together. This limit is set to 32 requests. > > This limit may be subject to future changes. > [!TIP] > > Use batching requests judiciously, making sure their overall execution time will not trigger an operation timeout! Webhooks /businessnxtapi/webhooks section Webhooks notify changes in table rows, enabling your app to retrieve updates via GraphQL. Notifications include table ID, primary key, change type, and can be verified using a shared key. 2025-04-15T09:48:42+02:00 # Webhooks Webhooks notify changes in table rows, enabling your app to retrieve updates via GraphQL. Notifications include table ID, primary key, change type, and can be verified using a shared key. The Business NXT webhooks will notify when a change has been made in a table row. Your application will need an endpoint capable of receving a POST request from Visma Connect. The request will contain a signature that can be verified by your application using a shared key. The payload contains the following fields | Field | Data type | Explanation | | ----- | --------- | ----------- | | `tableIdentifier` | `string` | Table that has been modified, corresponds to the table name in GraphQL. | | `customerNo` | `int` | The customer number the table belongs to, can be 0 if `companyNo` is non-zero. | | `companyNo` | `int` | The company number of the company the table belongs to, can be 0 if the table is a customer level table. As general advice rely on `companyNo`first to identify the tenant, and only if it's 0 check `customerNo`. | | `event` | `string [INSERT\|UPDATE\|DELETE]` | The performed row action, three possible values `INSERT`, `UPDATE`, and `DELETE`. | | `primaryKeys` | `array [[string: int64\|string]]` | The primary key represented as an array of key value pairs between the primary key column name and its value. Can contain multiple elements if the table has a composite primary key. The column name corresponds to its name in GraphQL. Note that integral values are signed and can be 64 bit. | | `timestamp` | `string` | UTC timestamp of when the webhook was created, should only be used for `DELETE` events. It's usually stamped slightly later than the `changedTimestamp`/`createdTimestamp` found in `changedColumns` and through GraphQL queries. The `changedTimestamp` value is the perfered value. This field is no longer neccessary for `INSERT` and `UPDATE` events after the introduction of the `changedTimestamp` and `createdTimestamp` column. | | `changedByUser` | `string` | The user name of the user performing the action, the information can also be found in changedColumns for `INSERT` and `UPDATE` events. | | `changedColumns` | `array [[string: int64\|decimal\|string]]` | Array with key value pairs of all changed/inserted column names and values in `UPDATE` and `INSERT` events, similar to the primary key field. Binary columns' values are their length and not the binary content. The field is not present for `DELETE` events. | Below is an example payload for updating an order line, some elements have been omitted from `changedColumns` for brevity. ```json { title = "Update order line example" } { "tableIdentifier":"OrderLine", "customerNo":4801958, "companyNo":5199768, "event":"INSERT", "primaryKeys": [ {"OrderNo":3}, {"LineNo":1} ], "timestamp":"2025-02-06T21:15:28.3971469Z", "changedByUser":"ola.normann", "changedColumns":[ {"Description":"Hello webhook"}, {"ChangedDate":20250206}, {"ChangedTime":2215}, {"ChangedByUser":"ola.normann"}, {"ChangeTimeInMs":80128366}, {"ChangedTimestamp":20250206211528366} ] } ``` Visma Development Portal /businessnxtapi/webhooks/connect page Guide on subscribing to BNXT webhooks via the Visma Development Portal, including setup steps, retry configurations, and securing the signature key. 2024-09-24T15:57:29+02:00 # Visma Development Portal Guide on subscribing to BNXT webhooks via the Visma Development Portal, including setup steps, retry configurations, and securing the signature key. To start subscribing on BNXT webhooks go to your application in the development portal, on the left menu select webhooks. Go to my subscriptions and select "New subscription". You will enter a wizard for setting up your subscription. 1. Publisher: Select Business NXT 2. Events: Select TableChange 3. Config: Set the url of the endpoint that can process the webhooks. Before you have an endpoint ready - it can be convenient to use a free resource like webhook.site. Just copy the url from the page when you enter. * Retry policy: Exponential backoff is the default and should be selected if there can be small down times in your application. * Retry attempts: If your application does not respond we will try again this number of times. * Retry interval: We will wait a number of seconds before trying again (in addition to exponential backoff). ![Visma Development Portal](../connect-webhook-config.png) 4. Summary: A signature key will be generated. You should store this in a key-vault or in an environment variable of your application. You will need it to validate the messages from BNXT. This can be done by hashing the utf-8 message with the HMAC SHA-256 algorithm, and then encoding it with base64. The result should be equal to the signature sent by header. Business NXT configuration /businessnxtapi/webhooks/bnxt page Guide to configuring Business NXT - Create and link User, WebhookTarget, and Webhook Subscription tables. Support outlined for multiple targets and subscriptions per application. 2024-09-24T15:57:29+02:00 # Business NXT configuration Guide to configuring Business NXT - Create and link User, WebhookTarget, and Webhook Subscription tables. Support outlined for multiple targets and subscriptions per application. Create a layout that contains the table User. From the user table, join in WebhookTarget via (User name->User name). Then from WebhookTarget join in Webhook Subscription (Webhook target->Line no.). Save changes. You will need a user with the same name as the application in connect. If you set up for graphql you will probably have that already. You can add multiple targets for the application, and multiple subscriptions on each target. You will have to add one line of subscription per combination of company and table. Samples /businessnxtapi/webhooks/samples page Integration samples are available on GitHub, including contributions by Ole Melhus. Visit the links for examples and layouts. 2024-09-24T15:57:29+02:00 # Samples Integration samples are available on GitHub, including contributions by Ole Melhus. Visit the links for examples and layouts. There are some samples open for getting started with your integration. * Our own samples is available [on github](https://github.com/Visma-Business/VBC-Webhook-Samples). * [Ole Melhus](https://github.com/omelhus) contributed [this sample](https://git.new/bnxt-hooks). * [Ole Melhus](https://github.com/omelhus) contributed [this layout](https://nxt.ninja/library/lyt_AJXleB5Gvy6PyzbZM0Op). How Tos /businessnxtapi/howto section Guides and best practices for integrating with the Business NXT GraphQL API, including creating orders and managing documents. 2024-09-24T15:57:29+02:00 # How Tos Guides and best practices for integrating with the Business NXT GraphQL API, including creating orders and managing documents. We recognize that having learned GraphQL API does not mean you are able to write integrations without further help. Although tools such as GraphiQL, Postman, and Insomnia allow you to browse the schema and discover the Business NXT model and operations with it, writing code to handle your data requires further knowledge. In this section, we provide a set of recommended practices as well as a series of guides to help you perform some common business operations. - [Best practices for Business NXT API](bestpractices.md) - [How to create and finish an order](order.md) - [How to add an attachment to an order](orderattachments.md) - [How to invoice an order](invoice_order.md) - [How to create and update a batch](batch.md) - [How to add a document to a voucher](voucherdocument.md) - [How to read text from the Text table](texts.md) Best practices /businessnxtapi/howto/bestpractices page GraphQL best practices guide for naming queries, parameterizing with variables, fetching only needed data, ensuring input order, batch inserting, handling transactions, and executing long-running operations asynchronously. 2025-04-15T09:48:42+02:00 # Best practices GraphQL best practices guide for naming queries, parameterizing with variables, fetching only needed data, ensuring input order, batch inserting, handling transactions, and executing long-running operations asynchronously. This page contains a series of recommendations for creating GraphQL queries and mutations. ## Name the queries Although it's possible to create anonymous queries (and mutations) it is recommended that you give each operation a relevant name. The main benefits is that it indicates the purpose of each operation in a clear manner. > [!TIP] > > Give each query a name. ```graphql { title = "Recommended ✅" } query read_gla { useCompany (no: 123456789) { generalLedgerAccount { items { accountNo name } } } } ``` ```graphql { title = "Not recommended ❌" } query { useCompany (no: 123456789) { generalLedgerAccount { items { accountNo name } } } } ``` ## Parameterize the queries You probably have to run the same query with different input values (such as filters or inputs for create and update operations). At the least, you need to provide either a company number or a customer number or run any query. You should avoid hardcoding such input values and use variables to provide them. The main advange is that this makes a query reusable. > [!TIP]+ Best practice > > Use variables to parameterize queries. ```graphql { title = "Recommended ✅" } query read_gla($cid : Int!) { useCompany (no: $cid) { generalLedgerAccount { items { accountNo name } } } } Variables: { "cid" : 123456789 } ``` ```graphql { title = "Not recommended ❌" } query read_gla { useCompany (no: 123456789) { generalLedgerAccount { items { accountNo name } } } } ``` ## Read only the data you need A major advantage of GraphQL compared to other API standards (such as REST or SOAP) is that it solves the problem of data overfetching (fetching more data than what you need) and underfetching (fetching less data than what you need). This is because GraphQL is a data-oriented API. In GraphQL, you can fetch exactly what you need, no more, no less. Even when you fetch data from the same table, sometimes you might need more fields, sometimes less. Reading only the data you need improves the performance by reducing unnecessary operation on the server and data transfer over the network. > [!TIP]+ Best practice > > Always fetch only the data that you need for the current operation. Example: you need to display the general ledger account numbers and names. ```graphql { title = "Recommended ✅" } query read_gla($cid : Int!) { useCompany (no: $cid) { generalLedgerAccount { items { accountNo name } } } } ``` ```graphql { title = "Not recommended ❌" } query read_gla($cid: Int) { useCompany (no:$cid) { generalLedgerAccount { items { accountNo name name2 shortName accountGroup accountSubType taxCode changedDate changedTime createdDate createdTime joinup_Currency { isoCode name } } } } } ``` ## Input values have a specific order When you insert a new record or update an existing one, the order of the given input fields is important. Various business logic operations occur in the backend based on the order of these fields. Therefore, you must be aware of the order of fields for various tables and use it accordingly. Otherwise, the result of an insert or update operation will be (at least partially) incorrect. > [!TIP]+ Best practice > > When you specify inputs, do the following: > > - fields must be specified in the correct order > - if a field should have a specific value, provide it as an input (e.g. `customerNo : 42`) > - if a field should remain unassigned, do not list it in the input > - if a field should have a system given value, specify `null` for its value (e.g. `voucherNo : null`) > - if a field should have a system given value but in a given interval and you want to specify the interval, use the `_suggest_interval` suffixed fields (e.g. `customerNo_suggest_interval: {from: 10000, to: 20000}`). If you do not want to specify the interval, use the regular input field and give the value `null` (e.g. `customerNo : null`). > - do not explicitly request a suggested value for primary key fields (the system does that implicitly) ## Do not fetch total count when you don't need it When you fetch a list of records, you can also fetch the total count of records that match the given filter. Although there are cases when you need this information, there are also cases when it's not need. For instance, when you need to fetch all the records from a table, you don't really need the total count. You do a repetitive fetch of the records (page by page) until there are no more records to fetch. Fetching of the total count translates to a `SELECT Count(*) FROM table`. On large tables (with millions or tens of millions of records), this operation can be very slow, an can take up 20-30 seconds to execute. > [!TIP]+ Best practice > > Do not fetch the total count when you don't need it. Avoid fetching the total count when you fetch all the records from a table. ## Do not pass entire input objects as variables Variables are parsed and deserialized into objects on the server. This results in loosing the order of the fields as given in the input variables. However, as explained above, the order of the fields is important. The outcome is an incorrect result of the insert or update operation. > [!TIP]+ Best practice > > When you specify inputs, do not pass entire input objects as variables. ```graphql { title = "Recommended ✅" } mutation create_order($cid : Int!, $cno: Int, $ourref : String, $yourref : String) { useCompany(no : $cid) { order_create(values: [{ customerNo : $cno, orderType : 2, orderPreferences : 0, information5 : "1", information6 : null, transactionType : 1, ourReference : $ourref, yourReference : $yourref }]) { affectedRows items { orderNo } } } } { "cid":12345678, "order": { "customerNo":12345, "ourReference":"", "yourReference":"Ref Test" } } ``` ```graphql { title = "Not recommended ❌" } mutation create_order($cid : Int!, $order: Order_Input!) { useCompany(no : $cid) { order_create(values: [$order]) { affectedRows items { orderNo } } } } { "cid":12345678, "order": { "customerNo":12345, "orderType":2, "orderPreferences":0, "information5":"1", "information6":null, "transactionType":1, "ourReference":"", "yourReference":"Ref Test" } } ``` ## Batch multiple inserts into a single request If you need to insert multiple records into the same table, doing so one record at a time will incur performance penalties, the more significant the more records you have. This will in incorrect > [!TIP]+ Best practice > > Insert multiple records into a table with in a single request. ```graphql { title = "Recommended ✅" } mutation create_voucher($cid: Int, $batchId: Int!) { useCompany(no: $cid) { voucher_create(values: [ { batchNo: $batchId voucherNo : null voucherDate: null amountDomestic: 1300 creditAccountType: 2 creditAccountNo: 50000 }, { batchNo: $batchId voucherNo : null voucherDate: null amountDomestic: 600 debitAccountType: 3 debitAccountNo: 4300 }, { batchNo: $batchId voucherNo : null voucherDate: null amountDomestic: 700 debitAccountType: 3 debitAccountNo: 1930 }]) { affectedRows items { voucherNo } } } } ``` ```graphql { title = "Not recommended ❌" } mutation create_voucher($cid: Int, $batchId: Int!) { useCompany(no: $cid) { voucher_create(values: [{ batchNo: $batchId voucherNo : null voucherDate: null amountDomestic: 1300 creditAccountType: 2 creditAccountNo: 50000 }]) { affectedRows items { voucherNo } } } } mutation create_voucher($cid: Int, $batchId: Int!) { useCompany(no: $cid) { voucher_create(values: [{ batchNo: $batchId voucherNo : null voucherDate: null amountDomestic: 600 debitAccountType: 3 debitAccountNo: 4300 }]) { affectedRows items { voucherNo } } } } mutation create_voucher($cid: Int, $batchId: Int!) { useCompany(no: $cid) { voucher_create(values: [{ batchNo: $batchId voucherNo : null voucherDate: null amountDomestic: 700 debitAccountType: 3 debitAccountNo: 1930 }]) { affectedRows items { voucherNo } } } } ``` ## Implement transactions in your application Business NXT GraphQL does not support transactional operations. If a request succeeds partially and has failed operations, the successful operations will be persisted. There is no mechanism to request the system to roll back the operations when something failed. This must be done in the client application. > [!TIP]+ Best practice > > Ensure that operations that must succeed entirely to be persisted are handled transactionally in your application. Let us consider the example of creating an order. This is done in two steps: 1. The order is added to the `Order` table and its `orderNo` (at least) is retrieved. 2. The order lines are added to the `OrderLines` table for the newly created order. In your application, you must handle the following cases: 1. Creating the order fails. In this case, the order lines should not be inserted. If the operation succeeds partially, you must verify the returned error and decide whether you can continue, or the order should be deleted from the table and the operation retried. 2. Creating one or more order lines failed. Typically, you do not want partial/incomplete orders in the system. Therefore, in this case, you must delete the successfully added lines (from the `OrderLines` table) and the order (from the `Order` table). ## Long running operations should be async If a GraphQL request takes more than 30 seconds to execute, you will get an HTTP timeout, even though the operation will continue to execute on the server. To avoid this problem that implies loosing the result of the operation, Business NXT provides the ability to execute the operations asynchronously. You should identify potentially long running operations and execute them async. You will receive an operation ID immediately, and you will use this to poll for the result. > [!TIP]+ Best practice > > Execute long running operations asynchronously. ```graphql { title = "Recommended ✅" } mutation update_batches_async($args: Dictionary) { asyncQuery(query:""" mutation update_batches($cid: Int) { useCompany (no:$cid) { batch_processings { updateBatch { succeeded voucherJournalNo } } } } """, args: $args) { operationId status } } Variables: { "args" : { "cid" : 123456789 } } ``` ```graphql { title = "Not recommended ❌" } mutation update_batches($cid: Int) { useCompany (no:$cid) { batch_processings { updateBatch { succeeded voucherJournalNo } } } } Variables: { "cid" : 123456789 } ``` ## Use batched requests judiciously You can send multiple requests in a single HTTP request. This is called **batched requests**. It is possible to batch both queries and mutations together in any order. They are executed individually, one by one, in the order they where provided. When batching multiple request, the results will only be returned after all the requests have been executed. A large number of requests could make the overall operation exceed the HTTP timeout interval. In this case, the operation will timeout and the result will be lost. You should use batched requests judiciously, making sure their overall execution time will not trigger an operation timeout! > [!TIP]+ Best practice > > Prefer to use an insert with multiple records over batching multiple inserts, or, similarly, an update with multiple records over batching multiple updates. ```graphql { title = "Recommended ✅" } mutation multi_update($cid : Int!, $ono : Int!) { useCompany(no : $cid) { orderLine_update( filters: [ {_and:[ {orderNo : {_eq : $ono}} {lineNo : {_eq : 1}}]}, {_and:[ {orderNo : {_eq : $ono}} {lineNo : {_eq : 2}}]} ] values: [ { priceInCurrency : 199.99, invoiceNow : 1.0 }, { priceInCurrency : 59.99, invoiceNow : 1.0 }, ]) { affectedRows items { orderNo lineNo quantity priceInCurrency invoiceNow } } } } ``` ```graphql { title = "Not recommended ❌" } mutation one_update($cid : Int!, $ono : Int!) { useCompany(no : $cid) { orderLine_update( filter: {_and:[ {orderNo : {_eq : $ono}} {lineNo : {_eq : 1}}]}, value: { priceInCurrency : 199.99, invoiceNow : 1.0 }) { affectedRows items { orderNo lineNo quantity priceInCurrency invoiceNow } } } } mutation another_update($cid : Int!, $ono : Int!) { useCompany(no : $cid) { orderLine_update( filter: {_and:[ {orderNo : {_eq : $ono}} {lineNo : {_eq : 2}}]}, value: { priceInCurrency : 59.99, invoiceNow : 1.0 }) { affectedRows items { orderNo lineNo quantity priceInCurrency invoiceNow } } } } ``` How to create and finish an order /businessnxtapi/howto/order page Guide to creating, adding lines, and finishing an order using GraphQL mutations. Includes field requirements, example queries, and responses for each step. 2025-04-15T09:48:42+02:00 # How to create and finish an order Guide to creating, adding lines, and finishing an order using GraphQL mutations. Includes field requirements, example queries, and responses for each step. You need to perform the following GraphQL requests, in this order: ## 1. Creating a new order head The minimum information you must provide is: | Field | Type | Description | | ----- | ---- | ----------- | | `orderDate` | int | The date of the order. An integer in the form `yyyymmdd`. For instance, 23 March 2022, is 20220323. | | `customerNo` | int | The number identifying the customer in Business NXT. (Do not confuse this with the Visma.net customer number!) | Use a mutation with the `order_create` field to add a new order: ```graphql { title = "Query" } mutation create_order($cid : Int!, $cno : Int!, $date : Int) { useCompany(no : $cid) { order_create(values:[{ orderDate : $date customerNo : $cno }]) { affectedRows items { orderNo orderDate } } } } ``` ```graphql { title = "Result" } { "data": { "useCompany": { "order_create": { "affectedRows": 1, "items": [ { "orderNo": 439, "orderDate": 20220401 } ] } } } } ``` From the result, you must use the `orderNo` to add order lines and execute processings such as finishing the order. ## 2. Adding order lines The minimum information you must provide for each order line is: | Field | Type | Description | | ----- | ---- | ----------- | | `orderNo` | int | The order number, from the previous mutation. | | `productNo` | int | The number identifying the product in the system. | | `quantity` | int | The quantity of items that you want to add. | Use a mutation with the `orderLine_create` field to add one or more order lines: ```graphql { title = "Query" } mutation create_order_line($cid : Int!, $ono : Int!, $pno1 : String, $pno2 : String) { useCompany(no : $cid) { orderLine_create(values:[ { orderNo : $ono productNo : $pno1 quantity : 1 }, { orderNo : $ono productNo : $pno2 quantity : 2 }, ]) { affectedRows items { lineNo } } } } ``` ```graphql { title = "Result" } { "data": { "useCompany": { "orderLine_create": { "affectedRows": 2, "items": [ { "lineNo": 1 }, { "lineNo": 2 } ] } } } } ``` ### Alternative: Create an order and its order lines with a single request You can create both an order and its order lines with a single request, using the `@export` directive. This [directive](../features/directives.md#the-export-directive) allows you to capture the value of an evaluated field into a variable that can be used later in the query. ```graphql { title = "Query" } mutation create_order_and_line($cid : Int!, $cno : Int!, $date : Int $pno1 : String, $pno2 : String, $ono : Int = 0) { useCompany(no : $cid) { # create the order first order_create(values:[{ orderDate : $date customerNo : $cno }]) { affectedRows items { # capture the value of the orderNo field # into the ono variable orderNo @export(as: "ono") orderDate } } # create the order lines orderLine_create(values:[ { orderNo : $ono productNo : $pno1 quantity : 1 }, { orderNo : $ono productNo : $pno2 quantity : 2 }, ]) { affectedRows items { orderNo lineNo } } } } ``` ```graphql { title = "Result" } { "data": { "useCompany": { "order_create": { "affectedRows": 1, "items": [ { "orderNo": 440, "orderDate": 20221122 } ] }, "orderLine_create": { "affectedRows": 2, "items": [ { "orderNo": 440, "lineNo": 1 }, { "orderNo": 440, "lineNo": 2 } ] } } } } ``` ## 3. Finishing an order The minimum information you need to finish an order is the order number. Use a mutation with the `order_processings` field to execute a processing on an order. To finish the order, use the `finish` field: ```graphql { title = "Query" } mutation finish_order($cid : Int!, $ono : Int!) { useCompany(no : $cid) { order_processings { finish( filter: {orderNo:{_eq : $ono}} ) { succeeded items { handledOrderLine { lineNo finishedNow } } } } } } ``` ```graphql { title = "Result" } { "data": { "useCompany": { "order_processings": { "finish": { "succeeded": true, "items": [ { "handledOrderLine": [ { "lineNo": 1, "finishedNow": 1 }, { "lineNo": 2, "finishedNow": 2 } ] } ] } } } } } ``` ## 3. Finishing parameters The `finish` processing has a parameter called `finishType` that is an integer defining what the processing should do. The implicit value is 0 which means the whole order should be finished. This is what we have seen in the previous example, where the argument was missing, therefore its default value was used. The possible values for `finishType` are: | Value | Description | | ----- | ----------- | | 0 | Finish the whole order. | | 1 | Finish the articles specified by their barcode. | | 2 | Finish the articles specified by their product number. | | 3 | Finish the articles specified by their `TransactionInformation1` field. | Alternatively, you can use the `finishTypeAsEnum` field, that enables you to use a enumeration value, instead of a numerical one. The possible values are: | Name | Value | | ---- | ----- | | WholeOrder | 0 | | Barcode | 1 | | ProductNumber | 2 | | TransactionInformation1 | 3 | When `finishType` has any other value than 0, then you need to provide the additional arguments using the `group` parameter. This is a dictionary of key-value pairs, where the key is the bar code, product number, or the `TransactionInformation1` value of a product, and value is the quantity to finish (remove from the order). ```graphql { title = "Query" } mutation finish_order($cid : Int!, $ono : Int!) { useCompany(no : $cid) { order_processings { finish( args : { finishTypeAsEnum : ProductNumber group : [ {key: "HW1", quantity: 2}, {key: "AGG4", quantity: 5} ] }, filter: {orderNo:{_eq : $ono}} ) { succeeded items { handledOrderLine { lineNo finishedNow } } } } } } ``` ```graphql { title = "Result" } { "data": { "useCompany": { "order_processings": { "finish": { "succeeded": true, "items": [ { "handledOrderLine": [ { "lineNo": 1, "finishedNow": 2 }, { "lineNo": 2, "finishedNow": 5 } ] } ] } } } } } ``` How to add an attachment to an order /businessnxtapi/howto/orderattachments page Instructions on adding attachments to orders using GraphQL, including setting specific document types and handling file size limitations. 2024-09-24T15:57:29+02:00 # How to add an attachment to an order Instructions on adding attachments to orders using GraphQL, including setting specific document types and handling file size limitations. You can add an attachment to an order by runnning the `AddAttachment` processing on the `Order` table. The following query adds an attachment to an existing order: ```graphql mutation upload_invoice_attachment( $cid: Int!, $orderNo: Int!, $fileName: String!, $description: String!, $data: String!) { useCompany(no: $cid) { order_processings { addAttachment( filter: { orderNo: { _eq: $orderNo } }, args: { description: $description, fileName: $fileName, fileBytes: $data, sendWithInvoicesAndCreditNotes : 1 } ) { succeeded } } } } ``` > [!NOTE] > > The content of the attachment must be provided as a base64 encoded string. The precence of the `sendWithInvoicesAndCreditNotes : 1` argument will set the order attachment processing to `Invoices/credit notes`. You can see this in the front-end, when you look at the `Order attachment processing` column of the `Order attachment` table. ![Order attachment processing](../order_attachment_processing.png) If you want to set any of the other available values, such as `Pick lists` or `Packing slips`, then you must use the `sendWithDocumentTypes` parameter instead. The following table shows the available values for this parameter: | Value | Description | | ----- | ----------- | | 1 | Invoices/credit notes | | 2 | Consignment notes | | 4 | Packing slips | | 8 | Pick lists | | 16 | Order confirmations | | 32 | Quotations | | 64 | Purchase orders | | 128 | Inquiries | | 256 | Production orders | | 512 | Order prints | | 1024 | Approval requests | The following example shows how to set the `sendWithDocumentTypes` parameter to `Packing slips`: ```graphql mutation upload_packing_slip_attachment( $cid: Int!, $orderNo: Int!, $fileName: String!, $description: String!, $data: String!) { useCompany(no: $cid) { order_processings { addAttachment( filter: { orderNo: { _eq: $orderNo } }, args: { description: $description, fileName: $fileName, fileBytes: $data, sendWithDocumentTypes : 4 } ) { succeeded } } } } ``` > [!NOTE] > > Beware there is a limit to the raw size of a request. Currently, this is set at 15MB. This limit may be prone to future changes. How to create and update a batch /businessnxtapi/howto/batch page Guide on creating and updating a batch using GraphQL. Includes steps for batch creation, determining voucher number, voucher creation, and batch update. 2025-04-15T09:48:42+02:00 # How to create and update a batch Guide on creating and updating a batch using GraphQL. Includes steps for batch creation, determining voucher number, voucher creation, and batch update. You need to perform the following GraphQL requests, in this order: ## 1. Creating a new batch The minimum information you need to provide is: | Field | Type | Description | | ----- | ---- | ----------- | | `valueDate` | int | The value date as an integer in the form `yyyymmdd`. For instance, 23 March 2022, is 20220323. | | `voucherSeriesNo` | int | The number of the voucher series. | Use a mutation with the `batch_create` field to create a new batch: ```graphql { title = "Query" } mutation create_batch($cid : Int!, $vsn : Int!, $valuedt : Int!) { useCompany(no : $cid) { batch_create(values:[ { voucherSeriesNo : $vsn valueDate : $valuedt } ]) { affectedRows items { batchNo valueDate } } } } ``` ```graphql { title = "Result" } { "data": { "useCompany": { "batch_create": { "affectedRows": 1, "items": [ { "batchNo": 1002, "valueDate": 20220415 } ] } } } } ``` You must read and store the `batchNo` in order to use it for creating a voucher. ## 2. Determining the next voucher no When creating a voucher, you need to provide a voucher number. You can determine this value by reading the `nextVoucherNo` value from the `VoucherSeries` table. To do so, you must provide the `voucherSeriesNo` which should be the same used for creating the batch. ```graphql { title = "Query" } query read_voucher_series($cid : Int!, $vsn : Int!) { useCompany(no : $cid) { voucherSeries(filter : { voucherSeriesNo : {_eq: $vsn} } ) { totalCount items { voucherSeriesNo name lastVoucherNo nextVoucherNo } } } } ``` ```graphql { title = "Result" } { "data": { "useCompany": { "voucherSeries": { "totalCount": 1, "items": [ { "voucherSeriesNo": 6, "name": "Diverse bilag", "lastVoucherNo": 69999, "nextVoucherNo": 60003 } ] } } } } ``` ## 3. Creating a new voucher The minimum information you need to provide in order to create a new voucher is: | Field | Type | Description | | ----- | ---- | ----------- | | `batchNo` | int | The number of the batch. Use the `batchNo` returned from the first mutation. | | `voucherNo` | int | The number of the voucher. Use the `nextVoucherNo` value read previously. | | `debitAccountNo` | int | The debit account number. | | `creditAccountNo` | int | The credit account number. | | `amountDomestic` | decimal | The amount value. | | `voucherDate` | int | The date of the voucher. | | `valueDate` | int | The value date of the voucher. | Use a mutation with the `voucher_create` field to create a new voucher: ```graphql { title = "Query" } mutation create_voucher($cid : Int!, $bno : Int!, $cno : Int!, $vno : Int!, $vdt : Int!, $valuedt : Int!) { useCompany(no : $cid) { voucher_create(values: [ { batchNo : $bno voucherNo : $vno debitAccountNo : 1930 creditAccountNo: $cno amountDomestic : 100 voucherDate : $vdt valueDate : $valuedt } ]) { affectedRows items { batchNo voucherNo voucherDate valueDate } } } } ``` ```graphql { title = "Result" } { "data": { "useCompany": { "voucher_create": { "affectedRows": 1, "items": [ { "batchNo": 1002, "voucherNo": 60003, "voucherDate": 20220323, "valueDate": 20220415 } ] } } } } ``` ### Alternative: Create a new voucher with suggested voucher number Instead of determining the next voucher number in the series you can request that the system figures out the next value for it. You can also do that for other fields, such as voucher date. This is done with the [Suggest Feature](/businessnxtapi/schema/mutations/inserts). ```graphql { title = "Query" } mutation create_voucher($cid : Int!, $bno : Int!, $cno : Int!, $valuedt : Int!) { useCompany(no : $cid) { voucher_create(values: [ { batchNo : $bno voucherNo : null debitAccountNo : 1930 creditAccountNo: $cno amountDomestic : 100 voucherDate : null valueDate : $valuedt } ]) { affectedRows items { batchNo voucherNo voucherDate valueDate } } } } ``` ```graphql { title = "Result" } { "data": { "useCompany": { "voucher_create": { "affectedRows": 1, "items": [ { "batchNo": 1002, "voucherNo": 60003, "voucherDate": 20220323, "valueDate": 20220415 } ] } } } } ``` ### Alternative: Create the batch and the voucher in a single request This is possible using the [@export](../features/directives.md#the-export-directive) directive. ```graphql { title = "Query" } mutation create_batch($cid : Int!, $vsn : Int!, $valuedt : Int!, $cno : Int!, $bno : Int! = 0) { useCompany(no : $cid) { batch_create(values:[ { voucherSeriesNo : $vsn valueDate : $valuedt }] ) { affectedRows items { batchNo @export(as: "bno") valueDate } } voucher_create(values: [ { batchNo : $bno voucherNo : null debitAccountNo : 1930 creditAccountNo: $cno amountDomestic : 100 voucherDate : null valueDate : $valuedt } ]) { affectedRows items { batchNo voucherNo voucherDate valueDate } } } } ``` ```graphql { title = "Result" } { "data": { "useCompany": { "batch_create": { "affectedRows": 1, "items": [ { "batchNo": 1002, "valueDate": 20220415 } ] }, "voucher_create": { "affectedRows": 1, "items": [ { "batchNo": 1002, "voucherNo": 60003, "voucherDate": 20220323, "valueDate": 20220415 } ] } } } } ``` ## 4. Updating a batch The minimum information to update a batch is the batch number. Use a mutation with the `batch_processings` field to execute processings on the batch table. Use the `updateBatch` field to update the batch: ```graphql { title = "Query" } mutation update_batch($cid : Int!, $bno : Int!) { useCompany(no : $cid) { batch_processings { updateBatch(filter : { batchNo : {_eq : $bno} } ) { succeeded voucherJournalNo } } } } ``` ```graphql { title = "Result" } { "data": { "useCompany": { "batch_processings": { "updateBatch": { "succeeded": true, "voucherJournalNo": 193 } } } } ``` How to invoice an order /businessnxtapi/howto/invoice_order page Guide on invoicing orders using GraphQL mutation. Includes example query, result, and parameter descriptions for generating invoices and credit note reports. 2025-04-15T09:48:42+02:00 # How to invoice an order Guide on invoicing orders using GraphQL mutation. Includes example query, result, and parameter descriptions for generating invoices and credit note reports. To invoice an order, you must first finish the order. You can run the `invoicesAndCreditNotes` report on the `Order` table in order to invoice an order, as shown in the following example: ```graphql { title = "Query" } mutation invoice_order($cid : Int!, $ono : [Int]!) { useCompany(no : $cid) { order_reports { invoicesAndCreditNotes( filter:{orderNo :{_in : $ono}}, returnDocuments : true, printDestination : PRINT_TO_PDF, approval : true ) { succeeded documents { name content attachments { name content } } } } } } ``` ```graphql { title = "Result" } { "data": { "useCompany": { "order_reports": { "invoicesAndCreditNotes": { "succeeded": true, "documents": [ { "name": "1000013.pdf", "content": "JVBERi0xLjQKJdD...", "attachments": [] } ] } } } } } ``` The parameters have the following meaning: | Parameter | Description | | --------- | ----------- | | `filter` | The filter to apply to the report. In this case, we are filtering by the order number (one or more orders). | | `returnDocuments` | If set to `true`, the report will return the invoice document. | | `printDestination` | The destination where the invoice will be printed. In this case, we are printing to a PDF file. | | `approval` | It updates the tables when set to `true` (the default value). Set to `false` to only preview results. | > [!TIP] > > The content of the returned documents (and attachments) is base64-encoded. You must decode it to get the original file. How to add a document to a voucher /businessnxtapi/howto/voucherdocument page Guide on adding a document to a voucher and transferring it to file service using GraphQL mutations. Includes example queries and notes on limitations. 2024-09-24T15:57:29+02:00 # How to add a document to a voucher Guide on adding a document to a voucher and transferring it to file service using GraphQL mutations. Includes example queries and notes on limitations. You can add a document to a voucher by running the the `AddNewDocument` processing on the `Voucher` table. The following query adds a document to an existing voucher: ```graphql mutation upload_voucher_document( $cid: Int!, $batchNo : Int!, $voucherNo: Int!, $fileName: String!, $description: String!, $data: String!) { useCompany(no: $cid) { voucher_processings { addNewDocument( filter: {_and:[ {batchNo : {_eq : $batchNo}}, {voucherNo : {_eq : $voucherNo}} ]}, args: { fileName : $fileName, description : $description, fileBytes : $data }) { succeeded } } } } ``` > [!NOTE] > > The content of the document must be provided as a base64 encoded string. > [!NOTE] > > Beware there is a limit to the raw size of a request. Currently, this is set at 15MB. This limit may be prone to future changes. ## Transfering a document to the file service Documents attached using the `Voucher.AddNewDocument` processing are stored in the database. If you want to transfer the document to the file service, you can use the `UploadToFileService` processing on the `IncomingAccountingDocumentAttachment` table, as shown in the following example: ```graphql mutation move_attachment_to_fileservice( $cid: Int, $fileName: String, $tok: String) { useCompany(no: $cid) { incomingAccountingDocumentAttachment_processings { uploadToFileService( filter: {fileName: {_eq: $fileName}}, args: { connectToken: $tok } ) { succeeded } } } } ``` The argument `connectToken` requires a valid Visma Connect access token. This is the same token you use to authenticate your requests to the Business NXT API. You can execute these two processings sequentially, in a single request. ```graphql mutation upload_voucher_document( $cid: Int!, $batchNo : Int!, $voucherNo: Int!, $fileName: String!, $description: String!, $data: String!, $tok: String) { useCompany(no: $cid) { voucher_processings { addNewDocument( filter: {_and:[ {batchNo : {_eq : $batchNo}}, {voucherNo : {_eq : $voucherNo}} ]}, args: { fileName : $fileName, description : $description, fileBytes : $data }) { succeeded } } incomingAccountingDocumentAttachment_processings { uploadToFileService( filter: {fileName: {_eq: $fileName}}, args: { connectToken: $tok } ) { succeeded } } } } ``` > [!NOTE] > > In the future, the `Voucher.AddNewDocument` processing may be updated to transfer the document to the file service directly without needing to execute the second processing explicitly. How to read text from the Text table /businessnxtapi/howto/texts page Learn how to query and filter text values from the Text table for various fields, including payment and delivery methods, using GraphQL. 2025-04-15T09:48:42+02:00 # How to read text from the Text table Learn how to query and filter text values from the Text table for various fields, including payment and delivery methods, using GraphQL. ## Overview The `Text` table contains text values for various fields from many tables. Examples are the text values for the payment method and delivery method of an order. It is a common need to retrieve these texts. The following image shows possible values for the payment method for an order: ![Payment methods](../text_paymentmethods.png) These values can be retrieved with a query as follows: ```graphql { title = "Query" } query read_payment_methods($cid :Int!) { useCompany(no :$cid) { text(filter:{_and : [ {textType : {_eq : 7}}, {languageNo : {_eq : 47}} ]}) { totalCount items { textNo text } } } } ``` ```graphql { title = "Result" } { "data": { "useCompany": { "text": { "totalCount": 10, "items": [ { "textNo": 1, "text": "Kontant kasse" }, { "textNo": 2, "text": "Bankkort" }, { "textNo": 3, "text": "Visa" }, { "textNo": 4, "text": "Master-/Eurocard" }, { "textNo": 5, "text": "Am-ex" }, { "textNo": 8, "text": "Tilgodelapp" }, { "textNo": 9, "text": "Gavekort" }, { "textNo": 97, "text": "Diverse" }, { "textNo": 98, "text": "Differanse dagsoppgjør" }, { "textNo": 99, "text": "Avbrutt" } ] } } } } ``` ## Filtering These text values are available in multiple languages. Therefore, you need to filter by language and text type, as shown in the previous example. The possible values for languages are the following: | LanguageNo | Language | | ---------- | -------- | | 44 | English | | 45 | Danish | | 46 | Swedish | | 47 | Norwegian | The possible values for the texttype field are presented in the following table: | Text type | Description (Identifier) | | --------- | ----------- | | 1 | FreeText | | 2 | ReminderText | | 3 | DocumentName | | 4 | DeliveryTerms | | 5 | DeliveryMethod | | 6 | PaymentTerms | | 7 | PaymentMethod | | 8 | InformationCategory | | 9 | District | | 10 | Trade | | 11 | OrderPriceGroup | | 12 | CustomerPriceGroup1 | | 13 | ProductPriceGroup1 | | 14 | EmployeePriceGroup | | 15 | PayrollRateNo | | 16 | Unit | | 17 | TaxAndAccountingGroup | | 18 | AccountSet | | 19 | ProductType1 | | 20 | TransactionGroup1 | | 21 | ProductPriceGroup2 | | 22 | BudgetLineType | | 23 | StockCountGroup | | 24 | AssociateGrouping1 | | 25 | AssociateGrouping2 | | 26 | AssociateGrouping3 | | 27 | AssociateGrouping4 | | 28 | AssociateGrouping5 | | 29 | AssociateGrouping6 | | 30 | GeneralLedgerAccountGrouping1 | | 31 | GeneralLedgerAccountGrouping2 | | 32 | GeneralLedgerAccountGrouping3 | | 33 | GeneralLedgerAccountGrouping4 | | 34 | GeneralLedgerAccountGrouping5 | | 35 | GeneralLedgerAccountGrouping6 | | 36 | OrgUnitGrouping1 | | 37 | OrgUnitGrouping2 | | 38 | OrgUnitGrouping3 | | 39 | OrgUnitGrouping4 | | 40 | OrgUnitGrouping5 | | 41 | OrgUnitGrouping6 | | 42 | ProductGrouping1 | | 43 | ProductGrouping2 | | 44 | ProductGrouping3 | | 45 | ProductGrouping4 | | 46 | ProductGrouping5 | | 47 | ProductGrouping6 | | 48 | OrderGrouping1 | | 49 | OrderGrouping2 | | 50 | OrderGrouping3 | | 51 | OrderGrouping4 | | 52 | OrderGrouping5 | | 53 | OrderGrouping6 | | 54 | ProductTransactionControlStatus | | 55 | AccountingTransactionControlStatus | | 56 | ReportHeading | | 57 | SumLine | | 58 | ProductType2 | | 59 | TransactionGroup2 | | 60 | OrgUnitStatus | | 61 | CapitalAssetGrouping1 | | 62 | CapitalAssetGrouping2 | | 63 | CapitalAssetGrouping3 | | 64 | CapitalAssetGrouping4 | | 65 | CapitalAssetGrouping5 | | 66 | CapitalAssetGrouping6 | | 67 | PaymentPriority | | 68 | DeliveryPriority | | 69 | AppointmentPriority | | 70 | DayPriority | | 71 | DocumentGroup | | 72 | ProductPriceGroup3 | | 73 | CustomerPriceGroup2 | | 74 | AssociateGrouping7 | | 75 | AssociateGrouping8 | | 76 | AssociateGrouping9 | | 77 | AssociateGrouping10 | | 78 | AssociateGrouping11 | | 79 | AssociateGrouping12 | | 80 | GeneralLedgerAccountGrouping7 | | 81 | GeneralLedgerAccountGrouping8 | | 82 | GeneralLedgerAccountGrouping9 | | 83 | GeneralLedgerAccountGrouping10 | | 84 | GeneralLedgerAccountGrouping11 | | 85 | GeneralLedgerAccountGrouping12 | | 86 | CapitalAssetGrouping7 | | 87 | CapitalAssetGrouping8 | | 88 | CapitalAssetGrouping9 | | 89 | CapitalAssetGrouping10 | | 90 | CapitalAssetGrouping11 | | 91 | CapitalAssetGrouping12 | | 92 | OrgUnitGrouping7 | | 93 | OrgUnitGrouping8 | | 94 | OrgUnitGrouping9 | | 95 | OrgUnitGrouping10 | | 96 | OrgUnitGrouping11 | | 97 | OrgUnitGrouping12 | | 98 | ProductGrouping7 | | 99 | ProductGrouping8 | | 100 | ProductGrouping9 | | 101 | ProductGrouping10 | | 102 | ProductGrouping11 | | 103 | ProductGrouping12 | | 104 | OrderGrouping7 | | 105 | OrderGrouping8 | | 106 | OrderGrouping9 | | 107 | OrderGrouping10 | | 108 | OrderGrouping11 | | 109 | OrderGrouping12 | | 110 | TransactionGroup3 | | 111 | TransactionGroup4 | | 112 | ProductType3 | | 113 | ProductType4 | | 114 | AppointmentGrouping1 | | 115 | AppointmentGrouping2 | | 116 | AppointmentGrouping3 | | 117 | AppointmentGrouping4 | | 118 | AppointmentGrouping5 | | 119 | AppointmentGrouping6 | | 120 | AppointmentGrouping7 | | 121 | AppointmentGrouping8 | | 122 | AppointmentGrouping9 | | 123 | AppointmentGrouping10 | | 124 | AppointmentGrouping11 | | 125 | AppointmentGrouping12 | | 126 | PriceType | | 127 | CreateDocument | | 128 | CrmTexts | | 129 | EftCurrencyCode | | 130 | EftTaxCode | | 131 | EftDeclarationCode | | 132 | EftPaymentMethod | | 133 | PriceRefundGrouping1 | | 134 | PriceRefundGrouping2 | | 135 | EuGoodsStatisticsNo | | 137 | AssociateInformationGroup1 | | 138 | AssociateInformationGroup2 | | 139 | AssociateInformationGroup3 | | 140 | AssociateInformationGroup4 | | 141 | AssociateInformationGroup5 | | 142 | AssociateInformationGroup6 | | 143 | AssociateInformationGroup7 | | 144 | AssociateInformationGroup8 | | 145 | ProductCategory | | 146 | CustomerPriceGroup3 | | 147 | ProxyType | | 148 | RoleType | | 149 | MessageType | | 150 | ExternalConfigurationGrouping1 | | 151 | ExternalConfigurationGrouping2 | | 152 | ExternalConfigurationGrouping3 | | 153 | ExternalConfigurationGrouping4 | | 154 | InterestRateGroup | | 155 | SmsProvider | | 156 | FreeInformationType1 | | 157 | FreeInformationGrouping1 | | 158 | FreeInformationGrouping2 | | 159 | FreeInformationGrouping3 | | 160 | FreeInformationGrouping4 | | 161 | ShipmentGrouping1 | | 162 | ShipmentGrouping2 | | 163 | VoucherGroup1 | | 164 | VoucherGroup2 | | 165 | VoucherTypeText | | 166 | AlternativeProductGrouping1 | | 167 | AlternativeProductGrouping2 | | 168 | StructureGrouping1 | | 169 | StructureGrouping2 | | 170 | StructureGrouping3 | | 171 | StructureGrouping4 | | 172 | StructureGrouping5 | | 173 | StructureGrouping6 | | 174 | StructureGrouping7 | | 175 | StructureGrouping8 | | 176 | StructureGrouping9 | | 177 | StructureGrouping10 | | 178 | StructureGrouping11 | | 179 | StructureGrouping12 | | 180 | FreeInformationCategory | | 181 | ExternalExportGrouping | | 182 | TimeScheduleBalanceGroup | | 183 | TaxTerm | | 184 | BankFormat | | 185 | AppointmentDescription | | 186 | RemittanceCodeForRemittanceAgreements | | 187 | RegistrationTypeForPaymentAgreements | | 188 | CommentCodeForPaymentAgreements | | 189 | FreeInformation1Type2 | | 190 | FreeInformation1Type3 | | 191 | FreeInformation2Type2 | | 192 | FreeInformation2Type3 | | 193 | FreeInformation3Type2 | | 194 | FreeInformation3Type3 | | 195 | FreeInformationGrouping5 | | 196 | FreeInformationGrouping6 | | 197 | FreeInformationGrouping7 | | 198 | FreeInformationGrouping8 | | 199 | FreeInformationGrouping9 | | 200 | FreeInformationGrouping10 | | 201 | FreeInformationGrouping11 | | 202 | FreeInformationGrouping12 | | 203 | EftFormType | | 204 | DeliveryAlternativeGrouping1 | | 205 | DeliveryAlternativeGrouping2 | | 206 | GiroType | | 207 | OssTaxTerm | | 208 | EmailTemplateGroup | | 211 | ExemptReason | | 212 | InvoiceNote | ## Joining values from the text table The values from the `Text` table are usually needed when reading records from other tables. An example was previously given: reading the payment method and the delivery method of an order. A direct joining mechanism is not available in the API (such as a `joinup_` \ `joindown_` relation). However, it is possible to read them in a single query with the help of the [@export directive](../features/directives.md#the-export-directive). The following example shows how to read the payment and delivery method of a particular order: ```graphql { title = "Query" } query read_texts($cid : Int!, $orderNo : Int, $dm : Long = 0, $pm : Long = 0) { useCompany(no : $cid) { order(filter :{orderNo :{_eq : $orderNo}}) { items { orderNo deliveryMethod @export(as :"dm") paymentMethod @export(as : "pm") } } deliveryMethodName : text(filter: { languageNo :{_eq : 47} # Norwegian textType : {_eq : 5} # delivery method textNo : {_eq : $dm} }) { items { text } } paymentMethodName : text(filter: { languageNo :{_eq : 47} # Norwegian textType : {_eq : 7} # payment method textNo : {_eq : $pm} }) { items { text } } } } ``` ```graphql { title = "Result" } { "data": { "useCompany": { "order": { "items": [ { "orderNo": 1, "deliveryMethod": 4, "paymentMethod": 3 } ] }, "deliveryMethodName": { "items": [ { "text": "Tollpost" } ] }, "paymentMethodName": { "items": [ { "text": "Visa" } ] } } } } ``` What you have to do is the following: - read the numerical value of the text type you want to retrieve (such as `paymentMethod` and `deliveryMethod` in this example) - export this numerical value to a query variable using the `@export` directive - make another read, this time from the `Text` table, and filter using the desired language, the appropriate text type (such as 5 for `DeliveryMethod` and 7 for `PaymentMethod`), and the text number stored in the (previously read) query variable - you can make as many reads as you want from the `Text` table in a single query, provided you diferentiate them using [aliases](../features/aliases.md) Release Notes /businessnxtapi/releasenotes section Here you can find new and old release notes for the Business NXT API 2025-01-30T10:42:12+02:00 # Release Notes Here you can find new and old release notes for the Business NXT API ### 2025.01.30 - Change of access rights handling: tables that are not readable will no longer appear in mutations for create/update/delete. Processings and reports will also be unavailable for these tables. Columns that are not readable will not be available in input types for create and update operations. - Support for enumeration type domains for columns, arguments, or result values. See [enum fields](/businessnxtapi/schema/enumdomains/). ### 2025.01.23 - Support for specifying that a field's execution depends on another one. See the [@dependsOn directive](/businessnxtapi/features/directives/). - Support for deleting an asynchronous request job. See [deleting an asynchronous request](businessnxtapi/schema/async/#deleting-an-asynchronous-request). ### 2024.10.09 - Availability of new `createdTimestamp` and `changedTimestamp` datetime fields on all tables. See [Date and time fields](businessnxtapi/schema/datetime/). - Improved error messages for two scenarious where available companies cannot be fetched. - Fix: date fields suffixed with `AsDate` return `null` if the date is invalid, instead of throwing an error. The situation can arrive when an invalid date was set in the system (a feature used in some limited scenarios). The actual date can still be fetched using the column field of type `Int`. ### 2024.07.19 - Support for specifying the language in `useModel` nodes with a numerical value (eg. `47` for Norwegian). See [Language selection](/businessnxtapi/schema/queries/modelinfo/). - Improved messages for some category of errors (context failures). ### 2024.06.27 - Support for inserting records in between existing ones. See [Insert between existing records](businessnxtapi/schema/mutations/inserts/). ### 2024.06.20 - Extended model information with relations and domain members and access restrictions, availability, and additional properties for each entity type. See [Model information](/businessnxtapi/schema/queries/modelinfo/). - Fields assigned from unset variables are ignored in insert/update mutations. See [Assigning fields from unset variables](/businessnxtapi/schema/mutations/inserts/). - Support for comparing date and time fields with the current date and time. See [Comparing with current date/time](/businessnxtapi/features/filtering/). - New argument `orderBy` for defining the sorting order of the results. See [Sorting](/businessnxtapi/features/sorting/). > [!WARNING] > > The previous `sortOrder` argument is deprecated and will be removed in the future. - Fixed bug that affected blob fields: base-64 encoded values were stored instead of the actual binary data. ### 2024.05.30 - Support for fetch distinct values using a new query argument called `distinct`. See [Distinct values](/businessnxtapi/schema/queries/distinct/). > [!WARNING] > > The previous schema section for aggregates, which includes distinct, is obsolete and will be removed in the future. See [Aggregates](/businessnxtapi/schema/queries/aggregates/). - Fix: incorrect results when filtering with `_is_null` / `_is_not_null` on blob columns. ### 2024.05.23 - Support for `_in` / `_not_in` operators in `having` expressions. - Support for `_between` / `_not_between` operators in filters and `having` expressions. See [Filtering: `_between` / `_not_between` operators](/businessnxtapi/features/filtering/). - Support for domain information in the model. See [Model information](/businessnxtapi/schema/queries/modelinfo/). - Eased some limitations on async requests. See [Async queries](/businessnxtapi/schema/async/). - Defined a limit of 32 requests in a batch. See [Batched requests](/businessnxtapi/features/batches/). - Improved description of table columns in the schema. See [Understanding column descriptions](/businessnxtapi/schema/queries/query/) ### 2024.03.07 - Async queries return correct results when fetching data from multiple tables in a single query. Also the `totalCount` fields works as expected. See [Async queries](/businessnxtapi/schema/async/). - Added `createdDateTimeUtc` and `changedDateTimeUtc` as well as `createdDateTimeTZ` and `changedDateTimeTZ` for filters. See [Datetime special fields](/businessnxtapi/schema/datetime/). - Performance improvement for pagination when `hasNextPage` is requested. For tables of tens of millions of records, the execution time is reduced with 20-25 seconds. - Empty arrays for `_in` and `_not_int` comparison operators in filter result in no data being returned. See [Filtering](/businessnxtapi/features/filtering/). - Fixed `Illegal query` bug for some queries that fetch aggregates using the group by feature. - Fixed `Unknown company` error when a join operation (`joinup_` or `joinddown_` field) is used in the projection a mutation request. ### 2024.01.25 - Added support for grouping by data. See [Grouping](/businessnxtapi/schema/queries/grouping/). - Define `changedDateTime` with second and millisecond precision for table that include the `changeTimeInMs` column. See [Date and time fields](/businessnxtapi/schema/datetime/). - Removed the restriction of executing a single async query at a time. See [Async queries](/businessnxtapi/schema/async/). - The `path` of a failed assignment in an update request (index of record) included an `error` object. See [Error handling](/businessnxtapi/features/errors/). - The `path` of a failed assignment in a create or update request included the `error` object when the operation is executed asynchronously. ### 2023.12.07 - Support for collecting multiple edits in a single update operation. See [Update operations](/businessnxtapi/schema/mutations/updates/). - Increased internal execution timeout to 59 seconds to allow longer synchronous operations. This value may be subject to change. [Async queries](/businessnxtapi/schema/async/) are recommended for long running operations. ### 2023.10.26 - Fix: Support for bitflag values passed in query variables. See [Bitflags](/businessnxtapi/schema/bitflags/). ### 2023.10.19 - Support for exporting values to an array variable. See the [@export directive](/businessnxtapi/features/directives/). ### 2023.10.05 - Fix: correct handling of filters with `changedDateTime`/`createdDateTime` and other `_or` expressions ### 2023.09.28 - Include name of status codes (with `status_name`) in error data not just status codes. See [Error handling](/businessnxtapi/features/errors/). ### 2023.09.07 - Fix: support for `byte` values in processing/report arguments - Fix: partially finish order ### 2023.08.24 - Fix: `hasNextPage` report correct value when all data has been fetched - Fix: allow more field to be set to `null` in order to request to suggest value ### 2023.07.20 - Support for batching multiple requests together. See [Batched requests](/businessnxtapi/features/batches/). ### 2023.07.06 - Support for skipping records. See [Pagination](/businessnxtapi/features/pagination/). - Better support for working with bit flags columns. See [Bitflags](/businessnxtapi/schema/bitflags/). ### 2023.06.29 - Support for fetching model information. See [Model information](/businessnxtapi/schema/queries/modelinfo/). ### 2023.06.01 - Support for `_in` and `_not_in` operators in filters. See [Filtering](/businessnxtapi/features/filtering/). ### 2023.04.27 - Added a new way to suggest values in the correct field order using `null` values. See [Suggested values](/businessnxtapi/schema/mutations/inserts/). - Deprecated the `suggest` argument for suggesting values. > [!WARNING] > > the `suggest` argument will be removed in the future. - Removed the deprecated `suggestValues` argument for suggesting values. ### 2023.02.09 - Added support for fetching distinct values. See [Distinct values](/businessnxtapi/schema/queries/distinct/). ### 2023.01.05 - The `availableCustomers` field works for service-to-service integrations. - The `availableCompany` returns all the companies from all customers if no customer ID was provided, not just the companies of the first found customer (as previously available). - Company information from the `availableCompanies` field includes customer ID with the `vismaNetCustomerId` field. ### 2022.12.15 - **`breaking change`**: Access to system tables available under the new `useCustomer` field both for queries and mutations. See [Accessing system tables](/businessnxtapi/schema/queries/query/) ### 2022.11.10 - Better support for suggesting values with the `suggest` argument. The previous `suggestValues` argument is deprecated and will be removed after 1 March 2023. See [Suggested values](/businessnxtapi/schema/mutations/inserts/). - Fix for `@export` directive. Nodes using exported variables execute only after variables have been resolved. ### 2022.09.29 - Improved performance for queries containing joins. See [Unoptimized queries](/businessnxtapi/features/unoptimized/). - Added the creation and completion time in the result value for asynchronous queries. See [Async queries](/businessnxtapi/schema/async/). - Fix for mutations timeout when a record is not found. ### 2022.08.25 - Support for fetching available companies when using client credentials grant type for the access token (for service-to-service integrations). See [Fetching user's available companies](/businessnxtapi/schema/queries/companies/). - Added the `@export` custom directive. See [Directives](/businessnxtapi/features/directives/). ### 2022.08.04 - Support for suggesting values when inserting new rows. See [Suggested values](/businessnxtapi/schema/mutations/inserts/). - Full support for aliases in queries. See [Aliases](/businessnxtapi/features/aliases/). ### 2022.07.08 - Support for asynchronous queries. See [Async queries](/businessnxtapi/schema/async/). - Added [Brotli](https://en.wikipedia.org/wiki/Brotli) compression for all responses. ### 2022.05.26 - Support for service-to-service integrations. See [Service integrations](/businessnxtapi/authentication/service/). Interactive GraphiQL IDE /businessnxtapi/graphiql section Link to an external Interactive GraphiQL IDE 2024-09-24T15:57:29+02:00 # Interactive GraphiQL IDE Link to an external Interactive GraphiQL IDE Deprecated features /businessnxtapi/deprecated_features section Description of deprecated features and their replacements, including sortOrder, suggest, and aggregate fetching. 2024-09-24T15:57:29+02:00 # Deprecated features Description of deprecated features and their replacements, including sortOrder, suggest, and aggregate fetching. The features listed in this section are deprecated and will be removed in future versions of the API. ## Sorting order argument The `sortOrder` argument has been replaced by the `orderBy` argument. The `orderBy` argument is similar to `sortOrder`, except that it is an array of objects instead of a single object. For more details see [Sorting](/businessnxtapi/features/sorting/). > [!NOTE] > > This feature will not be available after July 1, 2025. ## Suggesting argument The `suggest` argument has been replaced with passing `null` to values or using the `field_suggest_interval` fields. For more details see [Suggesting values](/businessnxtapi/schema/mutations/inserts/). > [!NOTE] > > This feature will not be available after July 1, 2025. ## Aggregates The schema section that enables fetching of some aggregate values is obsolete and has been replaced with a more complex reading using grouping. For more details see [Grouping](/businessnxtapi/schema/queries/grouping/). > [!NOTE] > > This feature will not be available after July 1, 2025.